Sentinel 限流熔断

1. 前言

1.1 什么是雪崩问题?

微服务之间相互调用,因为调用链中的一个服务故障,引起整个链路都无法访问的情况

1.2 处理雪崩问题的常见方式

  1. 超时处理:设置超时时间,请求超过一定时间没有响应就返回错误信息,不会一直等待

  2. 线程隔离:限定每个业务能使用的线程数,避免耗尽整个tomcat的资源

  3. 熔断降级:有断路器统计业务执行的异常比例,如果超出阈值则会熔断该业务,拦截访问业务的一切请求

  4. 流量控制:限制业务访问的QPS,避免服务因流量突增导致故障

前三种是出现故障都的应对措施,可以避免因服务故障引起的雪崩问题

后一种是预防方案,可以避免因瞬时高并发流量导致服务故障

1.3 与Hystrix对比

Sentinel Hystrix
隔离策略 信号量隔离 线程池隔离/信号量隔离
熔断降级策略 基于慢调用比例或异常比例 基于失败比例
实时指标实现 滑动窗口 滑动窗口(基于Rxjava)
规则配置 支持多种数据源 支持多种数据源
基于注解的支持 支持 支持
限流 基于QPS,支持基于调用关系的限流 有限的支持
流量整形 支持慢启动,匀速排队模式 不支持
系统自适应保护 支持 不支持
控制台 开箱即用,可配置规则,查看秒级监控、机器发现等 不完善
常见框架的适配 Servlet、Spring Cloud、Dubbo、gRPC等 Servlet、Spring Cloud Netflix

簇点链路: 就是项目内的调用链路,链路中被监控的每一个接口就是一个资源,默认情况下Sentinel会监控SpringMVC的每一个端点(Endpoint),
因此SpringMVC的每一个端点(Endpoint)就是调用链路中的一个资源

流控、熔断等都是针对簇点链路中的资源来设置的;

2. Sentinel概述

Sentinel(分布式系统的流量防卫兵)是案例开源的一套用于服务容错的综合性解决方案。它以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度来保护服务的稳定性。

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

Sentinel核心分为两个部分:

  • **核心库(java 客户端)**:能够运行于所有Java运行时环境,同时对Dubbo/Spring Cloud等框架也有较好的支持

  • **控制台(Dashboard)**:基于Spring boot开发,打包后可以直接运行

Sentinel的使用分为两个部分:

  • entinel-dashboard:与hytrix-dashboard类似,但是它更为强大一些,除了与hystrix-dashboard一样提供实时监控之外,还提供来流控规则、熔断规则的在线维护等功能
  • 客户端整合:每个微服务客户端都需要整合sentinel的客户端封装与配置,才能将监控信息上报给dashboard展示以及实时的更改限流或熔断规则等

3. 安装Sentinel服务(dashboard)

Sentinel提供一个轻量级的控制台,它提供机器发现、单机资源实时监控以及规则管理等功能,其控制台安装步骤如下:

  1. 打开Sentinel下载网址

    request
    1
    https://github.com/alibaba/Sentinel/releases

    百度网盘地址:

    1
    2
    3
    4
    5
    v2.0.0
    链接: https://pan.baidu.com/s/1Lj5mtZ8tLBA5Kr7GS38lkQ 提取码: irx8

    v1.8.6
    链接: https://pan.baidu.com/s/1_K8AvLvYS2kH7VZZrlF-1A 提取码: vqmv
  2. 下载jar包(存储到一个sentinel目录)

  1. 在sentinel对应目录,执行启动命令运行sentinel

    1
    java -Dserver.port=8180 -Dcsp.sentinel.dashboard.server=localhost:8180 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.8.1.jar

    启动效果如下图:

  2. 访问Sentinel服务

    通过浏览器访问,登录Sentinel,默认用户和密码都是sentinel


sentinel-dashboard不像Nacos的服务端那样提供了外置的配置文件,比较容易修改参数。不过不要紧,由于sentinel-dashbord是一个标准的Spring boot应用,所以如果要自定义端口号等内容的话,可以在启动命令增加参数来调整,比如-Dserver.port=8888

默认情况下,sentinel-dashboard以8080端口启动.

对于用户登录的相关配置可以在启动命令中增加下面的参数进行配置:

  • -Dsentinel.dashboard.auth.username=sentinel:用于指定控制台的登录用户名为sentinel

  • -Dsentinel.dashboard.auth.password=123456: 用于指定控制台的登录密码为123456

  • 如果省略上面两个参数,默认用户名和密码均为sentinel

  • -Dserver.servlet.session.timeout=7200:用于指定spring boot 服务端session的过期时间,如7200表示7200秒;60m表示60分钟,默认30分钟;

