博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Sentinel限流示例:编码和注解限流
阅读量:5226 次
发布时间:2019-06-14

本文共 11448 字,大约阅读时间需要 38 分钟。

一、Sentinel 是什么?

随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。

Sentinel 具有以下特征:

  • 丰富的应用场景:Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用应用等。

  • 完备的实时监控:Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据,甚至 500 台以下规模的集群的汇总运行情况。

  • 广泛的开源生态:Sentinel 提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring Cloud、Dubbo、gRPC 的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入 Sentinel。

  • 完善的 SPI 扩展点:Sentinel 提供简单易用、完善的 SPI 扩展接口。您可以通过实现扩展接口来快速地定制逻辑。例如定制规则管理、适配动态数据源等。

Sentinel 的主要特性:

Sentinel 的开源生态:

二、如何使用Sentinel

我们说的资源,可以是任何东西,服务,服务里的方法,甚至是一段代码。使用Sentinel 来进行资源保护,主要分为两个步骤:

  •     定义资源
  •     定义规则

先把可能需要保护的资源定义好,之后再配置规则。也可以理解为,只要有了资源,我们就可以在任何时候灵活地定义各种流量控制规则。在编码的时候,只需要考虑这个代码是否需要保护,如果需要保护,就将之定义为一个资源。

对于主流的框架,我们提供适配,只需要按照适配中的说明配置,Sentinel 就会默认定义提供的服务,方法等为资源。

三、示例

3.1、Sentinel编码限流

先来简单的体验下 Sentinel 吧,在你的Maven项目中增加 Sentinel 的依赖:

com.alibaba.csp
sentinel-core
1.4.0

Sentinel中需要限流的称之为资源,对资源进行处理,下面来看最简单的一段代码:

package com.example.demo.service;import java.util.ArrayList;import java.util.List;import java.util.concurrent.TimeUnit;import com.alibaba.csp.sentinel.Entry;import com.alibaba.csp.sentinel.SphU;import com.alibaba.csp.sentinel.slots.block.BlockException;import com.alibaba.csp.sentinel.slots.block.RuleConstant;import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;public class LimitByCoding {    public static void main(String[] args) throws InterruptedException {        initFlowRules();        long startTime = System.currentTimeMillis();        for (int i = 0; i < 1000; i++) {            /* 您的业务逻辑 - 开始 */            System.out.println("hello world");        }        long endTime = System.currentTimeMillis();        System.out.println(endTime - startTime);        long startTime2 = System.currentTimeMillis();        for (int i = 0; i < 1000; i++) {            Entry entry = null;            try {                entry = SphU.entry("HelloWorld");                /* 您的业务逻辑 - 开始 */                System.out.println("hello world");                /* 您的业务逻辑 - 结束 */            } catch (BlockException e1) {                /* 流控逻辑处理 - 开始 */                System.out.println("block!");                /* 流控逻辑处理 - 结束 */            } finally {                if (entry != null) {                    entry.exit();                }            }        }        long endTime2 = System.currentTimeMillis();        System.out.println(endTime2 - startTime2);    }    private static void initFlowRules() {        List
rules = new ArrayList<>(); FlowRule rule = new FlowRule(); rule.setResource("HelloWorld"); rule.setGrade(RuleConstant.FLOW_GRADE_QPS); // Set limit QPS to 20. rule.setCount(20); rules.add(rule); FlowRuleManager.loadRules(rules); }}

第一行中初始化限流的规则,创建了一个资源叫 HelloWorld,设置了这个资源的QPS 为 20。

结果:

上面的例子中次数为1000次,只输出一句话,不做任何限制,执行完成的时间大概在9毫秒左右。

下面加上限流的逻辑,执行完成的时间基本上就超过91毫秒了,可见限流起了作用。

 

在业务开始前使用SphU.entry();方法标识开始,结束使用entry.exit();,如果触发了流控逻辑就会抛出BlockException异常让用户自行处理。

代码运行之后,我们可以在日志 ~/logs/csp/${appName}-metrics.log.xxx 里看到下面的输出:

|--timestamp---|------date time--|-resource-|p |block|s  |e|rt    1529998904000|2018-06-26 15:41:44|HelloWorld|20|0    |20|0|0    1529998905000|2018-06-26 15:41:45|HelloWorld|20|5579 |20|0|728    1529998906000|2018-06-26 15:41:46|HelloWorld|20|15698|20|0|0    1529998907000|2018-06-26 15:41:47|HelloWorld|20|19262|20|0|0    1529998908000|2018-06-26 15:41:48|HelloWorld|20|19502|20|0|0    1529998909000|2018-06-26 15:41:49|HelloWorld|20|18386|20|0|0

 

  • p:通过的请求,

  • block:被阻止的请求

  • s:成功执行完成的请求个数

  • e:用户自定义的异常

  • rt:平均响应时长。

 

3.2、Sentinel注解限流

这种做法不好的地方在于每个需要限制的地方都得写代码,从 0.1.1 版本开始,Sentinel 提供了 @SentinelResource 注解的方式,非常方便。

要使用注解来保护资源需要引入下面的Maven依赖:

com.alibaba.csp
sentinel-annotation-aspectj
1.4.0

 完整的pom.xml

4.0.0
org.springframework.boot
spring-boot-starter-parent
1.5.19.RELEASE
com.dxz
LimitDemo
0.0.1-SNAPSHOT
LimitDemo
Demo project for Spring Boot
1.8
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-tomcat
com.alibaba.csp
sentinel-annotation-aspectj
1.4.0
org.springframework
spring-core
4.3.7.RELEASE
org.springframework
spring-beans
4.3.7.RELEASE
org.springframework
spring-context
4.3.7.RELEASE
org.springframework
spring-aop
4.3.7.RELEASE
org.springframework
spring-expression
4.3.17.RELEASE
org.springframework.boot
spring-boot-starter
slf4j-api
org.slf4j
1.7.10
ch.qos.logback
logback-classic
1.2.2
org.springframework.boot
spring-boot-starter-test
test
org.springframework.boot
spring-boot-maven-plugin

 

引入之后我们需要配置SentinelResourceAspect切面让其生效,因为是通过SentinelResourceAspect切面来实现的,我这边以Spring Boot中使用进行配置示列:

package com.example.demo.config;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import com.alibaba.csp.sentinel.annotation.aspectj.SentinelResourceAspect;@Configurationpublic class AopConfiguration{    @Bean    public SentinelResourceAspect sentinelResourceAspect()    {        return new SentinelResourceAspect();    }}

 

然后在需要限制的方法上加SentinelResource注解即可:

package com.example.demo.service;import org.springframework.stereotype.Component;import com.alibaba.csp.sentinel.annotation.SentinelResource;import com.alibaba.csp.sentinel.slots.block.BlockException;import com.example.demo.ExceptionUtil;@Componentpublic class BuzService {        @SentinelResource(value = "get", blockHandler = "exceptionHandler")    public String get(String id) {        return "http://cxytiandi.com";    }    @SentinelResource(value = "get2", blockHandler = "handleException", blockHandlerClass = { ExceptionUtil.class })    public String get2() {        return "http://cxytiandi.com";    }    public String exceptionHandler(String id, BlockException e) {        e.printStackTrace();        return "错误发生在" + id;    }}

SentinelResource:value

表示资源名,必填项

SentinelResource:blockHandler

处理 BlockException 的方法名,可选项。若未配置,则将 BlockException 直接抛出。

  • blockHandler 函数访问范围需要是 public

  • 返回类型需要与原方法相匹配

  • 参数类型需要和原方法相匹配并且最后加一个额外的参数,类型为 BlockException

  • blockHandler 函数默认需要和原方法在同一个类中

如果你不想让异常处理方法跟业务方法在同一个类中,可以使用 blockHandlerClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。

业务方法:

@SentinelResource(value = "get2", blockHandler = "handleException", blockHandlerClass = { ExceptionUtil.class })    public String get2() {        return "http://cxytiandi.com";    }

 

异常处理类:

package com.example.demo;import com.alibaba.csp.sentinel.slots.block.BlockException;public final class ExceptionUtil {    public static String handleException(BlockException ex) {        System.err.println("错误发生: " + ex.getClass().getCanonicalName());        return "error";    }}

如何测试?

我们可以在Spring Boot的启动类中定义规则,然后快速访问接口,就可以看出效果啦,或者用压力测试工具ab等。

package com.example.demo;import java.util.ArrayList;import java.util.List;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import com.alibaba.csp.sentinel.slots.block.RuleConstant;import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;@SpringBootApplicationpublic class LimitDemoApplication {    public static void main(String[] args) {        initFlowRules();        SpringApplication.run(LimitDemoApplication.class, args);    }    private static void initFlowRules() {        List
rules = new ArrayList<>(); FlowRule rule = new FlowRule(); rule.setResource("get"); rule.setGrade(RuleConstant.FLOW_GRADE_QPS); rule.setCount(1); rules.add(rule); rule = new FlowRule(); rule.setResource("get2"); rule.setGrade(RuleConstant.FLOW_GRADE_QPS); rule.setCount(1); rules.add(rule); FlowRuleManager.loadRules(rules); }}

结果:

 

源码分析

只需要配置了SentinelResourceAspect就可以使用注解,我们来简单的看下SentinelResourceAspect的源码

@Aspect    public class SentinelResourceAspect extends AbstractSentinelAspectSupport {  @Pointcut("@annotation(com.alibaba.csp.sentinel.annotation.SentinelResource)")    public void sentinelResourceAnnotationPointcut() {    }         @Around("sentinelResourceAnnotationPointcut()")    public Object invokeResourceWithSentinel(ProceedingJoinPoint pjp) throws Throwable {    // 获取当前访问的方法    Method originMethod = resolveMethod(pjp);    // 获取方法上的SentinelResource注解    SentinelResource annotation = originMethod.getAnnotation(SentinelResource.class);    if (annotation == null) {    // Should not go through here.    throw new IllegalStateException("Wrong state for SentinelResource annotation");    }    // 获取资源名    String resourceName = getResourceName(annotation.value(), originMethod);    EntryType entryType = annotation.entryType();    Entry entry = null;    try {    entry = SphU.entry(resourceName, entryType, 1, pjp.getArgs());    Object result = pjp.proceed();    return result;    } catch (BlockException ex) {    // 处理被限制的异常,回调事先配置的异常处理方法    return handleBlockException(pjp, annotation, ex);    } catch (Throwable ex) {    Tracer.trace(ex);    throw ex;    } finally {    if (entry != null) {    entry.exit();    }    }    }    }

 

上面是整个切面的代码,对所有加了SentinelResource注解的方法进去切入。细节代码在AbstractSentinelAspectSupport中,大家自己去看看。

转载于:https://www.cnblogs.com/duanxz/p/5280636.html

你可能感兴趣的文章
Go语言学习笔记(6)——指针
查看>>
第5次作业+160+曾元鹏
查看>>
String与Date的互相转换
查看>>
python的多行注释
查看>>
连接Oracle需要jar包和javadoc文档的下载
查看>>
UVA 10976 - Fractions Again?!
查看>>
poj 1183
查看>>
Dreamweaver cc新版本css单行显示
查看>>
Redis的Sorted Set有序集合命令
查看>>
【android】安卓的权限提示及版本相关
查看>>
java Linkedhashmap源码分析
查看>>
2016 年开发者头条十大文章系列
查看>>
【转载】COM 组件设计与应用(九)——IDispatch 接口 for VC6.0
查看>>
WIN2000 XP 2003系统密码破解方法
查看>>
64. Minimum Path Sum
查看>>
3D重建的进阶了解---深度图,网格,体素,点云是什么
查看>>
Bootstrap 学习笔记 之表单(4 day)
查看>>
EntityFramework 学习 一 Add New Entity using DBContext in Disconnected Scenario
查看>>
站点地图区域
查看>>
Python strip()方法介绍
查看>>