雪崩问题
在微服务中,一个服务往往依赖于其它多个服务,假设服务1出现了故障,用户的请求得不到响应,线程一直阻塞着,这时不依赖服务1的其它服务还可以用,但越来越多的用户过来请求,就会导致更多的线程阻塞,而服务能支持的线程和并发数是有限的,最终就会导致服务器的资源耗尽,导致其它服务都不可用,最后会导致服务1也不可用,依赖于服务1的其它服务也随之逐渐都不可用,形成一种类似"雪崩"的状态。
解决雪崩
有四种解决方案:超时处理,线程隔离,降级熔断,限流
前三种为补救 措施,而第四种限流为预防措施
什么是Sentinel
在SpringCloud当中支持多种服务保护技术:
早期比较流行的是Hystrix框架,但目前国内实用最广泛的还是阿里的Sentinel框架,这里我们做下对比:
Sentinel | Hystrix | |
---|---|---|
隔离策略 | 信号量隔离 | 线程池隔离/信号量隔离 |
熔断降级策略 | 基于慢调用比例或异常比例 | 基于失败比率 |
实时指标实现 | 滑动窗口 | 滑动窗口(基于 RxJava) |
规则配置 | 支持多种数据源 | 支持多种数据源 |
扩展性 | 多个扩展点 | 插件的形式 |
基于注解的支持 | 支持 | 支持 |
限流 | 基于 QPS,支持基于调用关系的限流 | 有限的支持 |
流量整形 | 支持慢启动、匀速排队模式 | 不支持 |
系统自适应保护 | 支持 | 不支持 |
控制台 | 开箱即用,可配置规则、查看秒级监控、机器发现等 | 不完善 |
常见框架的适配 | Servlet、Spring Cloud、Dubbo、gRPC 等 | Servlet、Spring Cloud Netflix |
Sentinel的功能特性
由图可以看出Sentinel的功能广泛,本文就以流量控制,熔断降级为主要内容进行讲述。
流量控制
通俗来讲就是限流的意思啦,那流量控制的原理是什么呢?
答:
- 通过监听应用流量的QPS或并发线程数等指标,当指标达到阈值(指标的临界值)的时候,会对流量进行控 制,使之保障应用的高可用型。
- 简单来说,假设我通过Jmeter测试该服务的最大承受QPS为180,那么超过180的话,服务就会被流量压垮,通过设置阈值类型为QPS且阈值设置为180就起到很好的保护作用。
- 下面看 一张直观的图来感受一下。左边就好比超过180的QPS,右边就限制为180以内了。
熔断降级
流量控制可以在一定程度上保护服务免受过载和崩溃的影响,但并不能解决所有问题,
比如:当一个服务发生故障或异常时,尽管流量控制限制了对该服务的请求,但其他依赖该服务的服务仍然可能受到影响。故障的服务可能会导致其他服务无法正常工作,从而影响整个系统的稳定性。
所以熔断降级就登场啦。
熔断降级的主要过程:
- 根据具体情况,监控服务的错误率、超时率、响应时间等指标
- 当服务的指标达到阈值时,触发熔断机制,熔断指停止对该服务的请求,快速失败并返回错误响应。这样可以减少对故障服务的压力,避免服务持续失败,对系统产生更严重的影响。
- 熔断触发后,启用降级处理。降级处理指将部分请求转发到降级服务,提供基本功能或响应,保证各个服务的整体稳定性。
- 一段时间后,可以尝试重新请求出现故障的服务,检查是否恢复正常,如果失败就继续熔断降级。
Sentinel的规则持久化
sentinel的所有规则都是内存存储,重启后所有规则都会丢失。在生产环境下,我们必须确保这些规则的持久化,避免丢失。那么该如何实现持久化呢?
答: 规则是否能持久化,取决于规则管理模式,sentinel支持三种规则管理模式:
- 原始模式:Sentinel的默认模式,将规则保存在内存,重启服务会丢失。
- pull模式
- push模式
pull模式
pull模式:控制台将配置的规则推送到Sentinel客户端,而客户端会将配置规则保存在本地文件或数据库中。以后会定时去本地文件或数据库中查询,更新本地规则。
push模式
push模式:控制台将配置规则推送到远程配置中心,例如Nacos。Sentinel客户端监听Nacos,获取配置变更的推送消息,完成本地配置更新。
本文使用push模式,将sentinel与nacos结合,nacos写限流配置,同步到sentinel控制台。
那么有人可能会问,sentinel编辑限流配置,会同步到nacos吗?
答: SentinelDashboard默认不支持nacos的持久化,需要修改源码。改写源码有可能会出现一些bug,所以在这里就不多展示了,有兴趣可以去了解一下。这里讲nacos写限流配置,同步到sentinel控制台。
网关流控
看这个图可以看见Sentinel的网关流控支持对Spring Cloud Gateway、Zuul等主流的API Gataway 进行限流。
本案例用的是springGateway。
流控分为网关流控和普通流控。
所谓网关流控就是应用于网关服务的。而普通流控应用于任何一个服务。
区别:
- 网关流控是在服务网关层实施的流量控制机制,用于保护后端服务免受外部请求的影响。而普通流控可以应用于任何一个服务或组件,以限制该服务或组件的访问速率或资源消耗。
- 网关流控通常位于服务架构的入口处,作为服务网关的一部分,拦截外部请求。而普通流控可以在服务的各个层面实施,无论是应用层、业务逻辑层还是数据访问层。
既然有网关流控了,那还要普通流控干什么?
答: 首先网关服务关联的其它服务都可以通过网关流控来进行限流,但是,其它服务之间可能也有相互调用的情况出现,那么它们之间的调用不走网关服务,那么网关流控就没办法咯,这时候就需要普通流控了。
gw-flow为网关流控,flow为普通流控。
网关流控跟普通流控面板是不一样的,下面左图为网关流控面板,右图为普通流控面板。
Sentinel默认流控面板是普通流控面板,那如何开启网关流控面板呢?
答: 只需在网关服务添加JVM启动参数
ini
# 注:通过 Spring Cloud Alibaba Sentinel 自动接入的 API Gateway 整合则无需此参数(指springGateway)
-Dcsp.sentinel.app.type=1
实现网关流控
1. 依赖配置
xml
<!-- Sentinel流量控制、熔断降级 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
</dependency>
<!-- Sentinel规则持久化至Nacos配置 -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
2. 网关配置
yaml
spring:
cloud:
nacos:
discovery:
server-addr: http://192.168.88.130:8848
config:
server-addr: http://192.168.88.130:8848
file-extension: yaml
gateway:
discovery:
locator:
enabled: true # 启用服务发现
#下面这个表示服务实例的标识将被转换为小写。
lower-case-service-id: true
routes:
- id: 认证中心
uri: lb://whisper-auth
predicates:
- Path=/whisper-auth/**
filters:
- StripPrefix=1
- id: 系统服务
uri: lb://whisper-system
predicates:
- Path=/whisper-system/**
sentinel:
enabled: true # sentinel开关
eager: true
transport:
dashboard: ${sentinel.dashboard} # sentinel控制台地址
port: 8719
datasource:
# 网关限流规则,gw-flow为key,随便定义
gw-flow:
nacos:
server-addr: ${spring.cloud.nacos.discovery.server-addr}
dataId: ${spring.application.name}-gw-flow-rules
groupId: SENTINEL_GROUP
rule-type: gw-flow #gw-flow为网关流控,flow为普通流控
# 自定义API分组,gw-api-group为key,随便定义
gw-api-group:
nacos:
server-addr: ${spring.cloud.nacos.discovery.server-addr}
dataId: ${spring.application.name}-gw-api-group-rules
groupId: SENTINEL_GROUP
rule-type: gw-api-group
这里说一下: Sentinel 网关流控可以选择路由ID还是API分组,本文使用API分组,因为API分组可以更好地对多个 API 接口进行统一管理,比如多个API都用的同一个QPS阈值要求的话,就可以用API分组。只需在该分组配置一次规则。
具体用哪个按需求来选择。
3. 看自定义API分组规则
- "matchStrategy": 0代表精确匹配
- "pattern": 匹配串
- API名称
- 看一个图对比一下就知道了
4. 看网关流控规则
- 假设系统服务的获取用户分页列表只能承受的QPS为5
- resource:分组名
- resourceMode:1代表自定义分组
- count:阈值
- grade:1代表QPS
- 对比一下这个图就知道了,注意一下那个流控方式跟间隔以及Burst Size默认就是下图这个值。
具体其它规则查看:
5. Jmeter测试
假设系统服务的获取用户分页列表只能承受的QPS为5
设置线程数为10,模拟一秒发起十次请求。
可以看见49分16秒QPS应该是6,拒绝了5,实现限流了。
这个错误信息是自定义的,接下来我们来看看如何自定义异常。
6. 网关流控自定义异常 Sentinel限流的默认异常响应如下
css
{"code":429,"message":"Blocked by Sentinel: ParamFlowException"}
总有
java
/**
* 自定义网关流控异常
*
* @author haoxr
* @date 2022/7/24
*/
@Configuration
public class SentinelConfiguration {
//@PostConstruct 注解可以用来在 Spring Bean 初始化之后执行特定的初始化操作
//作用大概是重新定义限流之后的异常改为中文
@PostConstruct //使用该实例之前,相关的初始化工作已经完成
private void initBlockHandler() {
BlockRequestHandler blockRequestHandler = (exchange, t) ->
ServerResponse.status(HttpStatus.TOO_MANY_REQUESTS)
.contentType(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromValue(ResultCode.FLOW_LIMITING.toString())
);
GatewayCallbackManager.setBlockHandler(blockRequestHandler);
}
}
- GatewayCallbackManager 是一个类,它是 Spring Cloud Gateway 中的一个回调管理器。在 Spring Cloud Gateway 中,回调管理器用于管理全局的回调处理器,例如限流处理器、降级处理器、鉴权处理器等。
- 使用 GatewayCallbackManager.setBlockHandler() 方法设置了一个限流处理器(BlockRequestHandler)作为全局的回调处理器。当发生限流时,就会调用限流处理器。
Sentinel结合Feign实现熔断降级
Sentinel提供了三种熔断策略:
- 慢调用比例 : 请求响应时间大于设置的RT(即最大的响应时间)则统计为慢调用。触发此熔断策略的条件需要满足两个条件,一是单位统计时长(statIntervalMs)内请求数大于设置的最小请求数,二是慢调用的比例大于阈值,接下来在熔断时长的范围内请求会自动的被熔断。过了熔断时长后,熔断器进入探测恢复状态(HALF-OPEN状态),若接下来的一个请求响应时间小于设置的慢调用RT则结束熔断,若大于设置的慢调用RT则会再次被熔断。
- 异常比例:当单位统计时长请求数大于设置的最小请求数,并且异常的比例大于阈值,则接下来的熔断时长内请求会被自动熔断。
- 异常数:当单位统计时长内的异常数目超过阈值之后会自动进行熔断。
实现熔断降级
先看一张图
Feign客户端远程调用目标服务失败后,就会走降级逻辑。Feign本身能够实现降级功能,因为内部集成了Hystrix
yaml
# 2. feign配置
feign:
# 微服务保护组件 熔断器
hystrix:
enabled: true
开启后便能有降级功能,hystrix也有熔断不过要做其它步骤,通过前面对比hystrix和Sentinel,果断选择后者,所以下面讲述通过Sentinel集合Fegin实现熔断降级。
1、服务器配置
只给出重要部分,其它跟流控配置差不多
yaml
sentinel:
enabled: true
eager: true # 取消控制台懒加载,项目启动即连接Sentinel
transport:
client-ip: localhost #默认就是localhost
dashboard: localhost:8090
datasource:
# 降级规则
degrade:
nacos:
server-addr: ${spring.cloud.nacos.discovery.server-addr}
dataId: ${spring.application.name}-degrade-rules
groupId: SENTINEL_GROUP
data-type: json
rule-type: degrade
2、依赖配置
xml
<!-- Sentinel流量控制、熔断降级 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!-- Sentinel规则持久化至Nacos配置 -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
3、看熔断降级规则
- resource: 表示需要进行熔断降级的资源,即接口的唯一标识符,GET:http://youlai-admin/api.admin/v1/users/username/{username}是一个feign接口。
- grade: 表示熔断降级的策略等级,0:平均响应时间,1:异常比例 ,2:异常数,3:最大并发数
- count: 表示触发熔断的异常数阈值
- timeWindow:熔断时长,单位为 s
对比一下这个图就明白了,最小请求数和统计时长的值都是用的默认的。
具体规则看这里
4、测试
为了展示效果,主动弄了个异常。因为设置的异常数阈值为1个,所以不需要Jmeter来测试,凭借我个人的手速来实现测试 ┗|`O′|┛ 嗷~~
通过上图可以看见11秒的时候通过了1个请求,其它4秒都是0通过,因为熔断时长为5s,可以看控制台的信息
很明显看到,当发生一个异常后,5s内的请求都走了降级处理。这就是熔断降级功能。
关于只是Sentinel实现的熔断降级 :只需使用 @SentinelResource注解方可实现,这里就不展示啦。
拓展
QPS
Queries Per Second, 每秒查询数,也叫每秒请求数,即是每秒能够响应的请求次数,注意这里的查询是指用户发出请求到服务器做出响应成功的次数,简单理解可以认为查询=请求request。
TPS
Transactions Per Second ,每秒处理的事务数,指系统在每秒钟内能够成功处理的事务数量。
成功处理:每秒处理的事务数是指在特定的时间间隔内,系统成功执行的事务数量。只有在事务完全执行并返回结果后才算作成功处理。
RT
Response Time,响应时间,指系统或接口处理请求所需要的时间,称之为响应时间。它表示从客户端发起请求到服务端接受到请求并响应所有数据的时间差。一般取平均响应时间
并发数
同时处理的请求数或者事务数量
计算公式
QPS=并发数/RT