案例源码gitee地址:https://gitee.com/BanSheng/spring-cloud-alibaba-examples/tree/master/sentinel-example-client
Spring Cloud Alibaba Sentinel
一、Sentinel 简介
随着微服务的流行,服务和服务之间的稳定性变得越来越重要。 Sentinel 以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。
Sentinel 具有以下特征:
- 丰富的应用场景:Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、实时熔断下游不可用应用等。
- 完备的实时监控:Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据,甚至 500 台以下规模的集群的汇总运行情况。
- 广泛的开源生态: Sentinel 提供开箱即用的与其它开源框架/库的整合模块,例如与Spring Cloud、Dubbo、gRPC 的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入 Sentinel。
- 完善的 SPI 扩展点: Sentinel 提供简单易用、完善的 SPI 扩展点。您可以通过实现扩展点,快速的定制逻辑。例如定制规则管理、适配数据源等。
二、Sentinel 控制台安装
Sentinel 提供一个轻量级的开源控制台,它提供机器发现以及健康情况管理、监控(单机和集群),规则管理和推送的功能。本节将详细记录何如通过 Sentinel 控制台控制 Sentinel 客户端的各种行为。Sentinel 控制台的功能主要包括:流量控制、降级控制、热点配置、系统规则和授权规则等。
2.1 下载 Sentinel
访问:https://github.com/alibaba/Sentinel/releases
找到:1.7.1 版本:
点击 sentinel-dashboard-1.7.1.jar 完成下载:
提示:
直接在 github 下载,速度非常的慢,百度网盘下载:
链接:https://pan.baidu.com/s/1qyIkkw0nKt1znR0e4dDWxQ 提取码:2f9q
2.2 启动 sentinel-dashboard
将下载好的 sentinel-dashboard-1.7.1.jar 复制到安装软件的目录里面。
使用:
java -jar sentinel-dashboard-1.7.1.jar
来启动一个 sentinel-dashboard 的实例。
启动成功后:
我们可以通过浏览器访问:
http://localhost:8080/
其中,用户名:sentinel
密码: sentinel
更多可用的启动参数配置:
java -D 参数名=参数值 -jar xx.jar |
java -jar xx.jar --参数名=参数值 |
- -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 分钟;
- -Dcsp.sentinel.dashboard.server=consoleIp:port 指定控制台地址和端口。
三、搭建客户端
刚才我们搭建了 sentinel-dashboard,我们还需要搭建一个客户端,用于测试 sentinel 的各种功能。
我们将搭建如图所示的 Maven 项目结构:
3.1 使用 IDEA 创建子模块
选择 Maven 项目:
点击 Next:
Parent:选择 spring-cloud-alibaba-examples
Name:命名为 sentinel-example-client
其他的项保持默认值即可。
点击 Finish完成创建。
3.2 添加依赖
修改 sentinel-example-client 里面的 pom.xml 文件:
添加以下的内容:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
</dependencies>
有 2 个依赖:
- spring-cloud-starter-alibaba-sentinel 这是 springcloud 和 sentinel 集成的项目
- spring-boot-starter-web 开启 web 最基础的依赖
添加 spring boot 的打包插件:
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
这样,我们的项目打包好了后,可以使用 java -jar 来直接运行了。
3.3 完整的 pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring-cloud-alibaba-examples</artifactId>
<groupId>com.bjsxt</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>sentinel-example-client</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
3.4 添加一个配置文件
命名为:
修改该配置文件,添加以下的配置:
server:
port: 8085
spring:
application:
name: sentinel-client
cloud:
sentinel:
transport:
dashboard: localhost:8080
port: 8719
其中:
- spring.cloud.sentinel.transport.dashboard 指定了 sentinel 控制台的 ip 和端口地址;
- spring.cloud.sentinel.transport.port 代表sentinel 客户端和控制台通信的端口,默认为8719,如果这个端口已经被占用,那么 sentinel 会自动从 8719 开始依次+1 扫描,直到找到未被占用的端口。
3.5 添加一个启动类
名称为:com.bjsxt.SentinelClientApplication
添加如下的代码:
@SpringBootApplication
public class SentinelClientApplication {
public static void main(String[] args) {
SpringApplication.run(SentinelClientApplication.class ,args) ;
}
}
3.6 添加一个 Controller
名称为:controller.TestController
在 TestContrller 里面添加如下接口:
@RestController
public classTestController {
@GetMapping("/hello")
public ResponseEntity<String> hello(){
return ResponseEntity.ok("hello,sentinel") ;
}
}
3.7 启动项目
在浏览器访问:
http://localhost:8080/#/dashboard/home
出现:
发现并没有任何的功能。
此时,我们访问一下我们写的 hello 接口:
http://localhost:8085/hello
多访问几次。
再次访问:
http://localhost:8080/#/dashboard/home
控制台已经显示正常了。
并且,在簇点链路中可以看到刚刚那笔请求,我们可以对它进行流控、降级、授权、热点等
配置(控制台是懒加载的,如果没有任何请求,那么控制台也不会有任何内容)。
四、流控规则
流量的控制规则。
在簇点链路列表中,点击/hello 后面的流控按钮:
出现:
- 资源名:标识资源的唯一名称,默认为请求路径,也可以在客户端中使用@SentinelResource 配置;
- 针 对 来 源 : Sentinel 可 以 针 对 服 务 调 用 者 进 行 限 流 , 填 写 微 服 务 名 称即spring.application.name,默认为 default,不区分来源;
- 阈值类型、单机阈值:
- QPS(Queries-per-second,每秒钟的请求数量):当调用该 api 的 QPS 达到阈值的时候,进行限流;
- 线程数:当调用该 api 的线程数达到阈值的时候,进行限流。
- 是否集群:默认不集群;
- 流控模式:
- 直接:当 api 调用达到限流条件的时,直接限流;
- 关联:当关联的资源请求达到阈值的时候,限流自己;
- 链路:只记录指定链路上的流量(指定资源从入口资源进来的流量,如果达到阈值,则进行限流)。
- 流控效果:
- 快速失败:直接失败;
- Warm Up:根据 codeFactor(冷加载因子,默认值为 3)的值,从阈值/codeFactor,经过预热时长,才达到设置的 QPS 阈值;
- 排队等待:匀速排队,让请求匀速通过,阈值类型必须设置为 QPS,否则无效。
4.1 QPS 直接失败
演示下 QPS直接失败设置及效果。点击簇点链路列表中/hello 请求后面的流控按钮:
上面设置的效果是,1 秒钟内请求/hello 资源的次数达到 2 次以上的时候,进行限流。
点击新增完成该规则的设置。
现在,在浏览器访问:
http://localhost:8085/hello
当手速快点的时候(1 秒超过 2 次),页面返回 Blocked by Sentinel (flow limiting)。并且响应码为 429。
4.2 线程数直接失败
4.2.1 添加接口
在 TestController 里面新建一个接口。
/**
* 线程直接失败
* @return
* @throwsInterruptedException
*/
@GetMapping("/thread")
public ResponseEntity<String> threadMode() throws InterruptedException {
TimeUnit.SECONDS.sleep(1);
return ResponseEntity.ok("hello,sentinel!") ;
}
其中,我们添加了:
TimeUnit.SECONDS.sleep(1);
让线程睡 1s ,这样,更容易触发规则。
重启项目。
访问我们添加的 thread 的接口:
http://localhost:8085/thread
发现,页面需要等待 1s 左右才会响应,这是线程 sleep 的效果。
4.2.2 新增流控规则
点击新增,完成创建。
4.2.3 测试该规则
浏览器快速的访问:
http://localhost:8085/thread
4.3 关联
访问某个接口达到一定的流控规则后,开始限制本接口。
4.3.1 在 TestController 里面添加 api 接口
@GetMapping("/test1")
public ResponseEntity<String> test1(){
return ResponseEntity.ok("hello,test1") ;
}
@GetMapping("/test2")
public ResponseEntity<String> test2(){
return ResponseEntity.ok("hello,test2") ;
}
重启项目,正常的访问 test1,test2 测试:
4.3.2 添加规则
我们想让 test1 关联 test2,也就是说,访问 test2 接口达到某种规则后,开始限流 test1 。
上述流控规则表示:当 1 秒内访问/test2 的次数大于 2 的时候,限流/test1。
4.3.3 测试规则
我们使用打开 2 个网页,密集访问/test2,然后我们手动浏览器请求/test1,看看效果。
访问 test1:
发现已经开始限流了。
4.4 链路
程序的调用可以看成为一条链路。当触发链路的某条规则后,该链路被限流。
上图从 API 出发,可以发现有 2 条链路,分别为:
Link1-->hello-->other
Link2-->hello-->other
2 条链路在调用上,限流规则互补影响。
4.4.1 添加一个 Service
名称为:(service.TestService)
代码为:
@Service
public class TestService {
public String hello(){
return "hello" ;
}
}
4.4.2 添加接口
@Autowired
private TestService testService ;
@GetMapping("/link1")
public ResponseEntity<String> link1(){
return ResponseEntity.ok(String.format("link1,调用 test,结果为%s",testService.hello())) ;
}
@GetMapping("/link2")
public ResponseEntity<String> lin2(){
return ResponseEntity.ok(
String.format("link2,调用test,结果为%s",testService.hello())) ;
}
4.4.3 声明资源
我们现在把 TestService 里面的 hello 方法变成一个资源:
注意:
@SentinelResource("hello")将该方法标识为一个sentinel 资源,名称为hello。
然后重启测试:
4.4.4 添加链路规则
点击簇点链路:
此时我们给 hello 该资源限流:
在入口资源,我们使用的是 link1,点击新增,完成对规则的添加。
上述配置的意思是,当通过/link1 访问 hello 的时候,QPS 大于 2 则进行限流;
言外之意就是/link 访问 hello 请求并不受影响。
4.4.5 测试该规则
打开 link1 ,link2 接口。
快速访问 link1,再访问 link2:
访问测试 n 次,发现还是不能成功。难受!
具体的错误:
4.5 预热 Warm Up
流控效果除了直接失败外,我们也可以选择预热 Warm Up。
Warm Up ( RuleConstant.CONTROL_BEHAVIOR_WARM_UP )方式,即预热 / 冷启动方式。
当系统长期处于低水位的情况下,当流量突然增加时,直接把系统拉升到高水位可能瞬间把系统压垮。通过 ” 冷启动 ” ,让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮。
sentinel 客户端的默认冷加载因子 coldFactor 为 3,即请求 QPS 从 threshold / 3 开始,经预
热时长逐渐升至设定的 QPS 阈值。
比如:
我们给 hello 资源设置该规则。
上面的配置意思是:对于/hello 资源,一开始的 QPS 阈值为 3,经过 10 秒后,QPS 阈值达到 10。
新增完成后:
快速访问/hello
http://localhost:8085/hello
查看:
过程类似于下图:
前期在预热环境,突然的高 QPS 会导致系统直接拒绝访问,慢慢地,开始大量的介绍新的请求。
最快的手速点刷新,一开始会常看到 Blocked by Sentinel (flow limiting)的提示,10 秒后几乎不再出现(因为你的手速很难达到 1秒 10 下)。
4.6 排队等待
排队等待方式不会拒绝请求,而是严格控制请求通过的间隔时间,也即是让请求以均匀的速度通过。
在 TestController里面添加接口:
private static Logger logger = LoggerFactory. getLogger( TestController.class ) ;
@GetMapping ( "/queue" )
public ResponseEntity < String > queue (){
logger.info ( "开始处理请求" ) ;
return ResponseEntity. ok( "ok" ) ;
}
重启项目并且访问 queue 接口:
给 queue 添加一个限流规则:
点击新增完成创建。
快速访问 queue 接口,观察后台的打印:
上述配置的含义是,访问/queue 请求每秒钟最多只能 1 次,超过的请求排队等待,等待超过 2000 毫秒则超时。新增该规则后,多次快速访问 localhost:8081/test1,sentinel 客户端控
制台日志打印如下:
五、降级规则
Sentinel 除了流量控制以外,对调用链路中不稳定的资源进行熔断降级也是保障高可用的重要措施之一。由于调用关系的复杂性,如果调用链路中的某个资源不稳定,最终会导致请求发生堆积。Sentinel 熔断降级会在调用链路中某个资源出现不稳定状态时(例如调用超时或异常比例升高),对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联错误。当资源被降级后,在接下来的降级时间窗口之内,对该资源的调用都自动熔断(默认行为是抛出 DegradeException)。
当访问系统失败超过一定的次数后,对该接口进行熔断的操作。
我们可以发现,降级策略分为 3种:
- RT,平均响应时间 (DEGRADE_GRADE_RT):当 1s 内持续进入 5 个请求,对应时刻的平均响应时间(秒级)均超过阈值(count,以 ms 为单位),那么在接下的时间窗口(DegradeRule 中的 timeWindow,以 s 为单位)之内,对这个方法的调用都会自动地熔断(抛出 DegradeException)。注意 Sentinel 默认统计的 RT 上限是 4900 ms,超出 此 阈 值 的 都 会 算 作 4900 ms , 若 需 要 变 更 此 上 限 可 以 通 过 启 动 配 置 项-Dcsp.sentinel.statistic.max.rt=xxx 来配置。
- 异常比例 (DEGRADE_GRADE_EXCEPTION_RATIO):当资源的每秒请求量 >= 5,并且每秒异常总数占通过量的比值超过阈值(DegradeRule 中的 count)之后,资源进入降级状态,即在接下的时间窗口(DegradeRule 中的 timeWindow,以 s 为单位)之内,对这个方法的调用都会自动地返回。异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%。
- 异常数 (DEGRADE_GRADE_EXCEPTION_COUNT):当资源近 1 分钟的异常数目超过阈值之后会进行熔断。注意由于统计时间窗口是分钟级别的,若 timeWindow 小于 60s,则结束熔断状态后仍可能再进入熔断状态。
5.1 RT
当 1s 内持续进入 5 个请求,对应时刻的平均响应时间(秒级)均超过阈值(count,以 ms为单位),那么在接下的时间窗口(DegradeRule 中的 timeWindow,以 s 为单位)之内,对这个方法的调用都会自动地熔断(抛出 DegradeException)
5.1.1 添加测试接口
@GetMapping ( "/rt" )
public ResponseEntity < String > rt () throws InterruptedException {
TimeUnit. SECONDS .sleep ( 1 ) ;
return ResponseEntity. ok( "ok" ) ;
}
5.1.2 添加降级的规则
在 1s 内,进入 5 个请求,若相应时间都超过 500ms ,则在接下来的 3s ,都执行熔断的机制。
5.1.3 测试
打开 Apache jmeter 。
文中所用jmeter百度网盘下载地址:
链接:https://pan.baidu.com/s/1o94QaFopjyNJC2YsDcdAAw 提取码:gw82
添加一个线程组:
1 s 10 个线程同时发请求。
添加一个取样器:
添加我们要测试的接口:
启动测试:
查看结果:
5.2 异常比例
当资源的每秒请求量 >= 5,并且每秒异常总数占通过量的比值超过阈值之后,资源进入降级状态,在接下来的时间窗口内,程序都会快速失败。
5.2.1 添加接口
@GetMapping ( "/exception" )
public ResponseEntity < String > exception () throws InterruptedException {
throw new RuntimeException ( "就是不想成功!" ) ;
}
5.2.2 添加降级规则
上面的配置含义是:如果/exception 的 QPS 大于 5,并且每秒钟的请求异常比例大于 0.5的话,那么在未来的 3秒钟(时间窗口)内,sentinel 断路器打开,该 api 接口不可用。
也就是说,如果一秒内有 10 个请求进来,超过 5 个以上都出错,那么会触发熔断,1
秒钟内这个接口不可用。
5.2.3 测试
打开 Jmeter,修改请求的地址:
开始测试。
在浏览器打开:
http://localhost:8086/exception
直接被熔断,停止 jemter,等待 3s,在此访问:
5.3 异常数
当策略为异常数时表示:当指定时间窗口内,请求异常数大于等于某个值时,触发降级。继续使用上面的接口测试。
5.3.1 添加规则
上面的规则表示:在 60 秒内,访问/exception 请求异常的次数大于等于 5,则触发降级。
5.3.2 测试该规则
可以看到,当第 5次访问的时候成功触发了降级。
六、热点规则
热点即经常访问的数据。很多时候我们希望统计某个热点数据中访问频次最高的数据,并对其访问进行限制。
6.1 添加一个接口
@GetMapping ( "/buy" )
@SentinelResource("buy")
public ResponseEntity < String > buy ( String prodName,Integer prodCount ){
return ResponseEntity. ok( "买" + prodCount+ "份" + prodName ) ;
}
6.2 添加热点的规则
对这个资源添加热点规则:
上面的配置含义是:对 buy 资源添加热点规则,当第 0 个参数的值为华为的时候 QPS阈值为 3,否则为1。此外,如果第 0 个参数不传,那么这笔请求不受该热点规则限制。
6.3 测试效果
不是华为:
买 1次后,里面限流。
是华为:同时买 3次,才限流。
七、系统规则
系统规则则是针对整个系统设置限流规则,并不针对某个资源,设置页面如下:
阈值类型包含以下五种:
- Load 自适应(仅对 Linux/Unix-like 机器生效):系统的 load1 作为启发指标,进行自适应系统保护。当系统 load1 超过设定的启发值,且系统当前的并发线程数超过估算的系统容量时才会触发系统保护(BBR 阶段)。系统容量由系统的 maxQps minRt估算得出。设定参考值一般是 CPU cores 2.5。
- CPU usage(1.5.0+ 版本):当系统 CPU 使用率超过阈值即触发系统保护(取值范围0.0-1.0),比较灵敏。
- 平均 RT:当单台机器上所有入口流量的平均 RT 达到阈值即触发系统保护,单位是毫秒。
- 并发线程数:当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护。
- 入口 QPS:当单台机器上所有入口流量的 QPS 达到阈值即触发系统保护。
八、授权规则
授权规则用于配置资源的黑白名单:
上述配置表示:只有 appA 和appB 才能访问test1 资源。
案例源码gitee地址:https://gitee.com/BanSheng/spring-cloud-alibaba-examples/tree/master/sentinel-example-client