Sentinel 流量防卫兵

一、Sentinel Dashboard 安装运行

  1、下载地址:https://github.com/alibaba/Sentinel/releases/download/1.8.2/sentinel-dashboard-1.8.2.jar

  启动

java -Dserver.port=8888 -Dcsp.sentinel.dashboard.server=localhost:8888 -Dproject.name=sentinel-dashboard -Dsentinel.dashboard.auth.username=sentinel -Dsentinel.dashboard.auth.password=sentinel -jar sentinel-dashboard-1.8.2.jar > /dev/null 2>&1 &

  -Dserver.port:设置控制台端口;-Dcsp.sentinel.dashboard.server=localhost:设置控制台的地址;-Dproject.name=sentinel-dashboard:设置控制台名称;-Dsentinel.dashboard.auth.username:设置服务账户;-Dsentinel.dashboard.auth.password:设置服务密码。

  注:csp.sentinel.dashboard.server这个配置是用在客户端,这里Sentinel控制台也使用是用于自己监控自己程序的api,否则无法显示控制台的api情况,当然这个也可以根据情况不显示。

  2、项目中引入Sentinel Dashboard

  引入依赖

        <!--sentinel客户端依赖-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
            <version>2.2.3.RELEASE</version>
        </dependency>

  环境配置

spring:
  application:
    name: consumer01-depart
  cloud:
    sentinel:
      transport:
        dashboard: 172.20.10.2:8888
        port: 8719
        eager: true

  Sentinel Dashboard使用的是懒加载,因此需要调用接口后才会在控制台显示,如果只引入而不调用接口,是不会在控制台显示的。由于这里只是简单的配置,还没有调用使用sentinel的接口,因此控制台是看不到的,到下面再演示。

二、服务降级

  对于 Sentinel,服务降级的实现方式根据消费者类型的不同,其支持两种方式:Sentinel 式降级和Feign 式降级

    Sentinel 式降级:通过 Sentinel 自身的API 实现的降级方式,适用于任意消费者类型。而根据降级方法应用范围、定义位置及可维护性的不同,又可分为两种:方法级降级(降级方法与原方法定义在同一个类中,其仅是本类中的原方法可以使用)和类级降级(降级方法定义在专门的一个类中,其是一个可以被共享的降级类。该类中的所有方法均为降级方法,所以便于维护与管理)

    Feign 式降级:通过 OpenFeign 的API 实现的降级方式,仅适用于 Feign 客户端的消费者类型。其只有类级降级方式。

  下面演示各种降级,对于Sentinel及Sentinel Dashboard的配置不再说明,按照上面处理即可。

(一) Sentinel方法式降级

  使用Sentinel做服务降级,需要用到@SentinelResource注解,该注解表明当前方法是一个由Sentinel管理的资源,value属性用于指定该资源的名称,fallback表示降级的方法。

@RestController
@RequestMapping("/sentinel/consumer/method/depart")
public class SentinelMethodController {

    @Autowired
    private DepartService departService;


    //跨服务根据id查询
    @GetMapping("/get/{id}")
    @SentinelResource(value = "getDepartById", fallback = "getDepartByIdBack")
    public Depart getHandle(@PathVariable("id") int id) {
        int i = 1/0;
        return departService.getDepartById(id);
    }

    public Depart getDepartByIdBack( int id){
        return Depart.builder().id(id).name("sentinel method fall back").build();
    }

}

 

   验证:

      

 

 

 (二)Sentinel类降级

  定义一个降级的处理类

public class DepartSentinelFallBack {

    public static Depart getDepartByIdBack(int id, Throwable e) {
        return Depart.builder().id(id).name("sentinel class fall back======" + e.getMessage()).build();
    }

    public static List<Depart> listAllDepartsBack() {
        return null;
    }
}

  然后使用,同样是使用@SentinelResource注解,不过需要使用fallbackClass来注明降级类

@RestController
@RequestMapping("/sentinel/consumer/class/depart")
public class SentinelClassController {

    @Autowired
    private DepartService departService;


    //跨服务根据id查询
    @GetMapping("/get/{id}")
    @SentinelResource(fallback = "getDepartByIdBack", fallbackClass = DepartSentinelFallBack.class)
    public Depart getHandle(@PathVariable("id") int id) {
        int i = 1/0;
        return departService.getDepartById(id);
    }

    //跨服务根据列表查询
    @GetMapping("/list")
    @SentinelResource(fallback = "listAllDepartsBack", fallbackClass = DepartSentinelFallBack.class)
    public List<Depart> listHandle() {
        return departService.listAllDeparts();
    }

}

 

   演示:

      

 

 

 (三)Feign式降级

  1、开启feign对sentinel的支持

feign:
  client:
    config:
      default:
        #连接超时时间
        connectTimeout: 5000
        #数据读取超时是时间
        readTimeout: 5000
  sentinel:
    enabled: true

  2、开启对feign客户端的支持

