Sentinel

学习目标

  • Sentinel的控制规则
    • 流控规则
    • 降级规则
    • 热点规则
  • 使用自定义错误信息替换默认的提示信息

1. 介绍

分布式系统的流量防卫兵,以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。
image.png
官网:https://github.com/alibaba/Sentinelhttps://sentinelguard.io/zh-cn/
中文文档:https://github.com/alibaba/Sentinel/wiki/介绍
image.png

2. 下载运行

地址:https://github.com/alibaba/Sentinel/releases
image.png
其实就是一个jar包,执行:**java -jar sentinel-dashboard-1.8.5.jar **
image.png
启动成功后,浏览器访问:http://192.168.56.102:8080/#/login
image.png
用户:sentinel,密码:sentinel
注意:Sentinel默认是8080端口,所以启动前确保8080没有被占用

3. 集成

1. 启动nacos

image.png

2. 项目配置

修改Consumer这个项目

1. pom.xml

使用Sentinel,需要以下几个依赖

<!-- sentinel -->
<dependency>
  <groupId>com.alibaba.cloud</groupId>
  <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!-- sentinel 持久化-->
<dependency>
  <groupId>com.alibaba.csp</groupId>
  <artifactId>sentinel-datasource-nacos</artifactId>
</dependency>

2. application.properties

增加以下配置:

#Sentinel的访问端口
spring.cloud.sentinel.transport.dashboard=localhost:8080

#Sentinel客户端通信端口,该端口负责跟Sentinel应用通信,默认是8719
spring.cloud.sentinel.transport.port=8719

3. Controller

新增 SentinelController

@RestController
public class SentinelController {

    @GetMapping("/yase")
    public String yase(){
        return "。。。。。。yase";
    }

    @GetMapping("/daji")
    public String daji(){
        return "。。。。。。daji";
    }
}

3. 结果

启动Sentinel后,再启动项目,查看结果
image.png
发现还是空的,这是因为Sentinel采用的是懒加载模式,需要先访问项目,才能显示,如:
image.png
访问时,多刷新几次
image.png

4. 配置

image.png
在这里可以配置一些 流控、降级、热点等规则

1. 流控规则

1. 配置解释

资源名 唯一名称,默认是请求路径
针对来源 Sentinel可以针对调用者进行限流,填写服务名,默认default(不区分来源)
阈值类型/单机阈值
  • QPS(每秒钟的请求数量):当该接口的QPS达到阈值时,进行限流
  • 线程数:当调用该接口的线程数达到阈值时,进行限流
    |
    | 是否集群 | 略 |
    | 流控模式 |
  • 直接:接口达到限流条件时,直接限流
  • 关联:当关联的资源达到阈值时,限流自己
  • 链路:待定
    |
    | 流控效果 |
  • 快速失败:直接失败,抛异常
  • Warm Up:预热,配置的阈值不会立即生效,需要经过一段时间的预热,具体看下方**Warm Up **示例
  • 排队等待:匀速排队,让请求以匀速通过,选择这种时,阈值类型必为QPS
    |

2. QPS

image.png
这样配置表示:访问/yase接口,每秒限制访问1次,如果超出阈值,直接返回失败信息
保存后可在流控规则中查看:
image.png
然后再浏览器中访问:http://localhost:8200/yase
image.png

3. 线程数

修改 /yase 接口

@GetMapping("/yase")
public String yase() throws InterruptedException {
    System.out.println(Thread.currentThread().getName()+"。。。。。。"+ LocalDateTime.now());
    Thread.sleep(5000);//休眠5秒钟,目的是验证同时访问这个方法的线程数量
    return "。。。。。。yase";
}

重启项目,配置 /yase 接口的流控规则为线程数
image.png
线程数:类似Semaphore(信号量),限制同时访问该资源的线程数量,表示同时只能有一个线程访问这个接口
浏览器上同时开两个页签访问:http://localhost:8200/yase
image.png
image.png
上图中,5秒内,只有一个线程成功访问 /yase 资源

4. 关联模式

根据关联资源的QPS,限流自己

修改代码,还原 /yase 接口

@GetMapping("/yase")
public String yase() throws InterruptedException {
    return "。。。。。。yase";
}

修改 /yase 接口的规则
image.png
设置 /daji 接口的流控规则
image.png
解释:**/yase 接口的实际QPS + /daji 接口的当前请求数(其实就是1) > /daji 接口配置的阈值,限流 **