4. Sentinel案例

  1. Sentinel应用于服务提供方,在服务提供方添加依赖

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>2.3.12.RELEASE</version>
    </dependency>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-log4j2</artifactId>
    <version>2.3.12.RELEASE</version>
    <scope>pom</scope>
    </dependency>
    <dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
    <version>2.2.5.RELEASE</version>
    </dependency>
  2. 添加配置

    1
    2
    3
    4
    5
    spring:
    cloud:
    sentinel:
    transport:
    dashboard: localhost:8180 #指定sentinel控制台地址
  3. 创建演示controller对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @RestController
    @RequestMapping("/provider")
    public class ProviderSentinelController {

    @GetMapping("/sentinel")
    public String doSentinel() {
    return "sentinel test ... ";
    }
    }
  4. 启动服务,浏览器访问

  5. 设置限流链路

    设置指定接口的流控(流量控制),QPS(每秒请求次数)单机阈值为1; 代表每秒请求不超过1次,不然就做限流处理,处理方式为直接调用失败

  1. 设置限流策略

  1. 返回刷新消费端服务,检测是否有限流信息输出,如图所示

5. Sentinel流控规则分析

5.1 阈值类型

  • QPS(Queries Per Second):当调用相关URL对应的资源时,QPS达到单机阈值时,就会限流

  • 线程数:当调用相关URL对应的资源时,线程数达到单机阈值时,就会限流

5.2 限流模式

设置: Sentinel的流控模式代表的方式,默认【直接】,还有关联、链路

5.2.1 直接模式

Sentinel默认的流控处理就是【直接->快速失败】

5.2.2 关联模式

当关联的资源达到指定阈值,就限流自己;例如设置来关联资源为url2时,加入关联资源url2的QPS达到阈值,就限流url1接口;

举例:订单服务中会有2个重要接口,读取订单信息和写入订单信息;在高并发业务场景中,两个接口都会占用资源,如果读取接口访问过大,就会影响写入接口的性能;一般优先考虑写入订单接口!!

这就可以利用关联模式,关联资源上设置写入接口,资源设置读取接口即可,这样一旦写入接口过多就限制读取接口。

  1. 在Demo中ProviderSentinelController增加方法如下:

    1
    2
    3
    4
    @GetMapping("/sentinel1")
    public String doSentinel1() {
    return "sentinel1 test ... ";
    }
  2. 在Sentinel中做限流设置

  3. 打开两个测试窗口,对provider/sentinel1访问,检查provider/sentinel的状态

5.2.3 链路模式

链路模式只记录指定链路入口的流量,也就是当多个服务对指定资源调用时,假如流量超出了指定阈值,则进行限流

被调用的方法用@SentinelResouce进行注解,然后分别用不同业务方法对此业务进行调用,假如A业务设置了链路模式额限流,在B业务中不受影响;

  1. 在指定Service创建一个ResourceService

    1
    2
    3
    4
    5
    6
    7
    8
    @Service
    public class ResourceService {

    @SentinelResource
    public String getResource() {
    return "do get resource";
    }
    }
  2. ProviderSentinelController中添加方法

    1
    2
    3
    4
    5
    6
    7
    8
    @Autowired
    private ResourceService resource;

    @GetMapping("/sentinel2")
    public String doSentinel2() {
    resource.getResource();
    return "sentinel2 test ... ";
    }
  3. sentinel-dashboard中配置限流规则

  1. 设置链路流控规则后,再频繁对限流链路进行访问,检测是否会出现500异常

以上说明,流控模式为链路模式时,假如是sentinel 1.7.2以后的版本,Sentinel Web过滤器默认会聚合所有URL的入口为sentinel_spring_web_content,因此单独对指定链路限流会不生效,需要在application.yaml中添加语句关闭URL PATH聚合;

1
2
sentinel:
web-context-unify: false #不实用聚合

当设置类这个配置后,启动服务,就可以对指定的特定链路进行限流了。

5.2.4 限流后自定义异常处理

我们也可以基于@SentinelResource注解描述的方法进行限流后的异常进行自定义处理。

  1. 定义异常处理类
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    @Slf4j
    @Component
    public class ResourceBlockHandler {
    /**
    * 限流后的异常处理方法,应用于@SentinelResource注解中,
    * 此方法在编写时有如下几个要求:
    * 1)方法修饰符为public
    * 2)必须为static方法
    * 3)返回值类型与@SentinelResource注解描述的方法相同
    * 4)参数类型为BlockException
    * 5)方法名自己定义
    * @return
    */
    public static String doHandler(BlockException e){
    log.error("block exception {}", e.getMessage());
    return "访问过于频繁,请稍候再访问";
    }
    }
  2. 修改@SentinelResource注解中的属性定义
    1
    2
    3
    4
    5
    6
    @SentinelResource(value = "getResource",
    blockHandlerClass = ResourceBlockHandler.class,
    blockHandler = "doHandler")
    public String getResource() {
    return "do get resource";
    }
  3. 调用
    1
    2
    3
    4
    @GetMapping("/sentinel2")
    public String doSentinel2() {
    return resource.getResource();
    }