@SpringBootApplication
@EnableFeignClients(basePackages = "com.lcl.cloud.alibaba.sentinel.service")//开启当前服务支持Feign客户端,作用扫描所有客户端接口
public class ConsumerSentinelApplication {

    public static void main(String[] args) {
        SpringApplication.run(ConsumerSentinelApplication.class, args);
    }

}

  3、定义降级类

@Component
@RequestMapping("/feign/consumer/fallback/depart")
public class DepartFeignFallBack implements DepartService {
    @Override
    public boolean saveDepart(Depart depart) {
        return false;
    }

    @Override
    public boolean removeDepartById(int id) {
        return false;
    }

    @Override
    public boolean modifyDepart(Depart depart) {
        return false;
    }

    @Override//接口降级处理方法:在出现异常或者是找不到对应服务,就走这个服务,而不是给用户输出connection refuse
    public Depart getDepartById(int id) {
        return Depart.builder().name("feign fall back").build();
    }

    @Override
    public List<Depart> listAllDeparts() {
        return null;
    }
}

  4、修改feign接口

@FeignClient(value = "provider02Nacosconfig"
        , fallback = DepartFeignFallBack.class
        //, configuration = LogConfig.class
        )//注解作用:声明当前为Feign客户端接口
@RequestMapping("/feign/depart")// 参数为要调用的提供者相应的uri,抽取所有方法的公有uri地址
public interface DepartService {//更加符合面向接口api调用习惯

    @PostMapping("/save")
    boolean saveDepart(@RequestBody Depart depart);
    @DeleteMapping("/del/{id}")
    boolean removeDepartById(@PathVariable("id") int id);
    @PutMapping("/update")
    boolean modifyDepart(@RequestBody Depart depart);
    @GetMapping("/get/{id}")
    Depart getDepartById(@PathVariable("id") int id);
    @GetMapping("/list")
    List<Depart> listAllDeparts();
}

 

  验证:

      

 

 

   此时再看Sentinel Dashboard,已经可以看到该服务。

      

 

 

 三、服务熔断

  熔断器原理:

    熔断器状态机有3个状态:

      关闭状态:所有请求正常访问

      打开状态:所有请求都会被降级

      半开状态:打开状态不是永久的,打开一会后会进入休眠时间(默认5秒))。休眠时间过后会进入半开状态。半开状态:熔断器会判断下一次请求的返回状况,如果成功,熔断器切回关闭状态。如果失败,熔断器切回打开状态。

(一)Sentinel Dashboard配置熔断

  这里可以直接在控制台进行设置,找到代码中配置的Sentinel资源名称,选择熔断进行配置。

  从下图可以看到,配置项非常的丰富,分别可以从慢调用比例、异常比例、异常数三个维度进行设置,同时可以设置熔断的时长等内容。

      

 

 

   这里主要说一下配置项。

  1、慢调用比例

    慢调用比例 :选择以慢调用比例作为熔断策略,需要设置允许的慢调用 RT(即最大响应时间ResponseTime),请求的响应时间大于该值则统计为慢调用。当单位统计时长(默认是1s)内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断。当触发熔断,则会在“熔断时长”内不再对请求进行处理,即熔断期间再来的请求,将直接进行降级响应。 

  2、异常比例

    当单位统计时长内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。异常比率的阈值范围是[0.0, 1.0] ,代表 0% - 100%。

    默认情况下,统计时间窗口大小为1 秒,期间接收到的请求至少 5 个。发生熔断后,熔断时长为指定的时长。熔断期间再来的请求,将直接地降级响应。 

  3、异常数

    当单位统计时长内的异常数目超过阈值之后会自动进行熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。

    默认情况下,统计时间窗口大小为1s。发生熔断后,熔断时长为指定的时长。熔断期间再来的请求,将直接地降级响应。

(二)代码配置熔断

  1、慢调用比例

    public static void main(String[] args) {
        SpringApplication.run(ConsumerSentinelApplication.class, args);
        initRule();//初始化熔断策略
    }

    public static void initRule() {
        List<DegradeRule> rules = new ArrayList<>();
        // 获取定义的规则
        DegradeRule rule = slowRequestDegradeRule();
        rules.add(rule);
        DegradeRuleManager.loadRules(rules);
    }

    //慢调用比例 熔断降级规则
    public static DegradeRule slowRequestDegradeRule(){
        //创建一个降级规则实例
        DegradeRule rule = new DegradeRule();
        //设置配置熔断规则的资源名称
        rule.setResource("getDepartById");
        //熔断策略:慢调用比例、异常比例、异常数
        rule.setGrade(RuleConstant.DEGRADE_GRADE_RT);
        //设置阈值:RT的时间,单位毫秒。若一个请求获取到响应的时间超出该值,则会将该请求统计 为“慢调用”
        rule.setCount(200);
        //熔断恢复时间窗口,单位秒
        rule.setTimeWindow(30);
        // 可触发熔断的最小请求数,默认是5个
        rule.setMinRequestAmount(1);
        // 设置发生慢调用的比例
        rule.setSlowRatioThreshold(0.5);
        return rule;
    }

 

 

  2、异常比例

    //异常比例 熔断降级规则
    private static DegradeRule errorRatioDegradeRule() {
        DegradeRule rule = new DegradeRule(); rule.setResource("getDepartById");
        // 指定熔断规则为 异常比例
        rule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO);
        // 设置阈值:发生熔断的异常请求比例
        rule.setCount(0.5);
        rule.setTimeWindow(60);
        rule.setMinRequestAmount(5);
        return rule;
    }

  3、异常数

    //异常数 熔断降级规则
    private static DegradeRule errorCountDegradeRule() {
        DegradeRule rule = new DegradeRule();
        rule.setResource("getDepartById");
        // 指定熔断规则为 异常数量
        rule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT);
        // 设置阈值:发生熔断的异常请求数量
        rule.setCount(5);
        rule.setTimeWindow(60);
        rule.setMinRequestAmount(5);
        return rule;
    }

 

 

(三)控制台有项目无监控解决办法

  看控制台有项目实例,但是没有监控信息,例如实时监控、簇点链路等都没有任何信息。

  首先要保证对应的依赖和参数配置正确。接入控制台需要上报的客户端依赖,如 sentinel-transport-simple-http

        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-transport-simple-http</artifactId>
            <version>1.8.1</version>
        </dependency>

  如果还是不行,可以看一下及其列表中的IP地址是否是客户端的地址,如果不是,需要手动配置客户端实例地址:在客户端代码中配置spring.cloud.sentinel.transport.client-ip,将其配置为当前客户端的ip地址。

spring:
  cloud:
    sentinel:
      filter:
        url-patterns: /**
      transport:
        dashboard: 172.20.10.2:8888
        port: 8719
        eager: true
        client-ip: 172.20.10.10

 

四、服务流控

  流量控制(flow control)也称为限流。Sentinel 实现流控的原理是监控应用流量的QPS 或并发线程数等指标,当达到指定的阈值时对再来的请求进行进行控制,以避免被瞬时的流量高峰冲垮,从而保障应用的高可用性。

  服务流控是为了在高并发场景下不至于将系统压垮。一条限流规则主要由下面几个因素组成,我们可以组合这些元素来实现不同的限流效果:

    resource :资源名,即限流规则的作用对象

    count : 限流阈值

    grade : 限流阈值类型(QPS 或并发线程数)

    limitApp : 流控针对的调用来源,若为 default 则不区分调用来源

    strategy : 调用关系限流策略

    controlBehavior : 流量控制效果(直接拒绝、Warm Up、匀速排队)

(一)流控配置

  1、Dashboard配置

    通过下图可以看到,在控制台可以通过QPS和并发线程数两个维度进行流控。

      

 

 

     我在这里设置了3,也就是每秒允许三个查询请求,我写了一个测试方法。测试结果如下图所示,可以看到,前三个请求成功了,后面的请求都被熔断降级了。

      

 

  2、代码配置(配置式)

    public static void main(String[] args) {
        SpringApplication.run(ConsumerSentinelApplication.class, args);
        initRule();
    }

    public static void initRule() {
        //初始化熔断策略
        initDegradeRule();
        //初始化流控策略
        initFlowRule();
    }

    public static void initFlowRule() { 
        List<FlowRule> flowRules = new ArrayList<>(); 
        FlowRule qpsRule = qpsFlowRule(); 
        flowRules.add(qpsRule); 
        FlowRuleManager.loadRules(flowRules);
    }
    
    private static FlowRule qpsFlowRule() { 
        //创建流控规则对象 
        FlowRule qpsRule = new FlowRule(); 
        //设置流控资源名称 
        qpsRule.setResource("getDepartById"); 
        //设置流控规则【QPS和线程数】 
        qpsRule.setGrade(RuleConstant.FLOW_GRADE_QPS); 
        //设置QPS数为1 
        qpsRule.setCount(2); 
        //值为default,表示对请求来源不做限定 
        qpsRule.setLimitApp("default"); 
        return qpsRule; 
    }

 

  3、代码配置(异常捕获式)--不建议使用

    @GetMapping("/flow/test/{id}")
    public Depart flowTest(@PathVariable("id") int id) {
        Entry entry = null;
        try {
            entry = SphU.entry("qpsFlowRule");
            return departService.getDepartById(id);
        }catch (BlockException e){
            return Depart.builder().id(id).name("exception flow=====").build();
        }finally {
            if(entry != null){
                entry.exit();
            }
        }
    }

 

(二)流控阈值类型

  在控制台中可以看到,阈值的类型有两种,分别为QPS和线程数。

    QPS,Queue Per Second,就是通常所说的每秒的访问量。该流控方案通过对每秒访问量的控制来达到保护。当指定资源的每秒访问量达到了设置的阈值时,可以立即拒绝再新进入的请求。 

    并发线程数,该方案通常是对消费者端的配置。是为了避免由于慢调用而将消费者端线程耗尽情况的发生,业内会使用隔离方案一般来说可以分为两种: 线程池隔离和信号量隔离

      A、 线程池隔离

      系统为不同的提供者资源设置不同的线程池来隔离业务自身之间的资源争抢。该方案隔离性较好,但需要创建的线程池及线程数量太多,系统消耗较大。当请求线程到达后,会从线程池中获取到一个新的执行线程去完成提供者的调用。由请求线程到执行线程的上下文切换时间开销较大。特别是对低延时的调用有比较大的影响。

      B、 信号量隔离

      系统为不同的提供者资源设置不同的计数器。每增加一个该资源的调用请求,计数器就变化一次。当达到该计数器阈值时,再来的请求将被限流。该方式的执行线程与请求线程是同一个线程,不存在线程上下文切换的问题。更不存在很多的线程池创建与线程创建问题。也正因为请求线程与执行线程没有分离,所以,其对于提供者的调用无法实现异步,执行效率降低,且对于依赖资源的执行超时控制不方便。

   线程隔离和信号量隔离原理对比图:

      

 

  

  Sentinel并发线程数控制也属于隔离方案,但不同于以上两种隔离方式,是对以上两种方案的综合与改进,或者说更像是线程池隔离。 其也将请求线程与执行线程进行了分离,但不负责创建和管理线程池,而仅仅是简单统计该资源的请求占用的线程数量超出了阈值,则可以立即拒绝再新进入的请求。

  代码配置的话,就是设置流控规则枚举改一下就OK。

(三)流控模式

  如下图所示,Sentinel的流控模式分为直接、关联、链路三种。

  1、直接:当对“资源名”指定资源的请求达到了设置阈值时,再新进入的请求将被直接执行指定的流控效果,快速失败。  

       

 

   2、关联:当对“关联资源”的访问达到了“单机阈值”指定的阈值时,会对当前的资源访问进行限流。当两个资源之间具有资源争抢或者依赖关系的时候,这两个资源便具有了关联。比如对数据库同一个字段的读操作和写操作存在争抢,读的速度过高会影响写得速度,写的速度过高会影响读的速度。如果放任读写操作争抢资源,则争抢本身带来的开销会降低整体的吞吐量。可使用关联限流来避免具有关联关系的资源之间过度的争抢。read_db 和 write_db 这两个资源分别代表数据库读写,我们可以给 read_db 设置限流规则来达到写优先的目的:设置 strategy 为 RuleConstant.STRATEGY_RELATE 同时设置 refResource 为 write_db 。这样当写库操作过于频繁时,读数据的请求会被限流。

    例如添加如下资源:

    //跨服务根据列表查询
    @GetMapping("/list")
    @SentinelResource(value = "listHandle", fallback = "listAllDepartsBack", fallbackClass = DepartSentinelFallBack.class)
    public List<Depart> listHandle() {
        return departService.listAllDeparts();
    }

 

   根据id查询和查询列表两个资源都是查询的同一张表,那么就可以设计为关联,防止其中一个流量太大最终导致其他的查询不能使用。   

       

 

  3、 链路:当对一个资源有多种访问路径时,可以对某一路径的访问进行限流,而其它访问路径不限流。

    引入依赖:

        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-web-servlet</artifactId>
            <version>1.7.0</version>
        </dependency>

    多个入口对同一个资源进行调用

@RestController
@RequestMapping("/sentinel/test")
public class SentinelFlowTestController {

    @Autowired
    private DepartServiceImpl departService;

    //跨服务根据列表查询
    @GetMapping("/list")

    public List<Depart> listHandle() {
        return departService.listAllDeparts();
    }

    //跨服务根据列表查询
    @GetMapping("/all")
    public List<Depart> allHandle(){
        return departService.listAllDeparts();
    }

}

  

@Service
public class DepartServiceImpl {

    @SentinelResource(value = "listAllDeparts", fallback = "listAllDepartsBack")
    public List<Depart> listAllDeparts(){
        List<Depart> departs = new ArrayList<>();
        departs.add(Depart.builder().id(1234).name("========= listAllDeparts ==========").build());

        return departs;
    }

    public List<Depart> listAllDepartsBack(){
        List<Depart> departs = new ArrayList<>();
        departs.add(Depart.builder().name("sentinel method fall back").build());
        return departs;
    }
}

 

 

  配置过滤器

@Configuration
public class FilterContextConfig {
    @Bean
    public FilterRegistrationBean sentinelFilterRegistration() {
        FilterRegistrationBean registration = new FilterRegistrationBean();
        //与Sentinel过滤器
        registration.setFilter(new CommonFilter());
        registration.addUrlPatterns("/*");
        registration.addInitParameter(CommonFilter.WEB_CONTEXT_UNIFY, "false");
        registration.setName("sentinelFilter");
        registration.setOrder(1);
        return registration;
    }
}

  在控制台进行配置

      

 

 

 

    这样就只用/sentinel/test/list这一个请求路径会被限流,而另外的/sentinel/test/all并不会被限流。

  4、代码配置流控模式

    /**
     * 带流控模式的流控规则
     * @return
     */
    private static FlowRule qpsChainFlowRule() {
        FlowRule qpsRule = new FlowRule();
        qpsRule.setResource("qpsChainFlowRule");
        qpsRule.setGrade(RuleConstant.FLOW_GRADE_QPS);
        qpsRule.setCount(1);
        qpsRule.setStrategy(RuleConstant.STRATEGY_CHAIN);
        qpsRule.setRefResource("/provider/depart/list");
        qpsRule.setLimitApp("default");
        return qpsRule;
    }

 

(四)流控效果

  流控效果分为快速失败、Warm Up、排队等待三种。

    快速失败:也称为直接拒绝,是默认的 QPS 流控超值处理方式。当 QPS 超过设置的阈值后,再来的请求将被直接拒绝,即抛出FlowException。我们前面的流控使用的就是这种处理方式。

    Warm Up:当系统中某service 的 QPS 长期处于低水位运行状态时,系统为该service 所分配的各种软硬件资源都会很少,例如,缓存空间、线程数量等资源。若QPS 陡然增加可能会将系统一下压垮。为了避免这种情况的发生,我们希望QPS 缓步增加到设定的阈值。这种应急情况的处理方式称为warmup,即预热,也称为冷启动。

    排队等待:排队等待,也称为匀速排队。该方式会严格控制请求通过的间隔时间,让请求以均匀的速度通过。其是漏桶算法的改进。不同的是,当流量超过设定阈值时,漏桶算法会直接将再来的请求丢弃,而排队等待算法则是将请求缓存起来,后面慢慢处理。不过,该算法目前暂不支持QPS 超过1000 的场景。

  代码配置流控效果

    /**
     * Warm Up
     * @return
     */
    private static FlowRule qpsWarmUpFlowRule() { 
        FlowRule qpsRule = new FlowRule(); 
        qpsRule.setResource("qpsWarmUpFlowRule"); 
        qpsRule.setGrade(RuleConstant.FLOW_GRADE_QPS); 
        qpsRule.setLimitApp("default"); 
        qpsRule.setCount(20); 
        qpsRule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_WARM_UP); 
        qpsRule.setWarmUpPeriodSec(5); 
        return qpsRule; 
    }

    /**
     * 排队等待
     * @return
     */
    private static FlowRule qpsQueueFlowRule() { 
        FlowRule qpsRule = new FlowRule(); 
        qpsRule.setResource("qpsQueueFlowRule"); 
        qpsRule.setGrade(RuleConstant.FLOW_GRADE_QPS); 
        qpsRule.setLimitApp("default"); 
        qpsRule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER); 
        qpsRule.setCount(20); 
        qpsRule.setMaxQueueingTimeMs(20*1000); 
        return qpsRule; 
    }

 

      

五、来源流控

  来源流控是针对请求发出者的来源名称所进行的流控。在流控规则中可以直接指定该规则用于限流的来源名称的请求,一条规则可以指定一个限流的来源。当然,若要指定多个来源,可以定义多个同名规则,也可以通过黑白名单规则一次性指定。无论是黑名单还是白名单,其实就是一个请求来源名称列表,当然,该名单是属于某一具体资源的。出现在黑名单中的来源请求将被降级,其它来源的请求则可以正常进行访问;出现在白名单中的来源请求是可以进行正常访问的,而其它来源请求则将被降级。

(一)服务名流控

  定义原始请求解析器,其用于从请求中获取来源标识

/**
 * 定义原始请求解析器,其用于从请求中获取来源标识
 */
@Component
public class DepartRequestOriginParser implements RequestOriginParser {
    /**
     * 该方法的返回值即为请求来源标识
     * @param request
     * @return
     */
    @Override
    public String parseOrigin(HttpServletRequest request) {
        // 本例的来源标识通过请求参数给出,
        // 这里获取的名称为source的请求参数就是来源名称
        String source = request.getParameter("source");
        // 指定默认来源名称为serviceA
        if (StringUtils.isEmpty(source)) {
            source = "serviceA";
        }
        // 返回的就是来源标识
        return source;
    }
}

 

  Dashboard中设置系统来源

      

 

  同时也可以对同一个资源设置多个来源流控规则。

      

 

   以上规则的意思是,对于 serviceA 来源的请求,其 QPS 的阈值为2;对于 serviceB 来源的请求,其QPS 的阈值为3;而其它来源的请求,其QPS 的阈值为10。但它们都是用于限制资源名称为reqsourceRule 的资源的。 

