使用Sentinel实现流控和服务降级的代码示例
作者:Supersist
伴随着微服务的兴起,服务间的调用的可靠性也越发重要了起来,在下面的这种服务调用关系中:
1.服务调用关系图
A服务调用服务C,B服务调用服务D,服务C/D调用服务E,而服务会进行数据库读写,如果服务E在服务C/D的频繁调用下不堪重负,使得服务E宕掉了,则将导致整个服务集群不可用,这种现象简称为服务雪崩,因此为了避免这种情况,就需要额外的技术保证服务的可用性。如常用的hystrix
和sentinel
就是用来处理这种情况的。前者是 Spring Cloud Netflix 常使用的组件,而后者是 Spring Cloud Alibaba 经常使用的组件。本文以介绍 sentinel
为主。
本文主要有以下内容:
Sentinel
组件的简单介绍Sentinel
流控规则Sentinel
服务降级方案
什么是Sentinel
随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 是面向分布式、多语言异构化服务架构的流量治理组件,主要以流量为切入点,从流量路由、流量控制、流量整形、熔断降级、系统自适应过载保护、热点流量防护等多个维度来帮助开发者保障微服务的稳定性。
服务雪崩现象的发生,一般有两种原因,一种是外部的大量请求调用,如在上面的例子中服务C/D频繁调用服务E,导致服务E不堪重负,另外一种是自身的原因,如服务E内部发生了不可预知的错误,如数数据库读写效率过低,亦或者自身业务逻辑代码抛出了异常等。
为了解决这两种情况,Sentinel
提供了针对外部的流量整形和针对本身的服务异常服务容错治理。
针对外部原因:提供了三种流控规则和三种流控效果来进行服务限流。
- 流控规则
- 直接流控
- 关联流控
- 链路流控
- 流控效果
- 快速失败
- Warm Up
- 排队等待
针对内部原因:提供了服务降级方案和服务熔断的方式来保证服务的稳定性。
- 服务降级:针对单次服务调用,如服务C调用服务E时,服务E的方法出现了异常导致无法完成正常的处理逻辑,如响应超时、服务异常,此时我们给服务C提供一个默认的返回值,保证此次调用得到处理。
- 服务熔断:指服务的异常调用占到一定的数量或者比例,达到了我们的判定条件,如在一个统计的时间窗口内,服务异常比例占比到达70%,触发了服务熔断,即所有的后续请求直接进入降级逻辑,不再调用目标访问方法。
对外部流量的控制
首先搭建sentinel
的运行环境:
在 Sentinel官网页面下载可执行的jar文件,版本sentinel-dashboard-1.8.2.jar
。下载完成后通过命令 java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.8.2.jar
启动 Sentinel
。通过 127.0.0.1:8080
访问 sentinel
控制台,默认的账户密码均为sentinel
接着在项目中集成sentinel
在pom.xml
添加下列依赖
<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency>
在application.yml
中添加如下配置
spring: cloud: sentinel: transport: # sentinel api端口,默认8719 port: 8719 # dashboard地址 dashboard: localhost:8080
在ConsumerController
添加如下代码
@GetMapping("sentinel") @SentinelResource( value = "sentinel", blockHandler = "consumerSentinelResource_Blocked", fallback = "consumerSentinelResource_fallback") public String consumerSentinelResource() { // if (Math.random() > 0.6 ){ // System.out.println("执行fallback降级逻辑"); // throw new RuntimeException("RuntimeException"); // } return "正常返回值"; } public String consumerSentinelResource_Blocked(BlockException blockException){ System.out.println("执行BlockException降级逻辑"); return new Date().toLocaleString() + "BlockException: 服务被降级"; } public String consumerSentinelResource_fallback(){ return new Date().toLocaleString() + "通用异常解决方法: 服务被降级"; }
@SentinelResource
注解表明这是一个Sentinel
资源。
value
属性:资源名称必须填写。blockHandler
:处理因触发流控规则的请求抛出的BlockException
,执行的的降级方法
流控规则
启动 sentinel
并登录控制台后,侧边栏初始状态下并没有我们的服务,这时需要我们访问一下服务,随便调用一个服务接口,侧边栏就会出现我们注册的服务。
2.sentinel控制台截图
点开我们的服务,在侧边栏有个流控规则按钮,点击流控规则,在页面的右上角有一个新增流控规则按钮,点击此按钮,就可以新增流控规则。
直接流控
直接流控是我们常用的流控,一般情况下直接添加即可。按照下图添加流控规则:
3.直接流控截图
规则说明:在每秒请求数超过1个时,就触发流控规则,即执行consumerSentinelResource_Blocked()
方法的逻辑,通过postman
调试接口即可得到下图的结果:
4.触发直接流控规则
关联流控
如果两个资源之间存在竞争关系,如共享一个线程池或者数据库连接池,这时候就可以使用关联流控,此时在低优先级的资源上设置流控规则,进而使得高优先级的资源获得竞争优势,
例如:资源A和资源B存在竞争关系,此时在资源A上设置流控规则和判断阈值,此时当高优先级的请求数量达到判断阈值时,就会对低优先级的资源进行流控,
在ConsumerController
添加如下代码:
@SentinelResource(value = "condition") @GetMapping("condition") public String condition() { return "condition: 我是高优先级的资源"; }
在控制台添加如下规则
5.配置关联流控规则
ps:通过postman
工具怎么都无法出现低优先级服务降级的响应,于是乎我使用了一个python脚本模拟http请求,关联流控规则就起作用了!脚本内容如下
import requests url = "http://127.0.0.1:10001/consumer/condition" url2 = "http://127.0.0.1:10001/consumer/sentinel" while(True): res = requests.get(url=url) res2 = requests.get(url=url2) print("condition response:",res.text) # 返回请求结果 print("sentinel response:",res2.text) # 返回请求结果 print("------------------------------")
响应结果如下图
6.关联流控规则
链路流控
在一个应用中,对同一个资源有多条不同的访问路径时,如果需要对访问路径限流,则可以选用链路流控。如下图:
7.链路流控示意图
query 和 query2 都能访问到 resource, 如果想要对api/query2
进行流控,则此时可以选择链路流控的方式。具体配置如下
8.配置链路流控规则
流控效果
快速失败
在前面的示例中,所有的流控效果都是快速失败,快速失败是sentinel
默认的流控效果,即到达我们的设置的阈值之后就失败。
Warm Up
这种方式实现了预热的效果,即在设定的预热时间内,阈值从起始阈值逐渐提升到我们设定的阈值,起始阈值的值为设定的 单机阈值 / 冷加载因子,sentinel
默认的冷加载因子为 3。
9.Warm Up配置
以上图的配置,则流控效果在 10s 从 100 / 3 = 33 缓慢提升到 100。
内部降级逻辑
降级方案
流控规则和流控效果是作用在外部流量的,而对自身服务而言,当服务调用出现足够多的异常时,为保证服务的稳定性,可采取服务熔断策略,服务熔断是多次异常调用的累积结果。
在上面的示例代码中,我们在使用@SentinelResource
注解时,配置了blockHandler、 fallback
这两个属性, 这两个属性的值都是编写降级逻辑的,但是稍有不同。
blockHandler
只能处理服务抛出的异常时BlockException
的情况,BlockException
是Sentinel
组件配置的流控规则起作用时抛出的异常,即被当请求被流控规则拦截时,抛出的异常。
public String consumerSentinelResource_Blocked(BlockException blockException){ String s = new Date().toLocaleString() + "BlockException: 服务被降级"; return s; }
在编写blockHandler
的方法时,需要在参数列表的最后添加一个BlockException
类型的参数,否则降级逻辑不会起作用。
fallback
使用场景是当调用的方法抛出了除BlockException
类型的其他运行时异常,如代码中分子为 0 抛出的java.lang.ArithmeticException: / by zero
。
fallback
适用于编写通用的降级逻辑,fallback
方法的编写不需要添加额外参数,和 controller 中的方法形参保持一致即可。
熔断策略
在sentinel
中,定义了三种熔断策略:
异常比例
在统计时长指定的时间内,异常调用比例超过设置的比例阈值,且请求数满足设置的最小请求数,则进行服务熔断,直接进入降级逻辑。如果请求数没有满足设置的最小请求数,则即使高于这个比例也不会熔断。如最小请求数是 10,比例为 0.5,即使前 9 次都失败了,也不会熔断。
异常数
在统计时长指定的时间内,异常调用比例大于设置的请求数,且请求数满足设置的最小请求数,则进行服务熔断,直接进入降级逻辑。这里大于指的是加入设置的异常数是3,则熔断发生在第四次失败调用之后。
慢调用比例
在统计时间窗口内,请求响应的时间大于了设置的 最大RT 时间的请求数所占全部请求数的比例超过了设置的比例阈值,且总请求数大于等于最小请求数,则触发熔断规则。
最大RT是判断慢请求的条件,不是触发熔断的条件。
熔断状态的转换
Sentinel
的熔断器有三个状态:关闭,全开,半开,在没有达到触发熔断的条件时,熔断器处于关闭状态,当达到熔断开始的条件时,熔断器出处于全开状态,当熔断时长结束时,熔断器不会立马关闭,而是处于半开状态,此时,下一个请求的状态决定了是关闭还是全开,如果下一个请求请求成功,则关闭熔断器,退出熔断状态,如果失败,则立马进入熔断状态,不需要再次满足设置的条件。
10.熔断状态转换示意图
需要说明的是:开源的Sentinel
组件的配置的规则并没有被持久化,因此当我们重启应用或者重启sentinel
组件,都需要重新配置相关规则。
以上就是使用Sentinel实现流控和服务降级的代码示例的详细内容,更多关于Sentinel 流控和服务降级的资料请关注脚本之家其它相关文章!