Spring Cloud Sentinel
文章目录
- Spring Cloud Sentinel
- 1. Sentinel Dashboard 启动
- 2. Spring Cloud 客户端配置
- 3. Sentinel Dashboard 限流配置
- 流控模式
- 直连
- 关联
- 链路
- 流控规则
- 快速失败
- Warm Up
- 排队等待
- 4. Sentinel Dashboard 熔断配置
- 5. Sentinel Dashboard 热点配置
1. Sentinel Dashboard 启动
- 下载sentinel dashboard jar包
- sentinel dashboard启动在8889端口上
java -jar -Dserver.port=8889 sentinel-dashboard-1.8.8.jar
- 打开dashboard页面
http://localhost:8889
默认用户名密码都是sentinel
2. Spring Cloud 客户端配置
- 引入sentinel客户端
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
- application.yaml文件添加sentinel客户端配置
spring:
application:
name: stock-service
cloud:
sentinel:
transport:
dashboard: localhost:8889 # dashboard服务所在的地址跟ip
client-ip: 192.168.0.5 # 这里是程序所在的客户端ip,使用ipconfig命令查看,不配置无法再dashboard上显示当前服务
- 打开dashboard页面,由于Sentinel Dashboard是惰性加载,此时簇点链路不会有服务的信息跟服务的接口信息,需要我们调用接口,才会在上面显示
- 调用order-service的/order/add接口后可以看到比上图增加了一个/order/add接口.
3. Sentinel Dashboard 限流配置
流控模式
流控模式有三种,直连,关联跟链路
直连
限制并发过高问题,防止当前程序崩溃,限制当前接口的qps,设置为5,可以在流控规则中看到
发起压测,并发10个,只能通过5个,不限流的话10个请求时都可以通过的
关联
关联用于两个接口间的资源竞争
这里有两个接口/order/{productId}跟/order/add接口,一个接口是订单查询,一个接口是订单新增后,因为两者都要去操作数据库的订单表,所以可能存在数据库资源抢占问题,一般认为订单的新增接口优先级比订单查询接口高,所以当/order/add的QPS达到某个阈值时,对优先级低的/order/{productId}接口进行限流,这里设置当/order/add的QPS达到5时(只是一个监控,不会限制add接口的QPS),/order/{productId}接口进行限流
同时发起/order/add接口跟/order/{productId}接口的压测,add接口都成功,QPS是8,不会被上图设置的QPS=5限制住,/order/{productId}则是因为add接口的QPS达到8,直接限制了接口,拒绝请求
/order/add并发:
/order/{productId}并发:
链路
链路主要是资源分配,对同一个资源有多个入口的情况下,限制该资源的访问
- Service类通过@SentinelResource定义一个方法为资源,/order/add接口跟/order/{productId}接口都会调用到这个服务,add是新增后查询,/order/{productId}是直接查询
@Override
@SentinelResource(value = "order")
public TProduct getProduct(String productId) {
return productMapper.selectById(productId);
}
- 客户端的spring.cloud.sentinel.web-context-unify的默认值是true,资源是聚合的,这里的入口资源是sentinel_web_servlet_context,顶层的入口,统一对资源进行限流,两个接口共享资源的QPS,同一时间两个接口并发的总QPS为5
同时发起/order/add接口跟/order/{productId}并发请求10s,可以看到 两个接口的成功数(5+46)/并发时间10s = 5QPS
/order/add并发:
/order/{productId}并发:
- spring.cloud.sentinel.web-context-unify设置为false,则入口资源可以分别单独设置为/order/add接口跟/order/{productId},将对不同接口进行限流
spring:
application:
name: order-service
cloud:
sentinel:
transport:
dashboard: localhost:8889
client-ip: 192.168.0.5
web-context-unify: false
QPS大概是5
流控规则
流控规则也有三种快速失败、Warm Up、排队等待
快速失败
快速失败就是之前例子展示的,达到阈值时,拒绝请求:
Warm Up
Warm Up代表一个预热过程,使用场景是刚启动时需要一些耗时的准备工作的接口,例如刚开始需要跟其他系统建立长连接的接口,待接口逐渐建立长连接时,接口能处理的请求量也逐渐上升
客户端流控配置有个coldFactor,默认值是3,例如我们设置的PQS是12,预热时间是4s,一开始访问请求时,能达到的 起始QPS=设置的QPS/coldFactor=12/3=4,之后在4s内QPS逐渐上升到12
spring.cloud.sentinel.flow.coldFactor:
Warm Up流控配置:
接口进行4s的压测,预测下请求成功数,这里可以看成是求梯形的面积,(上底是4QPS+下底12QPS)*4秒 / 2 = 32,压测结果成功的请求数是34,差不多:
查看dashboard的接口监控,可以看到接口在4s内,QPS由起始的4达到最终的12:
排队等待
排队等待就是让当前无法处理的请求进入等待队列,等待超时时间,等待期间如果可以处理则处理,等待超时则拒绝,可以应对某个时间段峰值请求,当某个时间段有大量请求进入,又不想抛弃这些请求,就让这些请求进入队列等待处理或超时。
等待队列设置,QPS为5,等待时间为2s:
接口并发测试如下
10个并发请求:
- 第1s进来10个请求,按照5个5个划分为A、B两组请求,每组5个请求
- 第1s,A组的5个请求被处理,B组等待1s
- 第2s,A组5个已处理完成,B组5个还未超时,则再处理B组的5个请求
- 预测10个请求都被处理完成
接下来是并发15个请求
- 第1s进来15个请求,按照5个5个划分为A、B、C三组请求,每组5个请求
- 第1s,A组的5个请求被处理,B组跟C组共10个等待1s
- 第2s,A组5个已处理完成,B组5个还未超时,则再处理B组的5个请求,C组的5个请求等待1s
- 第3s,A组跟B组共10个请求已处理完成,但是此时C组已经等待了2s,达到了超时时间,C组不会被处理
- 预测大约10个请求被处理,5个请求被拒绝
4. Sentinel Dashboard 熔断配置
熔断是上级微服务调用下级微服务时,下级微服务如果出现超时或者异常达到一定比例时,则下次调用下级接口时,需要在上级微服务层面上不去调用下级微服务,所以上级微服务的调用框架需要跟Sentinel有所关联,这里微服务调用框架使用的Feign,需要开启Feign跟Sentinel之间的联系,在Feign调用其他微服务接口前,使用Sentinel进行判断是否熔断。
- application.yaml中配置feign开启sentinel
feign:
sentinel:
enabled: true
这样Sentinel就会使用SpringBoot的自动装配修改Feign.Builder,往Feign相关代码中加入Sentinel相关代码
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ SphU.class, Feign.class })
public class SentinelFeignAutoConfiguration {
@Bean
@Scope("prototype")
@ConditionalOnMissingBean
@ConditionalOnProperty(name = "feign.sentinel.enabled")
public Feign.Builder feignSentinelBuilder() {
return SentinelFeign.builder();
}
}
- Sentinel 的熔断配置
客户端添加了Feign跟Sentinel相关联的配置后,重启再调用接口,可以看到下次服务的资源
对该资源添加熔断配置:
- RT配置200ms,代表一个请求的响应时长超出200ms就代表着慢调用
- 比例阈值0.1代表统计时间内10%的请求超过200ms就进行熔断
- 最小请求数5代表统计时长内如果有超过5个请求,才会触发该熔断器,进行阈值判断
- 熔断时长代表熔断器生效时,10s内无法调用下级接口
- 统计时长就是统计这段时间内的请求,这里是统计1s内的请求
- 程序模拟上级服务是OrderService,下级服务是StockService集群,其中StockService2修改接口内容,调用该接口时,当前线程会sleep 300ms,这样就超过了上面配置的RT时间
@ResponseBody
@GetMapping("reduceProduct")
public String reduce(@RequestParam("productId") String productId, @RequestParam("num") int num) {
System.out.println("Current Time:" + LocalDateTime.now() + " Reduce Product:" + productId + " for:" + num);
try {
Thread.sleep(300);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return "SUCCESS";
}
- 并发请求
并发10次,由于Feign使用的轮询策略,所以OrderServce会两个下级服务各调用5次,这样慢请求的比例会达到0.5,这次统计窗口就会触发熔断:
下一次统计窗口再发起请求时就不会调用下级服务,10个请求都失败:
这里原本以为熔断是熔断集群内的一个微服务节点(StockService2),但是发现其实是整个下级微服务集群的某个接口的熔断,查看代码确实是熔断的一个集群:
SentinelFeign.Builder返回了一个SentinelInvocationHandler
代理方法中methodHandler.invoke是Feign的原逻辑,根据服务名选择具体的微服务进行调用,上面的SphU.entry则是Sentinel处理一些流控或者熔断逻辑,如果被熔断就会走catch异常语句:
往里面走会获取到一个chain对象,是一个ProcessorSlot链对象,chain.entry()方法来对请求进行判断,是否需要熔断降级:
其中的一个实现类是DegradeSlot,在判断的时候,拿的是ResourceName是GET:http://stock-service/stock/reduceProduct,没有区分出是哪一个下级微服务(StockService1 or StockService2),只要tryPass匹配不通过,直接就往外抛DegradeException,后面的Feign原逻辑就不会往下走去调用下级微服务了。
5. Sentinel Dashboard 热点配置
热点配置是对接口的热点数据进行限制,比如/order/{productId},productId代表产品id,产品有热销跟不热销产品,在秒杀系统时,某些产品的访问量会比其他产品多。
- 热点配置只对@SentinelResource有限制反应,无法直接对某个接口进行热点限制,在查询接口上添加@SentinelResource
@ResponseBody
@GetMapping("/{productId}")
@SentinelResource("query")
public TProduct getProduct(@PathVariable("productId") String productId) {
TProduct product = tProductService.getProduct(productId);
return product;
}
- 全局热点配置
- 参数索引0,代表一个参数,即productId,不管productId是什么,查询阈值都是10
- 单机阈值10QPS
- 全局并发测试
同时发起100045跟1000455产品的请求,并发5s,两个请求成功数都是50,可以看到两个不同商品的QPS互不影响,都是10
100045:
1000455:
- 例外热点配置
热点规则的高级选项可以配置例外项,这里配置100046的QPS为15,注意参数类型一定要跟后端接口的参数类型对得上,否则例外配置不会生效:
- 例外项并发测试
100046产品并发5s,成功数是75,QPS是15
100045的QPS没有变,还是10