(二)黑白名单流控  

  流控规则中,一条规则仅可指定一个来源,若要指定多个来源,则需要定义多条同名规则,比较麻烦。可以通过黑白名单来一次性指定多个来源。

    

   也可以通过代码设置

    public static void main(String[] args) {
        SpringApplication.run(ConsumerSentinelApplication.class, args);
        initRule();
    }

    public static void initRule() {
        //初始化熔断策略
        initDegradeRule();
        //初始化流控策略
        initFlowRule();
        //初始化黑白名单
        initAuthorityRule();
    }

    public static void initAuthorityRule() {
        List<AuthorityRule> rules = new ArrayList<>();
        AuthorityRule rule = reqsourceRule();
        rules.add(rule);
        AuthorityRuleManager.loadRules(rules);
    }


    private static AuthorityRule reqsourceRule() {
        AuthorityRule rule = new AuthorityRule();
        rule.setResource("getDepartById");
        rule.setStrategy(RuleConstant.AUTHORITY_WHITE);
        rule.setLimitApp("serviceA,serviceB");
        return rule;
    }

  

六、热点参数限流

  热点参数限流指的是,在流控规则中指定对某参数的 QPS,当所有对该资源的请求 URL中携带有指定参数的请求QPS 达到了阈值,则发生限流。

  设置两个参数的方法

    //跨服务根据id查询
    @GetMapping("/hot")
    // 该注解表明当前方法是一个由Sentinel管理的资源,value属性用于指定该资源的名称
    @SentinelResource(value = "hotTest", fallback = "hotTestBack")
    public Depart hotTest(int id, String name) {
        return Depart.builder().id(id).name(name).build();
    }

    public Depart hotTestBack(int id, String name){
        return Depart.builder().id(id).name("fallback==========" + name).build();
    }

  在控制台进行配置

      

 

 

  参数例外项是指,对于热点参数中某个或某些特殊值单独设置规则。参数类型仅支持基本数据类型或其对应的包装类型,及 String 类型。

  代码设置

    public static void initRule() {
        //初始化熔断策略
        //initDegradeRule();
        //初始化流控策略
        //initFlowRule();
        //初始化黑白名单
        //initAuthorityRule();
        //热点参数
        initParamFlowRule();
    }

    //初始化规则
    public static void initParamFlowRule() {
        List<ParamFlowRule> rules = new ArrayList<>();
        ParamFlowRule rule = paramFlowRule();
        rules.add(rule);
        ParamFlowRuleManager.loadRules(rules);
    }

    //配置热点参数限流
    private static ParamFlowRule paramFlowRule() {
        ParamFlowRule rule = new ParamFlowRule();
        rule.setResource("hotTest");
        rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
        rule.setLimitApp("default");
        rule.setCount(2);
        rule.setParamIdx(1);
        rule.setDurationInSec(10);
        List<ParamFlowItem> items = new ArrayList<>();
        items.add(nameParamItem("human", 100));
        items.add(nameParamItem("administrative", 100));
        rule.setParamFlowItemList(items);
        return rule;
    }

 

 

七、系统自适应限流

  Sentinel 系统自适应限流对应用级别入口流量进行整体控制,结合应用的 Load、CPU 使用率、平均RT、入口 QPS 和入口并发线程数等几个维度的监控指标,通过自适应的流控策略,让系统的入口流量和系统的负载达到一个平衡,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。 由于该限流方式中阈值的设置需要很多系统软硬件相关的数据,而与代码关系不大,所以这种限流方式一般是由运维来设置的。

      

 

   从上图可以看到,分别可以从LOAD、RT、线程数、入口QPS、CPU使用率这五个维度进行设置。

  1、系统负载 Load

    该模式仅对Linux/Unix-like 系统生效。当系统 CPU 最近一分钟的负载量load1 超过了设置的阈值时会触发系统保护,即对再来的请求进行限流处理。这个阈值就是系统负载容量,系统容量可以由maxQps *minRt 估算得出。不过,也可以通过 CPU cores * 2.5 计算出其参考数值。

  2、CPU 使用率

    当系统 CPU 使用率超过阈值即触发系统保护(取值范围 0.0-1.0),比较灵敏。

  3、平均响应时间 RT

    当对当前应用上所有入口流量的平均RT 达到阈值时触发系统保护,单位是毫秒。

  4、并发线程数

    当对当前应用的所有入口流量进行处理的所有线程数量达到阈值时触发系统保护。

  5、入口 QPS

    当对当前应用的所有入口流量的总 QPS 达到阈值时触发系统保护。