测试:
这时候浏览器上高频率的 /daji 接口,不会限流,因为 /yase 接口的实际QPS 是 0
image.png
原理:
在 DefaultController 的 canPass 方法打断点,
注意:下方断点是51行
image.png

但是,postman不断访问** /yase**
image.png

image.png
浏览器访问/daji,结果:
image.png
原理:
在 DefaultController 的 canPass 方法打断点,
注意:下方断点是52行
image.png
实际场景:支付接口正在经历着巨大的访问压力,这个时候对下单的接口进行限流就很有必要了

5. 链路模式

统计从指定资源到本资源的请求数量,如果超过阈值,报错

新增 WangzheService

@Service
public class WangzheService {

    //Sentinel默认只标记Controller中的方法为资源,如果要标记其它方法,需要利用@SentinelResource注解
    @SentinelResource("xishi")
    public void xishi(){
        System.out.println("追求西施");
    }
}

修改 /yase 和 /daji 接口

@Autowired
private WangzheService wangzheService;

@GetMapping("/yase")
public String yase() throws InterruptedException {
    wangzheService.xishi();
    return "。。。。。。yase";
}

@GetMapping("/daji")
public String daji(){
    wangzheService.xishi();
    return "。。。。。。daji";
}

修改 application.properties,增加

# 默认为true,表示将调用链路收敛,会导致链路流控效果无效,需要改成false
spring.cloud.sentinel.web-context-unify=false

重新启动项目,并且访问 :http://localhost:8200/yase,可以看到:
image.png
对 xishi 这个资源设置流控规则
image.png
上图:如果从 /yase 到 xishi 的请求频率,超过1秒1次,则报错

浏览器高频率访问 /yase 接口,会看到下面错误
image.png

但是高频率访问 /daji 接口,没有问题
image.png

6. Warm Up

当系统长期处于低QPS的情况下,流量突然增加,瞬间时流量过大,有可能把系统压垮。这种方式,让通过的流量缓慢增加,在指定时间内逐渐增加到阈值上限。

  1. 恢复代码

image.png

  1. 修改 /yase 接口的配置:

image.png
这样,阈值是10,预热时间是5秒,在5秒后,让阈值上升到10,也就是说,前5秒访问时,阈值不是10,前5秒阈值=10/coldFactor, coldFactor 是冷加载因子,默认为3,所以初始阈值为 10 / 3 = 3
这样配置,在浏览器中刷新访问/testa,前5秒会有限流信息,5秒后就看不到了

7. 排队等待

控制请求通过的间隔时间,也即是让请求匀速通过。
场景:在某一秒有大量的请求到来,而接下来的一段时间则处于空闲状态,我们希望系统能够在接下来的空闲期间逐渐处理这些请求,而不是直接拒绝多余的请求。

修改 /yase 接口

@GetMapping("/yase")
public String yase() throws InterruptedException {
    System.out.println(Thread.currentThread().getName()+"。。。。。。"+ LocalDateTime.now());
    return "。。。。。。yase";
}

重启项目后,编辑 流控规则
image.png
上图:让请求排队,每秒处理一个请求

这样子,即使不断访问,系统也是默认每秒处理一个请求
image.png

8. 规则持久化

重新启动服务,发现之前配置的规则都没有了,这很不合适
下面把配置好的规则持久化到nacos中

1. pom.xml

<!-- sentinel-nacos -->
<dependency>
  <groupId>com.alibaba.csp</groupId>
  <artifactId>sentinel-datasource-nacos</artifactId>
</dependency>

2. nacos

image.png

[
  {
    "resource":"/yase",
    "limitApp":"default",
    "grade":1,
    "count":1,
    "strategy":0,
    "controlBehavior":0,
    "clusterMode":false
  }
]

配置解释:

  • resource:资源名
  • limitApp:来源应用
  • grade:阈值类型:0表示线程数,1表示QPS
  • count:阈值
  • strategy:流控模式:0表示直接,1表示关联,2表示链路
  • controlBehavior:流控效果:0表示快速失败,1表示Warm Up,2表示排队等待
  • clusterMode:是否集群

3. properties

修改 application.properties,增加:

# nacos 地址
spring.cloud.sentinel.datasource.ds1.nacos.server-addr=localhost:8848
#这个data-id需要跟nacos的对应
spring.cloud.sentinel.datasource.ds1.nacos.data-id=mz-sentinel
spring.cloud.sentinel.datasource.ds1.nacos.group-id=DEFAULT_GROUP
spring.cloud.sentinel.datasource.ds1.nacos.data-type=json
# flow:流控规则,其他规则查看:	com.alibaba.cloud.sentinel.datasource.RuleType
spring.cloud.sentinel.datasource.ds1.nacos.rule-type=flow

4. 结果

重启服务后,浏览器访问:http://localhost:8200/yase,可以发现Sentinel中已经有了流控规则
image.png
这时修改nacos中的配置,可以在sentinel中实时看到
image.png
image.png
但是在sentinel中编辑规则,并不会同步到nacos中,官方文档也已经说明:
https://github.com/alibaba/Sentinel/wiki/%E5%9C%A8%E7%94%9F%E4%BA%A7%E7%8E%AF%E5%A2%83%E4%B8%AD%E4%BD%BF%E7%94%A8-Sentinel
image.png

2. 降级规则

先删除之前的流控规则

1. 慢调用比例

规则解释:
image.png
最大RT:请求最大响应时间,请求的响应时间大于该值则认为是为慢调用
慢调用比例:根据上图,连续访问3次,如果有2次都是慢调用,慢调用比例 = 2 / 3 = 0.66
配置解释:在3秒内,如果请求个数>=2 && 慢调用的比例>0.5,则接下来5秒内熔断
熔断时不接受新的请求
熔断结束后,若接下来的一个请求响应时间<200ms, 结束熔断,否则再次熔断。

修改 /yase 接口

@GetMapping("/yase")
public String yase() throws InterruptedException {
    System.out.println(Thread.currentThread().getName()+"。。。。。。"+ LocalDateTime.now());
    Thread.sleep(300);// 300毫秒后再返回
    return "。。。。。yase";
}

重启项目后,设置 /yase 资源的降级规则
image.png
浏览器访问:http://localhost:8200/yase,前3秒内都正常相应
由于每次相应时间都超过200ms,3秒后熔断,5秒内不接受新的请求
image.png
5秒后再次访问,如果响应时间还是>200ms,再次熔断5秒

2. 异常比例

image.png
配置解释:在3秒内,如果请求个数>=2 && 异常的比例>0.5,则接下来5秒内熔断
熔断结束后,若接下来的一个请求响应成功, 结束熔断,否则再次熔断。
修改 /yase 接口

public static int count = 0;
@GetMapping("/yase")
public String yase() throws InterruptedException {
    System.out.println(Thread.currentThread().getName()+"。。。。。。"+ LocalDateTime.now());
    if(count<3){//前3次访问都让他报错,之后的访问正常响应
        count++;
        int i = 0;
        int n = 2 / i;
    }
    return "。。。。。。yase";
}

// 增加一个接口,修改count的值,方便测试
@GetMapping("/count")
public String count() {
    count =0;
    return count+"";
}

重启项目后,设置 /yase 资源的降级规则
image.png
浏览器访问:http://localhost:8200/yase,每次都报错,3秒后熔断,5秒内不接受新的请求
image.png
之后再次访问,因为count值>=3,不报错,所以不再熔断
image.png

3. 异常数

image.png
配置解释:在3秒内,如果请求个数>=2 && 异常的数量>2,则接下来5秒内熔断
熔断结束后,若接下来的一个请求响应成功, 结束熔断,否则再次熔断。

演示:省略

3. 热点规则

1. 基础

新建一个带参数的方法

@GetMapping("/diaochan")
@SentinelResource("diaochan")//向Sentinel注册一个资源,资源名:diaochan,配置热点规则需要使用这个注解
public String diaochan(int id){
    return "。。。。。。diaochan"+"----"+id;
}

新增 热点规则:
image.png
表示:针对 **diaochan **资源的 id 参数进行限流
规则:每秒的访问数量不能大于1

这样在浏览器中连续对 diaochan 资源进行访问,结果:
image.png
查看控制台,发现:
image.png
ParamFlowException:Sentinel中的参数限流异常,表示对指定参数限流成功

2. 友好提示

但是浏览器展示错误信息非常不合适,对代码进行修改

@GetMapping("/diaochan")
@SentinelResource(value = "diaochan", blockHandler = "err")//声明一个方法,处理异常
public String diaochan(int id){
    return "。。。。。。diaochan"+"----"+id;
}
public String err(int id, BlockException e){
    return "。。。。。。资源错误。。。。。。" + "-----" + id;
}

重启后,重新配置规则,再次高频率访问:
image.png
注意:blockHandler只能处理Sentinel抛出的异常,不能处理我们自己的业务代码异常

@GetMapping("/diaochan")
@SentinelResource(value = "diaochan", blockHandler = "err")//声明一个方法,处理异常
public String diaochan(int id){
    int i = 1/0;// 这样的异常,blockHandler不能处理
    return "。。。。。。diaochan"+"----"+id;
}
private String err(int id, BlockException e){
    return "。。。。。。资源错误。。。。。。" + "-----" + id;
}

image.png
针对这种情况,之后再说

3. 参数例外项

可以针对具体的参数值,设置阈值
image.png
配置解释:id=1的数据,QPS是100,其他的QPS还是1

连续访问id=2的数据
image.png
连续访问id=1的数据
image.png

4. 系统规则

对整个服务进行限流,在请求达到我们的服务之前就拦截住
image.png
配置规则:
image.png
配置解释:如果整个服务每秒接收的请求数量>1,就限流

阈值类型:

  • Load(仅对 Linux/Unix-like 机器生效):当系统 load1 超过阈值,且系统当前的并发线程数超过系统容量时才会触发系统保护。系统容量由系统的 maxQps * minRt 计算得出。设定参考值一般是 CPU cores * 2.5。
  • CPU usage(1.5.0+ 版本):当系统 CPU 使用率超过阈值即触发系统保护(取值范围 0.0-1.0)。
  • RT:当单台机器上所有入口流量的平均 RT 达到阈值即触发系统保护,单位是毫秒。
  • 线程数:当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护。
  • 入口 QPS:当单台机器上所有入口流量的 QPS 达到阈值即触发系统保护。

5. SentinelResource

image.png

1. 两种限流方式

  • url限流:限流后返回Sentinel的默认提示
    • image.png
  • 资源名限流:限流后可以指定返回结果
    • image.png

问题:

  • url限流:可以自定义提示
    • 需要实现 BlockExceptionHandler 接口
  • 资源名限流:可以自定义提示,需要SentinelResource注解

2. SentinelResource

缺点:代码入侵很高,而且每个方法都这样干,代码太膨胀

1. 统一blockHandler

新建类:

public class CustomHandle {

    public static String test_err(int i, BlockException e){
        return "限流啦。。。。。。";
    }
    //可以定义多个方法
}

修改 /diaochan 方法

@GetMapping("/diaochan")
@SentinelResource(value = "diaochan",
                  blockHandlerClass = CustomHandle.class,//指定类
                  blockHandler = "test_err")//指定方法,方法的参数数量要对应
public String diaochan(int id){
    return "。。。。。。diaochan"+"----"+id;
}

演示:省略

2. fallback

其实就是服务降级,这个可以处理我们程序的业务异常
新建 CustomFallBack 类:

public class CustomFallBack {

    public static String teste_fallBack(int i, Throwable e){
        return "出错啦!!!问题是:"+e.getMessage();
    }

}

修改接口

@GetMapping("/diaochan")
@SentinelResource(value = "diaochan",
                  blockHandlerClass = CustomHandle.class, blockHandler = "test_err",
                  fallbackClass = CustomFallBack.class, fallback = "teste_fallBack")
public String diaochan(int id){
    if(id == 2){
        int i = 1/0;
    }
    return "。。。。。。diaochan"+"----"+id;
}

结果:
image.png

3. exceptionsToIgnore属性

指定的异常不走fallback

@GetMapping("/zhenji")
@SentinelResource(value = "zhenji",fallbackClass = CustomFallBack.class,fallback = "teste_fallBack",
                  exceptionsToIgnore = {ArithmeticException.class})
public String zhenji(){
    int i = 2/0;
    return "。。。。。。zhenji";
}

image.png

3. BlockExceptionHandler

新建立一个类,实现 BlockExceptionHandler 接口

@Component
public class MyBlockExceptionHandler implements BlockExceptionHandler {
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception {
        response.setHeader("Content-Type","application/json;charset=UTF-8");
        String message = "{\"code\":444,\"msg\":\"访问人数过多\"}";
        response.getWriter().write(message);
    }
}

对 /daji 接口设置流控规则
image.png
结果:
image.png

posted @ 2023-02-23 16:31  空空大首领  阅读(328)  评论(0)    收藏  举报