Spring Cloud Alibaba实践——使用Sentinel
文章目录
Spring Cloud Alibaba实践——使用Sentinel
使用Sentinel实现服务容错
当微服务调用链底部的一个被众多微服务实例调用的服务不可用时,很容易导致这个底层服务所在的众多链路一起不能正常提供服务。在这种情况下,为了避免雪崩效应,就需要实现服务容错功能。
微服务常见容错方案:
-
超时
通过超时来释放资源,这样就不容易被拖死,只要释放够快。
-
限流
通过评估来限制流量,防止微服务被拖死。
-
仓壁模式
资源有对立线程池,拥有自己拒绝策略。资源之间不相互影响。
-
断路器模式
监控错误率或者错误次数达到一定阈值,就跳闸,就认为依赖微服务不可用,监控加开关
其中,断路器方案可以很巧妙地通过三状态来实现服务的自动保护和自动恢复:
-
断路器关闭
此时接口可正常使用
-
断路器打开
当一段时间内服务调用失败的次数达到了阈值,触发断路器,后续的调用直接失败
-
断路器半开
断路器打开后不是一直处在打开状态,会在后续的某个时间点尝试执行一个请求,如果请求成功,说明此时下游服务已可用,断路器关闭,否则继续这个过程直到服务可用。通过这种机制,既保护了服务不会因为下游服务的失败而被拖垮,又实现了服务的自我修复。
搭建Sentinel控制台
在https://github.com/alibaba/Sentinel/releases下载sentinel的jar包,然后使用java -jar sentinel-dashboard-1.8.0.jar命令运行即可。
用Sentinel为内容中心实现服务容错
首先为内容中心引入依赖:
|
|
添加Sentinel相关配置信息:
|
|
启动内容中心,此时内容中心相关接口还未出现在Sentinel中,因为Sentinel默认和Ribbon一样都是懒加载的。
访问一下http://127.0.0.1:8082/shares/1,可以发现此时该接口路径已经出现在Sentinel中了。
Sentinel流控规则
在Sentinel控制台上,可以看到菜单栏中的簇点链路功能。其中包含已经访问过的接口路径,比如/shares/1,可以点击该接口后面的流控按钮为其添加流控规则。
流控规则可以对不同的来源的QPS或线程数设置单机阈值进行限流。其中,高级模式中可以设置流控模式和流控效果。
流控模式:
-
直接
最好理解的流控模式,比如设置该接口的
QPS为1,多次访问/shares/1,就会出现部分请求直接返回Blocked by Sentinel (flow limiting)的情况。 -
关联
当关联的资源达到阈值,就限流自己。
比如,设置
/shares/{id}的接口的关联接口为/getInstances,QPS的阈值为1,并编写如下测试类代码循环调用/getInstances:1 2 3 4 5 6 7 8 9 10public class Test { public static void main(String[] args) { RestTemplate restTemplate = new RestTemplate(); for (int i = 0;i < 1000000; i ++){ String object = restTemplate.getForObject("http://localhost:8082/getInstances", String.class); System.out.println(object); } } }在此循环调用类执行时,访问
http://127.0.0.1:8082/shares/1,会发现虽然没有设置这个接口的直接流控规则,但是因为设置了关联流控规则,依然因为/getInstances的QPS超过阈值而不能访问。关联的流控规则可以用在两个相互影响性能的接口上,比如一个接口查询,另一个接口修改,如果看重修改的性能,可以为查询接口设置流控规则,关联修改接口,这样当修改接口
QPS过大时,查询接口会被限制,进而保证修改接口的性能。也就是说,关联的流控规则是为了保护被关联的接口。 -
链路
只记录指定链路上的流量。
新建TestService并创建
common方法:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17package tech.punklu.contentcenter; import com.alibaba.csp.sentinel.annotation.SentinelResource; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @Slf4j @Service public class TestService { @SentinelResource("common") public String common(){ log.info("common...."); return "common"; } }在
TestController中新建两个接口调用这个接口:1 2 3 4 5 6 7 8 9 10 11 12 13 14@Autowired private TestService testService; @GetMapping("/test-a") public String testA(){ this.testService.common(); return "test-a"; } @GetMapping("/test-b") public String testB(){ this.testService.common(); return "test-b"; }增加如下配置:
1 2 3 4 5spring: cloud: sentinel: # 配置web接口上下文不合并 web-context-unify: false之所以增加这个接口是因为像上述代码中,出现两个web接口都调用被
@SentinelResource注解标注的方法时,如果不增加这个配置项,会只在一个接口中出现common的上下文,且失去链路流控规则的效果。这个配置是在Spring Cloud Alibaba 2.2.1.RELEASE(不包括) 之后才出现的。之前的版本需要另外通过代码配置的方式解决。启动内容中心服务,先后访问
http://127.0.0.1:8082/test-a和http://127.0.0.1:8082/test-b,使这两个接口出现在Sentinel控制台中。然后在Sentinel控制台的簇点链路中可以看到如下的层级关系:1 2 3 4/test-a common /test-b common选择
/test-a下的common进行流控配置,QPS阈值设置为1,流控模式选择链路,流控模式的入口资源选择/test-a,然后不断访问http://127.0.0.1:8082/test-a,可以发现也会被Sentinel直接返回错误。而不断访问http://127.0.0.1:8082/test-b就不会出现这种情况。
流控规则中的针对来源指的是微服务级别的调用来源,入口资源指的是API级别的调用来源。
流控规则中还可配置流控效果,流控效果可选配置:
-
快速失败
直接失败、抛出异常。相关源码:
com.alibaba.csp.sentinel.slots.block.flow.controller.DefaultController -
Warm Up(预热)
根据
codeFactor(冷加载因子,默认为3)的值,从最开始的阈值/codeFactor作为最初的阈值,经过预热时长,才到达设置的最高QPS阈值。适用于秒杀服务等流量爆发增长的场景下保护微服务。Warm Up的相关讲解文档:限流-冷启动
相关源码:
com.alibaba.csp.sentinel.slots.block.flow.controller.WarmUpController -
排队等待
匀速排队,让请求以均匀的速度通过,阈值类型必须设成
QPS,否则无效。排队等待的效果必须使用QPS而不能使用线程作为阈值类型。线程无效将
common的流控QPS阈值设置为1,流控效果设置为超时等待,等待时间设置长一些(5000000),然后编写如下测试代码:1 2 3 4 5 6 7public static void main(String[] args) { RestTemplate restTemplate = new RestTemplate(); for (int i = 0;i < 1000000; i ++){ String object = restTemplate.getForObject("http://localhost:8082/test-a", String.class); System.out.println(object); } }运行上述代码,可以发现虽然
common的QPS阈值只有1,但是这个循环里的后续请求并未直接失败,而是排队执行,通过这种方式保证了不会因为突然特别多的线程而导致微服务挂掉的情况,也实现了不直接丢弃请求的功能。匀速排队相关讲解文档:限流-匀速排队
相关源码:
com.alibaba.csp.sentinel.slots.block.flow.controller.RateLimiterController
Sentinel降级规则
在Sentinel控制台菜单栏里也有专门的降级规则入口,降级规则相比流控规则要简单很多。
降级规则里的降级策略有三种:
-
慢调用比例
平均响应时间
在簇点链路里的
/shares{id}这个URL上设置降级规则,选择RT策略,最大RT设置为1,熔断时长设置为5,最小请求数也设置为5。比例阈值代表当慢调用的比例超过这个值时触发断路器。代表当平均响应时间(秒级统计)超出阈值并且在设置的熔断时长(5s内)通过的请求数大于等于设置的最小请求数并且比例阈值大于设置的值时,则触发降级(断路器打开),熔断时长(5s后)结束后关闭降级。 -
异常比例
和慢调用比例的概念相似,只是把触发降级规则的条件改为了
比例阈值、熔断时长、最小请求数。 -
异常数
把触发降级规则的条件改为了
异常数、熔断时长、最小请求数。需要注意的是,异常数的统计层级是以分钟计的,而不像其他两个是以秒计的。因为是以异常数为判断条件,所当熔断的时间结束时,异常数依然大于设置的阈值,就会再次触发降级,因此最好将熔断时长设置大一点。降级相关源码:
com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule#passCheck
Sentinel热点规则
热点规则可以针对某个接口的某个参数进行热点降级,即当统计窗口时长内该接口的指定参数有值且数量大于设置好的阈值时,对该接口进行保护,直接抛出异常。
新建如下接口:
|
|
启动后访问该接口,然后在Sentinel控制台上的簇点链路里对该接口进行热点规则设置:
-
参数索引
即参数在接口参数列表中的下标,从0开始计算。
-
单机阈值
即为该参数有值时的统计窗口时长中的最大请求个数,当超过这个个数时,后续请求会直接抛出异常。
-
统计窗口时长
设置在多长时间内指定的参数有值的请求数量,才会使接口抛出异常。
设置参数索引为0,单机阈值和统计窗口时长都为1,然后不停的访问http://127.0.0.1:8082/test-hot?a=1&b=2,会发现部分请求直接返回错误。而不停的访问http://127.0.0.1:8082/test-hot?b=2时则不会抛出异常。可见规则是对第一个参数a使用的。
除此之外,还可以在高级选项里设置参数例外项。例如参数值设为5,限流阈值设为1000,参数类型设置为1000,然后不停访问http://127.0.0.1:8082/test-hot?a=5,会发现不会被直接返回异常。也就是说,可以在对单个参数设置全局热点规则后,再对这个参数的特殊值设置另外的热点规则。
热点规则需要特别注意的是,参数类型必须是基本类型或String,否则不会生效。
相关源码:com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowChecker#passCheck
Sentinel系统规则
Sentinel系统规则支持五种阈值类型:
-
LOAD
当系统一分钟的负载超过阈值,且并发线程数超过系统容量时触发,建议设置为CPU核心数*2.5(仅对Linux/Unix-like机器生效,可以使用
uptime命令查看load负载)。系统容量 = maxQps * minRt
-
maxQps
Sentinel秒级统计出来的最大QPS
-
minRt
Sentinel秒级统计出来的最小响应时间
相关源码:
com.alibaba.csp.sentinel.slots.system.SystemRuleManager#checkBbr -
-
RT
所有入口流量的平均RT达到阈值触发
-
线程数
所有入口流量的并发线程数达到阈值触发
-
入口QPS
所有入口流量的QPS达到阈值触发
相关源码:
com.alibaba.csp.sentinel.slots.system.SystemRuleManager#checkSystem -
CPU使用率
Sentinel授权规则
授权规则可用来对其他微服务进行授权,比如对/shares/{id}这个接口增加授权规则,设置流控应用为test,授权类型为白名单,即意味着这个接口对名为test的微服务开放调用,同理,还可设置黑名单,禁止某个服务调用。
代码配置规则
前面的规则设置都是通过Sentinel的控制台进行的,Sentinel也支持通过代码配置的形式设置规则。
|
|
Sentinel与控制台通信原理
微服务会集成sentinel-transport-simple-http模块,通过这个模块把微服务集成到Sentinel控制台上,并定时给Sentinel控制台发送心跳。此时Sentinel控制台可以知道各个微服务的地址,在Sentinel控制台的机器列表里可以看到对应的微服务机器信息(ip、端口号等)。Sentinel也就可以获取到各个微服务的监控信息(通过调用微服务的监控API),也可以通过调用微服务上的接收Sentinel规则的接口将设置好的规则推送过去。
-
注册/心跳发送
com.alibaba.csp.sentinel.transport.heartbeat.SimpleHttpHeartbeatSender -
通信API
com.alibaba.csp.sentinel.command.CommandHandler的实现类
控制台相关配置项
应用端连接控制台配置项:
|
|
控制台启动时的可选配置项:
| 配置项 | 默认值 | 描述 |
|---|---|---|
| server.port | 8080 | 指定端口 |
| csp.sentinel.dashboard.server | localhost:8080 | 指定地址 |
| project.name | - | 指定程序的名称 |
| sentinel.dashboard.auth.username[1.6版本开始支持] | sentinel | Dashboard登录账号 |
| sentinel.dashboard.auth.password[1.6版本开始支持] | sentinel | Dashboard登录密码 |
| server.servlet.session.timeout[1.6版本开始支持] | 30分钟 | 登录Session过期时间 配置为7200表示7200秒 配置为60m表示60分钟 |
例如,可以在启动时指定登录的账号密码:
|
|
Sentinel相关API
在之前的例子中,Sentinel都是用来保护Spring MVC的接口,但是Sentinel不止可以保护Spring MVC定义的接口,也可保护其他资源。
首先添加配置项禁用掉Sentinel对Spring MVC的监控和保护,防止干扰:
|
|
在TestController中增加如下测试代码:
|
|
启动内容中心,访问http://127.0.0.1:8082/test-sentinel-api?a=2接口,可以看到此时相关的Spring MVC接口已经不再出现在Sentinel控制台上了。此时可以对这个非Spring MVC 资源进行限流,设置QPS阈值为1,不停访问http://127.0.0.1:8082/test-sentinel-api?a=2就可以看到流控规则生效,直接报错了。
在上面的代码中,通过使用Sentinel的SphU类保护被指定的名为test-sentinel-api的资源。当QPS、线程数、RT、错误率、错误次数等指标超过阈值时直接抛出BlockException。
需要注意的是,这种用法对于抛出的其他类型的异常是不会进行捕获并返回相应的Sentinel错误信息的。要想使自己抛出的异常也被Sentinel降级,需要进行特殊处理:
|
|
通过Tracer捕获其他类型的异常后,再重新启动应用,设置该接口的QPS的阈值为1,访问http://127.0.0.1:8082/test-sentinel-api,会发现此时IllegalArgumentException也可以触发流控规则了。
之前简略提过可以对接口设置针对不同的微服务来源设置流控规则。可以通过Sentinel里的ContextUtil来完成对应的功能实现。
修改代码:
|
|
定义一个名称为test-sentinel-api的资源,保护从test-service这个微服务来的请求。启动后,先访问http://127.0.0.1:8082/test-sentinel-api,会发现此时未触发流控规则,返回结果为参数非法,说明未被流控,对该接口新增流控规则,针对来源填写代码里声明的test-service,再多次访问http://127.0.0.1:8082/test-sentinel-api会发现此时已经被流控,将针对来源改为其他值再次访问,则不会触发流控。
SentinelResource注解
上面的通过ContextUtil、Tracer、SphU的方式实现限流的方式会导致流控降级代码和业务代码耦合,所以应该使用@Sentinel注解实现相关功能。
|
|
可以看到通过@Sentinel注解可以只在方法中关心业务代码,在注解的属性中指定对应的流控和降级处理方法。1.6以后还可以在注解属性中指定对应的流控处理类和降级处理类。流控处理类和降级处理类里的方法必须使用static修饰符修饰。
相关源码:com.alibaba.csp.sentinel.annotation.aspectj.SentinelResourceAspect
Sentinel整合RestTemplate
在启动类中的声明RestTemplate实例的方法上添加上@SentinelRestTemplate注解即可实现。之后在Sentinel控制台中即可看到对应的接口并设置相应规则。
可通过开关关闭,便于本地调试:
|
|
相关源码:org.springframework.cloud.alibaba.sentinel.custom.SentinelBeanPostPrecessor
Sentinel整合Feign
首先添加配置项:
|
|
启动项目,http://127.0.0.1:8082/shares/1。然后可以在Sentinel对控制台看到该接口依赖的用户中心的接口,对用户中心的该接口设置流控规则,设置QPS阈值为1。然后不停访问http://127.0.0.1:8082/shares/1,会发现已经被流控了。
除此之外,还可为Feign设置当限流降级发生时的自己的处理逻辑:
新建UserCenterFeignClient的对应异常处理类:
|
|
即新建一个类实现之前的用户中心Feign代理接口,并实现对应的流控降级后的默认处理方法,然后在UserCenterFeignClient类上的@FeignClient注解上增加fallback属性,将fallback的默认兜底处理类指向刚才创建好的类:
|
|
启动项目,设置流控规则后,重复访问http://127.0.0.1:8082/shares/1后,会发现虽然触发了流控规则,但是并没有直接抛出异常且wxNickName仍然有值,只是被替换为了兜底策略里的默认值。
此时控制台里并没有打印进入兜底策略的相关日志,如果想要显示这样的日志,可以使用@FeignClient注解的fallbackFactory属性来指定对应的兜底类,首先新建兜底类:
|
|
然后在UserCenterFeignClient中通过@FeignClient的fallbackFactory注解指定新创建的兜底类即可:
|
|
启动项目后重复上述测试、增加流控步骤,可以发现此时wxNickName依然有默认值,且控制台也打印出了对应的兜底日志。
相关源码:org.springframework.cloud.alibaba.sentinel.feign.SentinelFeign
Sentinel错误页优化
之前的代码中,不管是限流还是降级或是其他错误,都会统一返回同样的Sentinel默认错误信息,不能区分错误类型。为了实现根据不同的错误类型返回不同的信息,需要编写专门的处理逻辑:
|
|
如上所示,实现Sentinel的扩展接口BlockExceptionHandler,即可以实现对应的区分错误来源并做相应的限制。
启动后分别设置流控、降级规则,并不停访问http://127.0.0.1:8082/shares/1接口,可以看到已经根据不同的错误返回了不同的错误信息了。
Sentinel区分来源
在Sentinel控制台里的流控规则里的针对来源和授权规则里的流控应用都需要区分调用者的来源才能生效。为了区分调用者的来源,也需要进行二次开发。
首先创建一个实现Sentinel的RequestOriginParser接口的实现类:
|
|
启动项目,直接访问http://127.0.0.1:8082/shares/1接口,因为没有指定origin参数,所以直接报错了。添加来源信息后http://127.0.0.1:8082/shares/1?origin=browser,可以正常访问。
对该接口添加授权规则,指定流控应用为browse,授权类型为browser,代表该接口不对浏览器开发,再次访问http://127.0.0.1:8082/shares/1?origin=browser则显示被授权规则拦截了。
对该接口添加流控规则,设置针对来源项为browser,QPS为1,多次访问http://127.0.0.1:8082/shares/1?origin=browser,发现会被限流。设置针对来源项为其他值比如android时,则不会被限流,说明流控规则起到了针对来源的效果。
不管是区分来源还是错误页优化,在底层都是通过Sentinel的CommonFilter过滤器实现的相关功能。在过滤器中对来源信息、抛出的异常等信息进行先行处理。获取到我们自定义的相关处理类并调用相关方法进行的。
文章作者 punk1u
上次更新 2021-01-31