简介
https://github.com/alibaba/Sentinel 官⽹地址
https://github.com/alibaba/Sentinel/wiki/%E4%BB%8B%E7%BB%8D 中⽂⽂档
随着微服务的流⾏,服务和服务之间的稳定性变得越来越重要。Sentinel 以流量为切⼊点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。
Sentinel 具有以下特征:
丰富的应⽤场景:Sentinel 承接了阿⾥巴巴近 10 年的双⼗⼀⼤促流量的核⼼场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填⾕、集群流量控制、实时熔断下游不可⽤应⽤等。
完备的实时监控:Sentinel 同时提供实时的监控功能。您可以在控制台中看到接⼊应⽤的单台机器秒级数据,甚⾄ 500 台以下规模的集群的汇总运⾏情况。
⼴泛的开源⽣态:Sentinel 提供开箱即⽤的与其它开源框架/库的整合模块,例如与 SpringCloud、Dubbo、gRPC 的整合。您只需要引⼊相应的依赖并进⾏简单的配置即可快速地接⼊Sentinel。
完善的 SPI 扩展点:Sentinel 提供简单易⽤、完善的 SPI 扩展接⼝。您可以通过实现扩展接⼝来快速地定制逻辑。例如定制规则管理、适配动态数据源等。
安装
1)下载
https://github.com/alibaba/Sentinel/releases/download/1.8.1/sentinel-dashboard-1.8.1.jar 下载地址
2)启动
nohup java -jar sentinel-dashboard-1.8.1.jar > myout.file 2>&1 &
3)访问
http://localhost:8080,⽤户名密码都是sentinel
Windows 启动
案例:
pom.xml
<!-- 后续做Sentinel的持久化会⽤到的依赖 -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
<!-- sentinel -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
yml 配置:
spring:
cloud:
nacos:
discovery:
server-addr: localhost:8848
sentinel:
transport:
#配置Sentinel dashboard地址
dashboard: localhost:8080
#默认8719端⼝,假如被占⽤会⾃动从8719开始依次+1扫描,直⾄找到未被占⽤的端⼝
port: 8719
FlowLimitController
@RestController
@Slf4j
public class FlowLimitController
{
@GetMapping("/testA")
public String testA()
{
return "------testA";
}
@GetMapping("/testB")
public String testB()
{
log.info(Thread.currentThread().getName()+"\t"+"...testB");
return "------testB";
}
}
sentinel采⽤懒加载⽅式,执⾏⼀次微服务调⽤,sentinel控制台即可监控到该微服务
设置好以后
本地 启动Nacos,启动Sentinel
sentinel采⽤懒加载⽅式,执⾏⼀次微服务调⽤,sentinel控制台即可监控到该微服务
流量控制
介绍
流量控制(flow control),其原理是监控应⽤流量的 QPS 或并发线程数等指标,当达到指定的阈值时对流量进⾏控制,以避免被瞬时的流量⾼峰冲垮,从⽽保障应⽤的⾼可⽤性。
**资源名:**唯⼀名称,默认请求路径
针对来源:Sentinel可以针对调⽤者进⾏限流,填写微服务名,默认default(不区分来源)
阈值类型:
QPS(每秒请求数量):当调⽤改api的QPS达到阈值的时候,进⾏限流
线程数,当调⽤该api的线程数达到阈值的时候,进⾏限流
是否集群:不需要集群
流控模式
直接:api达到限流条件时,直接限流
关联:当关联的资源达到阈值时,限流⾃⼰
链路,只记录链路上的流量(指定资源从⼊⼝资源进来的流量,如果达到阈值,就进⾏限流)
流控效果:
快速失败:直接失败,抛出异常
Warm UP:根据codeFactor(冷加载因⼦,默认3)的值,从阈/codeFactor,经过预热时⻓,才达到设置的QPS阈值
排队等待:匀速排队,让请求以匀速的速度通过,阈值类型必须设置QPS,否则⽆效
功能对⽐
线程池隔离和信号量隔离区别
信号量模式
“信号量”在编程术语中使⽤单词semaphore,那什么是“信号量”?信号量就好⽐你家厨房⼊⼝架⼦上摆了三把锅:
如果你的孩⼦热奶拿⾛⼀把,你的⽼婆热汤拿⾛⼀把,你的妈妈做菜拿⾛⼀把,你想煮⾯条就没有锅了。当你看到这种情况,你就不会进⼊厨房了,也就说厨房按照“锅的数量”作为信号量,只能容纳三个⼈(线程)。
当你的⽼婆热完汤之后,把锅重新放回架⼦上,你就可以去获得⼀个锅,你就可以进⼊厨房了。所以说:当信号量总数为1的时候,也就是你家只有⼀把锅,此时信号量作⽤就等同于互斥锁。
在该模式下,接收请求和执⾏下游依赖在同⼀个线程内完成,不存在线程上下⽂切换所带来的性能开销,所以⼤部分场景应该选择信号量模式,但是在下⾯这种情况下,信号量模式并⾮是⼀个好的选择。
⽐如⼀个接⼝中依赖了3个下游:serviceA、serviceB、serviceC,且这3个服务返回的数据互相不依赖,这种情况下如果针对A、B、C的熔断降级使⽤信号量模式,那么接⼝耗时就等于请求A、B、C服务耗时的总和,⽆疑这不是好的⽅案。
另外,为了限制对下游依赖的并发调⽤量,可以配置Hystrix的
execution.isolation.semaphore.maxConcurrentRequests ,当并发请求数达到阈值时,请求线程可以快速失败,执⾏降级
实现也很简单,⼀个简单的计数器,当请求进⼊熔断器时,执⾏ tryAcquire() ,计数器加1,结果⼤于阈值的话,就返回false,发⽣信号量拒绝事件,执⾏降级逻辑。当请求离开熔断器时,执⾏ release() ,计数器减1。
hystrix中配置使⽤信号量模式
hystrix.command.default.execution.isolation.thread.strategy=SEMAPHORE
线程池隔离模式
在该模式下,⽤户请求会被提交到各⾃的线程池中执⾏,把执⾏每个下游服务的线程分离,从⽽达到资源隔离的作⽤。当线程池来不及处理并且请求队列塞满时,新进来的请求将快速失败,可以避免依赖问题扩散。
在application.yml中修改默认线程池⼤⼩
hystrix.threadpool.service-provider.coreSize=100
在信号量模式提到的问题,对所依赖的多个下游服务,通过线程池的异步执⾏,可以有效的提⾼接⼝性能。
演示QPS案例
测试每秒钟超过1此api请求,抛出Blocked by Sentinel (flow limiting)
阈值类型:线程数
并发数控制⽤于保护业务线程池不被慢调⽤耗尽。Sentinel 并发控制不负责创建和管理线程池,⽽是简单统计当前请求上下⽂的线程数⽬(正在执⾏的调⽤数⽬),如果超出阈值,新的请求会被⽴即拒绝,
效果类似于信号量隔离。并发数控制通常在调⽤端进⾏配置。
@GetMapping("/testA")
public String testA() throws InterruptedException {
TimeUnit.MILLISECONDS.sleep(2000);
return "------testA";
}
测试线程超出阈值,直接抛出Blocked by Sentinel (flow limiting)
*流控模式:直接
限流表现:当超过阀值,就会被降级,抛出异常Blocked by Sentinel (flow limiting)
流控模式:
关联:
当关联的资源达到阈值,就限流⾃⼰,也就是当与testA关联的testB达到阈值,就限流⾃⼰配置如下:
使⽤Postman请求/testB
效果:/testB达到访问阈值,/testA降级
流控效果–预热
Warm Up( RuleConstant.CONTROL_BEHAVIOR_WARM_UP )⽅式,即预热/冷启动⽅式。当系统⻓期处于低⽔位的情况下,当流量突然增加时,直接把系统拉升到⾼⽔位可能瞬间把系统压垮。通过"冷启动",让通过的流量缓慢增加,在⼀定时间内逐渐增加到阈值上限,给冷系统⼀个预热的时间,避免冷系统被压垮
测试效果,开始阈值默认3,经过预热慢慢涨到10
warm :一段时间后QPS达不到10的话会又调整回3去,当访问量再次上去的时候,会再次调整预热
流控效果:匀速排队
匀速排队( RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER )⽅式会严格控制请求通过的间隔时间,也即是让请求以均匀的速度通过,对应的是漏桶算法。
该⽅式的作⽤如下图所示:
这种⽅式主要⽤于处理间隔性突发的流量,例如消息队列。想象⼀下这样的场景,在某⼀秒有⼤量的请求到来,⽽接下来的⼏秒则处于空闲状态,我们希望系统能够在接下来的空闲期间逐渐处理这些请求,
⽽不是在第⼀秒直接拒绝多余的请求。
注意:匀速排队模式暂时不⽀持 QPS > 1000 的场景。
熔断降级
1)概述
除了流量控制以外,对调⽤链路中不稳定的资源进⾏熔断降级也是保障⾼可⽤的重要措施之⼀。⼀个服务常常会调⽤别的模块,可能是另外的⼀个远程服务、数据库,或者第三⽅ API 等。例如,⽀付的时候,可能需要远程调⽤银联提供的 API;查询某个商品的价格,可能需要进⾏数据库查询。然⽽,这个被依赖服务的稳定性是不能保证的。如果依赖的服务出现了不稳定的情况,请求的响应时间变⻓,那么调⽤服务的⽅法的响应时间也会变⻓,线程会产⽣堆积,最终可能耗尽业务⾃身的线程池,服务本身也变得不可⽤。
现代微服务架构都是分布式的,由⾮常多的服务组成。不同服务之间相互调⽤,组成复杂的调⽤链路。
以上的问题在链路调⽤中会产⽣放⼤的效果。复杂链路上的某⼀环不稳定,就可能会层层级联,最终导致整个链路都不可⽤。因此我们需要对不稳定的弱依赖服务调⽤进⾏熔断降级,暂时切断不稳定调⽤,避免局部不稳定因素导致整体的雪崩。熔断降级作为保护⾃身的⼿段,通常在客户端(调⽤端)进⾏配置。
2)熔断策略
Sentinel 提供以下⼏种熔断策略:
慢调⽤⽐例 ( SLOW_REQUEST_RATIO ):选择以慢调⽤⽐例作为阈值,需要设置允许的慢调⽤ RT(即最⼤的响应时间),请求的响应时间⼤于该值则统计为慢调⽤。当单位统计时⻓( statIntervalMs )内请求数⽬⼤于设置的最⼩请求数⽬,并且慢调⽤的⽐例⼤于阈值,则接下来的熔断时⻓内请求会⾃动被熔断。经过熔断时⻓后熔断器会进⼊探测恢复状态(HALF-OPEN 状态),若接下来的⼀个请求响应时间⼩于设置的慢调⽤ RT 则结束熔断,若⼤于设置的慢调⽤ RT 则会再次被熔断。
异常⽐例 ( ERROR_RATIO ):当单位统计时⻓( statIntervalMs )内请求数⽬⼤于设置的最⼩请求数⽬,并且异常的⽐例⼤于阈值,则接下来的熔断时⻓内请求会⾃动被熔断。经过熔断时⻓后熔断器会进⼊探测恢复状态(HALF-OPEN 状态),若接下来的⼀个请求成功完成(没有错误)则结
束熔断,否则会再次被熔断。异常⽐率的阈值范围是 [0.0, 1.0] ,代表 0% - 100%。
异常数 ( ERROR_COUNT ):当单位统计时⻓内的异常数⽬超过阈值之后会⾃动进⾏熔断。经过熔断时⻓后熔断器会进⼊探测恢复状态(HALF-OPEN 状态),若接下来的⼀个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。
3)演示
慢调用比例:
条件1 :在1s内有5次以上请求,比如说10次请求
条件2: 8次请求RT超过0.2秒
效果:在接下来1秒内熔断
使⽤JMeter启动并发请求:
效果:/testD服务降级,抛出异常Blocked by Sentinel (flow limiting),关闭JMeter,1秒后恢复
异常⽐例
配置如下:
使⽤JMeter启动并发请求
效果:/testD服务降级,抛出异常Blocked by Sentinel (flow limiting),关闭JMeter,3秒后恢复
异常数
配置如下:
效果:/testD服务降级,抛出异常Blocked by Sentinel (flow limiting),关闭JMeter,10秒后恢复
热点参数限流
何为热点?热点即经常访问的数据。很多时候我们希望统计某个热点数据中访问频次最⾼的 Top K 数据,并对其访问进⾏限制。⽐如:
商品 ID 为参数,统计⼀段时间内最常购买的商品 ID 并进⾏限制
⽤户 ID 为参数,针对⼀段时间内频繁访问的⽤户 ID 进⾏限制
热点参数限流会统计传⼊参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调⽤进⾏限流。热点参数限流可以看做是⼀种特殊的流量控制,仅对包含热点参数的资源调⽤⽣效。
Sentinel 利⽤ LRU 策略统计最近最常访问的热点参数,结合令牌桶算法来进⾏参数级别的流控。热点参数限流⽀持集群模式。
热点参数限流需要使⽤@SentinelResource注解,具体代码如下:
@GetMapping("/testHotKey")
@SentinelResource(value = "testHotKey", blockHandler = "deal_testHotKey")
public String testHotKey(@RequestParam(value = "p1", required = false) String p1,
@RequestParam(value = "p2", required = false) String p2) {
//int age = 10/0;
return "------testHotKey";
}
public String deal_testHotKey(String p1, String p2, BlockException exception) {
return "------deal_testHotKey,o(╥﹏╥)o"; //sentinel默认抛出 ParamFlowException
}
注:@SentinelResource,功能类似@HystrixCommand
参数索引为0,默认就是P1
@SentinelResource详解
注意:注解⽅式埋点不⽀持 private ⽅法
@SentinelResource ⽤于定义资源,并提供可选的异常处理和 fallback 配置项。
@SentinelResource 注解包含以下属性:
value :资源名称,必需项(不能为空)
entryType :entry 类型,可选项(默认为 EntryType.OUT )
blockHandler / blockHandlerClass : blockHandler 对应处理 BlockException 的函数名称,可选项。blockHandler 函数访问范围需要是 public ,返回类型需要与原⽅法相匹配,参数类型需要和原⽅法相匹配并且最后加⼀个额外的参数,类型为 BlockException 。blockHandler函数默认需要和原⽅法在同⼀个类中。若希望使⽤其他类的函数,则可以指定blockHandlerClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则⽆法解析。
fallback/fallbackClass:fallback 函数名称,可选项,⽤于在抛出异常的时候提供 fallback 处理逻辑。fallback 函数可以针对所有类型的异常(除了 exceptionsToIgnore ⾥⾯排除掉的异常类型)进⾏处理。fallback 函数签名和位置要求:
- 返回值类型必须与原函数返回值类型⼀致;
- ⽅法参数列表需要和原函数⼀致,或者可以额外多⼀个 Throwable 类型的参数⽤于接收对应的异常。
- fallback 函数默认需要和原⽅法在同⼀个类中。若希望使⽤其他类的函数,则可以指定fallbackClass 为对应的类的 Class
对象,注意对应的函数必需为 static 函数,否则⽆法解析。
defaultFallback (since 1.6.0):默认的 fallback 函数名称,可选项,通常⽤于通⽤的fallback 逻辑(即可以⽤于很多服务或⽅法)。默认 fallback 函数可以针对所有类型的异常(除了exceptionsToIgnore ⾥⾯排除掉的异常类型)进⾏处理。若同时配置了 fallback 和defaultFallback,则只有 fallback 会⽣效。defaultFallback 函数签名要求:
返回值类型必须与原函数返回值类型⼀致;
⽅法参数列表需要为空,或者可以额外多⼀个 Throwable 类型的参数⽤于接收对应的异常。
defaultFallback 函数默认需要和原⽅法在同⼀个类中。若希望使⽤其他类的函数,则可以指定 fallbackClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则⽆法解析。
exceptionsToIgnore (since 1.6.0):⽤于指定哪些异常被排除掉,不会计⼊异常统计中,也不会进⼊ fallback 逻辑中,⽽是会原样抛出。
服务熔断综合应⽤
Ribbon
在订单微服务中使⽤RestTemplate调⽤⽀付微服务,配置服务限流和降级
@GetMapping("/consumer/ribbon/{id}")
@SentinelResource(value = "ribbon", fallback = "handlerFallback",
blockHandler = "blockHandler", exceptionsToIgnore ={IllegalArgumentException.class} )
public CommonResult consumerRibbon(@PathVariable("id") Long id) {
CommonResult<Payment> result = restTemplate.getForObject(SERVICE_URL +"/paymentSQL/" + id, CommonResult.class);
if (4 == id) {
throw new IllegalArgumentException("id=4,抛出⾮法参数异常");
} else if (result.getData() == null) {
throw new NullPointerException("空指针异常,id不存在。。。");
}
return result;
}
public CommonResult handlerFallback(@PathVariable("id") Long id, Throwable e) {
Payment payment = new Payment(id, "业务异常。。。");
return new CommonResult(444, "业务异常兜底⽅法 e=" + e.getMessage(), payment);
}
public CommonResult blockHandler(@PathVariable("id") Long id, BlockException e) {
Payment payment = new Payment(id, "控制台违规异常。。。");
return new CommonResult(445, "控制台违规异常 e=" + e.getMessage(), payment);
}
设置限流规则,或者关闭⽀付微服务,可以看到服务降级的效果
common.yaml配置
#对Feign的⽀持
feign:
sentinel:
enabled: true
设置限流规则,或者关闭⽀付微服务,可以看到服务降级的效果
blockHandler 处理控制台异常,配置的规则违规会进入到这个处理器。
后者处理业务异常
按照系统没有处理异常,报错如下:
加入Sentinel异常处理后,
访问id 为5的商品时,出现
当加上流控规则以后
可以看到已经做出了流控规则的响应。
如果不设置blockHandler,则默认会按照handlerFallback的方法执行异常返回
这里加上 忽略 非法参数异常的参数后,系统会按照默认的方法去报错,不进行降级处理
feign
当服务挂掉以后,默认抛异常
当启用了feign的服务降级以后,
服务挂掉以后,再去调用会显示
持久化
⽬前的sentinel 当重启以后,数据都会丢失,和 nacos 类似原理。需要持久化。它可以被持久化到 nacos 的数据库中。
1)pom依赖:
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
2)yml配置:
spring:
cloud:
sentinel:
datasource:
ds1:
nacos:
server-addr: localhost:8848
dataId: ${spring.application.name}
group: DEFAULT_GROUP
data-type: json
rule-type: flow
3)去nacos上创建⼀个dataid ,名字和yml配置的⼀致,json格式,内容如下:
[
{
"resource": "/testA",
"limitApp": "default",
"grade": 1,
"count": 1,
"strategy": 0,
"controlBehavior": 0,
"clusterMode": false
}
]
4)启动应⽤,发现存在 关于 /testA 请求路径的流控规则
总结: 就是在 sentinel 启动的时候,去 nacos 上读取相关规则配置信息,实际上它规则的持久化,就是第三步,粘贴到nacos上保存下来,就算以后在 sentinel 上⾯修改了,重启应⽤以后也是有效的。
持久化配置应用截图
此时当我们重启 payment微服务的时候,
,他会默认去读取Nacos的配置的流控规则。
这时也会配置到Sentinel里面