八、网关流控

  Sentinel 对于主流网关限流的实现,是通过 Sentinel API Gateway Adapter Common 这个公共适配器模块实现的。这个适配模块提供了Route和API两种维度的限流。

    Route 维度:根据网关路由中指定的路由 id 进行路由

    API 维度:使用Sentinel 提供的API 自定义分组进行限流,是比Route 维度更加细粒度的限流 

(一)Route维度限流

  1、引入依赖

        <!--sentinel 与 spring cloud gateway 适配依赖-->
        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-spring-cloud-gateway-adapter</artifactId>
            <version>1.8.0</version>
        </dependency>

        <!--gateway 依赖-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
            <version>2.2.3.RELEASE</version>
        </dependency>

  2、配置route路由规则

spring:
  application:
    name: consumer-sentinel-gateway-depart
  cloud:
    gateway:
      # 配置开启定位器
      discovery:
        locator:
          enabled: true
      routes:
        - id: depart_route1
          uri: lb://consumer-sentinel-depart
          predicates:
            - Path=/sentinel/**
        - id: depart_route2
          uri: lb://provider02Nacosconfig
          predicates:
            - Path=/provider/**

 

  3、配置限流异常处理器和全局限流filter

@Configuration
public class GatewayConfiguration {
    private final List<ViewResolver> viewResolvers;
    private final ServerCodecConfigurer serverCodecConfigurer;
    public GatewayConfiguration(ObjectProvider<List<ViewResolver>> viewResolversProvider, ServerCodecConfigurer serverCodecConfigurer) {
        // 负责视图解析与生成
        this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
        // 负责gateway中http消息的读写操作配置
        this.serverCodecConfigurer = serverCodecConfigurer;
    }

    // 被限流后的异常处理器
    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
        // Register the block exception handler for Spring Cloud Gateway.
        return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
    }


    // 实现sentinel限流的全局Filter
    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public GlobalFilter sentinelGatewayFilter() {
        return new SentinelGatewayFilter();
    }
}

  4、配置限流规则及降级方法

    public static void main(String[] args) {
        SpringApplication.run(ConsumerSentinelGatewayApplication.class, args);
        initRule();
        initBlockHandlers();
    }

    private static void initRule() {
        Set<GatewayFlowRule> rules = new HashSet<>();
        GatewayFlowRule rule = gatewayFlowRule();
        rules.add(rule);
        GatewayRuleManager.loadRules(rules);
    }

    // 对名称为staff_route的路由规则进行限流
    private static GatewayFlowRule gatewayFlowRule() {
        // 定义一个Gateway限流规则实例
        GatewayFlowRule rule = new GatewayFlowRule();
        // 指定规则模式是route限流,其为默认值
        rule.setResourceMode(SentinelGatewayConstants.RESOURCE_MODE_ROUTE_ID);
        // 指定sentienl资源名称为 路由规则id
        rule.setResource("depart_route1");
        rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
        rule.setCount(2);
        return rule;
    }

    // 路由限流降级方法
    private static void initBlockHandlers() {
        GatewayCallbackManager.setBlockHandler((exchange, th) -> {
            // 从请求中获取uri
            URI uri = exchange.getRequest().getURI();
            // 将响应数据写入到map
            Map<String, Object> map = new HashMap<>();
            map.put("uri", uri); map.put("msg", "访问量过大,请稍候重试");
            return ServerResponse.status(HttpStatus.TOO_MANY_REQUESTS) .contentType(MediaType.APPLICATION_JSON).body(BodyInserters.fromValue(map));
        });
    }

 (二)API限流

  这里其实是对Route限流的一种补充,对于转发还是需要使用route进行处理,然后在通过 API配置更为精细的限流策略。

public static void main(String[] args) {
        SpringApplication.run(ConsumerSentinelGatewayApplication.class, args);
        //route路由
        //initRule();
        //api路由限流
        initAPIRule();
        //API路由
        initCustomizedApis();
        //路由限流降级方法
        initBlockHandlers();
    }

    private static void initRule() {
        Set<GatewayFlowRule> rules = new HashSet<>();
        GatewayFlowRule rule = gatewayFlowRule();
        rules.add(rule);
        GatewayRuleManager.loadRules(rules);
    }

    // 对名称为staff_route的路由规则进行限流
    private static GatewayFlowRule gatewayFlowRule() {
        // 定义一个Gateway限流规则实例
        GatewayFlowRule rule = new GatewayFlowRule();
        // 指定规则模式是route限流,其为默认值
        rule.setResourceMode(SentinelGatewayConstants.RESOURCE_MODE_ROUTE_ID);
        // 指定sentienl资源名称为 路由规则id
        rule.setResource("depart_route1");
        rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
        rule.setCount(2);
        return rule;
    }

    // 路由限流降级方法
    private static void initBlockHandlers() {
        GatewayCallbackManager.setBlockHandler((exchange, th) -> {
            // 从请求中获取uri
            URI uri = exchange.getRequest().getURI();
            // 将响应数据写入到map
            Map<String, Object> map = new HashMap<>();
            map.put("uri", uri); map.put("msg", "访问量过大,请稍候重试");
            return ServerResponse.status(HttpStatus.TOO_MANY_REQUESTS) .contentType(MediaType.APPLICATION_JSON).body(BodyInserters.fromValue(map));
        });
    }




    private static void initAPIRule() {
        Set<GatewayFlowRule> rules = new HashSet<>();
        // 在这里指定了对哪些路由api进行限流
        GatewayFlowRule providerRule = gatewayFlowRule("depart_provider", 1);
        GatewayFlowRule consumerRule = gatewayFlowRule("depart_consumer", 2);
        rules.add(providerRule);
        rules.add(consumerRule);
        GatewayRuleManager.loadRules(rules);
    }

    private static void initCustomizedApis() {
        // 定义一个名称为 depart_provider 的路由api
        ApiDefinition providerApi = new ApiDefinition("depart_provider").setPredicateItems(
                new HashSet<ApiPredicateItem>() {{
                    add(new ApiPathPredicateItem().setPattern("/sentinel/get/**")
                            // 指定该路由api对于请求的匹配策略为 前辍匹配
                    .setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX)); }});
        // 定义一个名称为 depart_consumer 的路由api
        ApiDefinition consumerApi = new ApiDefinition("depart_consumer").setPredicateItems(
                new HashSet<ApiPredicateItem>() {{
                    add(new ApiPathPredicateItem().setPattern("/provider/depart/get/2"));
                    add(new ApiPathPredicateItem().setPattern("/provider/depart/get/3")
                            // 指定该路由api对于请求的匹配策略为 精确匹配
                            .setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_EXACT)); }});
        Set<ApiDefinition> definitions = new HashSet<>();
        definitions.add(providerApi);
        definitions.add(consumerApi);
        GatewayApiDefinitionManager.loadApiDefinitions(definitions);
    }

    //定义网关限流规则
    private static GatewayFlowRule gatewayFlowRule(String apiName, int count) {
        GatewayFlowRule rule = new GatewayFlowRule();
        // 指定规则模式为路由api限流
        rule.setResourceMode(SentinelGatewayConstants.RESOURCE_MODE_CUSTOM_API_NAME );
        rule.setResource(apiName);
        //api名称
        rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
        rule.setCount(count);
        return rule;
    }

 

 

九、动态规则扩展

  loadRules() 方法只接受内存态的规则对象,但更多时候规则存储在文件、数据库或者配置中心当中。 DataSource 接口给我们提供了对接任意配置源的能力。相比直接通过 API 修改规则,实现DataSource 接口是更加可靠的做法。

  我们推荐通过控制台设置规则后将规则推送到统一的规则中心,客户端实现 ReadableDataSource 接口端监听规则中心实时获取变更,流程如下:

      

 

  拉模式:

    客户端主动向某个规则管理中心定期轮询拉取规则,这个规则中心可以是 RDBMS、文件,甚至是 VCS等。这样做的方式是简单,缺点是无法及时获取变更。

  推模式:

    规则中心统一推送,客户端通过注册监听器的方式时刻监听变化,比如使用 Nacos、Zookeeper 等配置中心。这种方式有更好的实时性和一致性保证。官方推荐使用推送模式。

  这里以nacos演示

  1、引入依赖

        <!--sentinel的nacos 数据源依赖-->
        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-datasource-nacos</artifactId>
            <version>1.8.0</version>
        </dependency>
        <!--nacos-config依赖-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
            <version>2.2.3.RELEASE</version>
        </dependency>

  2、添加配置

    说明一下,配置文件需要配置配置中心地址等信息,这信息需要配置在bootstrap.yml中,配置在application.yml中不生效。

spring:
  application:
    name: consumer-sentinel-depart
  cloud:
    sentinel:
      filter:
        url-patterns: /**
      transport:
        dashboard: 172.20.10.2:8888
        port: 8719
        eager: true
        client-ip: 172.20.10.10
      datasource:
        my-datasource:
          nacos:
            server-addr: 172.20.10.2:8840,172.20.10.2:8845,172.20.10.2:8849
            # 配置文件名称
            data-id: my-ds-fw
            # 规则类型
            rule-type: flow
            # 配置文件类型
            data-type: json
    nacos:
      discovery:
        server-addr: 172.20.10.2:8840,172.20.10.2:8845,172.20.10.2:8849
      config:
        server-addr: 172.20.10.2:8840,172.20.10.2:8845,172.20.10.2:8849
        file-extension: yml

 

   3、在nacos中配置规则

      

[
    {
        "resource":"getDepartById",
        "limitApp":"service1",
        "grade":1,
        "count":2
    }
]

 

posted @ 2021-10-23 11:44  李聪龙  阅读(332)  评论(0编辑  收藏  举报