6. Sentinel 熔断降级

处理流量控制之外,对调用链路中不稳定的资源进行熔断降级也是保障高可用的重要措施之一,由于调用关系的复杂性,如果调用链路中某个资源不稳定,最终会导致请求堆积。

Sentinel熔断降级会在调用链路中某个资源出现不稳定状态时(调用超时或者异常比例升高),对这个资源进行限制,让请求快速失败,避免影响其他资源而导致级联错误。
当资源被降级后,在接下来的降级时间窗口内,对该资源的调用都会自动熔断(默认行为时抛出DegradeException)。

演示代码如下:

  1. 添加doSentinel4方法,基于此方法演示慢调用过程下的限流

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    private AtomicLong atomicLong = new AtomicLong(1);

    @GetMapping("/sentinel4")
    public String doSentinel4() throws InterruptedException {
    //获取自增对象的值 + 1
    long num = atomicLong.getAndIncrement();
    if (num % 2 == 0){
    //模拟50% 的慢调用比例
    Thread.sleep(200);
    }
    return "sentinel4 test";
    }
  2. 设置降级的链路

    熔断策略选择:慢调用比例,表示请求次数超过3时,假如平均响应时间超过200毫秒(Response Time 简称RT)的有30%,则对请求进行熔断,熔断时长为20秒,10秒后恢复正常。

  3. 对指定链路进行刷新,检测是否会出现限流,默认异常为DegradeException,也可以在DefaultBlockExceptionHandler中的handle方法内部加断点,分析异常类型

7. Sentinel 异常处理

系统提供类默认的异常处理机制,假如默认的异常处理机制不满足我们的需求,我们可以进行自定义,直接或间接的实现BlockExceptionHandler接口,并将对象交给spring管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
* 自定义限流,降级等异常处理
*
* @author xiaoyuge
*/
@Slf4j
@Component
public class ServiceBlockExceptionHandler implements BlockExceptionHandler {
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse response, BlockException e) throws Exception {
//设置响应数据编码
response.setCharacterEncoding("utf-8");
//告诉客户端响应数据的类型,以及客户端显示内容的编码
response.setContentType("text/html;charset=utf-8");
//向客户端响应一个json格式的字符串
//String str="{\"status\":429,\"message\":\"访问太频繁了\"}";
Map<String, Object> map = new HashMap<>();
map.put("status", 429);
map.put("message", "访问太频繁了");
String jsonStr = new ObjectMapper().writeValueAsString(map);
PrintWriter out = response.getWriter();
out.print(jsonStr);
out.flush();
out.close();
}
}

8. Sentinel 热点规则分析

  • 何为热点?热点即经常访问的数据,比如:

    • 商品ID为参数,统计一段时间内最常购买的商品ID并进行限制
    • 用户ID为参数,针对一段时间内频繁访问的用户进行限制
  • 热点参数限流会统计传入参数中的热点数据,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。热点参数限流可以看作是一种特殊的流量控制,仅对包含热点参数的资源调用生效,其中,sentinel会利用LRU策略统计最近最常访问的热点数据,
    结合令牌桶算法来进行参数级别的流控

  • 注意的时,热点参数限流对默认的SpringMVC资源无效,只有使用SentinelResource注解的资源有用

8.1 demo 演示

  1. 在controller添加方法
    1
    2
    3
    4
    5
    @GetMapping("/sentinel/findById")
    @SentinelResource("resource")
    public String doFindById(@RequestParam("id") Integer id){
    return "resource id is "+id;
    }
  2. 启动服务,选择要限流的热点链路热点规则的限流模式只有QPS模式。参数索引为@SentinelResouce 注解的方法参数下标,0 表示第一个参数,1表示第二个参数。单机阈值以及统计窗口时长表示在此窗口时间超过阈值就限流
  1. 多次访问热点参数方法,查看浏览器显示信息

    然后在后台出现以下异常表示限流成功

    1
    com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowException: 2

    热点参数其实就是特殊的流控,流控设置是针对整个请求的,但是热点参数可以设置到具体的参数,甚至参数针对的值,这样更灵活的进行流控管理。

    一般应用在某些特殊的资源处理上,如某些商品流量大,其他流量正常,可以利用热点参数方案限流,配置参数例外项,如图所示: