💥 该系列属于【SpringBoot基础】专栏,如您需查看其他SpringBoot相关文章,请您点击左边的连接
目录
一、引言
1. 什么是雪崩问题
微服务调用链路中的某个服务故障,引起整个链路中的所有微服务都不可用,这就是级联失败问题,或者叫雪崩问题。
2. 雪崩问题产生的原因
- 微服务相互调用,服务提供者出现故障或阻塞。
- 服务调用者没有做好异常处理,导致自身故障。
- 调用链中的所有服务级联失败,导致整个集群故障。
3. 解决思路
- 尽量避免服务出现故障或阻塞,保证代码的健壮性,保证网络畅通,能应对较高的并发请求;
二、微服务保护
1. 服务保护方案
(1)请求限流
服务故障最重要原因,就是并发太高!解决了这个问题,就能避免大部分故障。当然,接口的并发不是一直很高,而是突发的。因此请求限流,就是限制或控制接口访问的并发流量,避免服务因流量激增而出现故障。
请求限流往往会有一个限流器,数量高低起伏的并发请求曲线,经过限流器就变的非常平稳。这就像是水电站的大坝,起到蓄水的作用。
(2)线程隔离
为了避免某个接口故障或压力过大导致整个服务不可用,我们可以限定每个接口可以使用的资源范围,也就是将其"隔离"起来。
(3)服务熔断
线程隔离虽然避免了雪崩问题,但故障服务(商品服务)依然会拖慢购物车服务(服务调用方)的接口响应速度。
所以,我们要做两件事情:
-
编写服务降级逻辑:就是服务调用失败后的处理逻辑,根据业务场景,可以抛出异常,也可以返回友好提示或默认数据。
-
异常统计和熔断:统计服务提供方的异常比例,当比例过高表明该接口会影响到其它服务,应该拒绝调用该接口,而是直接走降级逻辑。
2. Sentinel
Sentinel是阿里巴巴开源的一款服务保护框架,目前已经加入SpringCloudAlibaba中。
(1)介绍和安装
下载地址:Releases · alibaba/Sentinel (github.com)
将jar包放在任意非中文、不包含特殊字符的目录下,重命名为sentinel-dashboard.jar
:
例如放在桌面Desktop下,然后运行如下命令启动控制台:
java -Dserver.port=8090 -Dcsp.sentinel.dashboard.server=localhost:8090 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard.jar
需要输入账号和密码,默认都是:sentinel
登录后,即可看到控制台,默认会监控sentinel-dashboard服务本身:
(2)微服务整合
在cart-service
模块中整合sentinel,连接sentinel-dashboard
控制台,步骤如下:
引入sentinel依赖
<!--sentinel-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
配置控制台
修改application.yaml文件,添加下面内容:
spring:
cloud:
sentinel:
transport:
dashboard: localhost:8090
http-method-specify: true #开启请求方式前缀
我们的SpringMVC接口是按照Restful风格设计,因此购物车的查询、删除、修改等接口全部都是/carts
路径,所以我们可以选择打开Sentinel的请求方式前缀,把请求方式 + 请求路径
作为簇点资源名。把spring.cloud.sentinel.http-method-specify设置为true。
重启,刷新前端购物车页面
访问cart-service
的任意端点
访问查询购物车接口,sentinel的客户端就会将服务访问的信息提交到sentinel-dashboard
控制台。并展示出统计信息:
3. 请求限流
对某个接口的请求数进行限制。
在簇点链路后面点击流控按钮,即可对其做限流配置:
这样就把查询购物车列表这个簇点资源的流量限制在了每秒6个,也就是最大QPS为6.
利用Jemeter做限流测试,我们每秒发出10个请求:
最终监控结果如下:
可以看出GET:/carts
这个接口的通过QPS稳定在6附近,而拒绝的QPS在4附近,符合我们的预期。
4. 线程隔离
每个微服务如果给某个业务(例如查询购物车)过多的线程,那么会导致其他业务(例如业务1)也失灵,这是因为微服务的资源耗尽了,因此必须对各个业务的最大使用线程做一个限制。
模拟商品服务查询很慢:
@ApiOperation("根据id查询商品")
@GetMapping("{id}")
public ItemDTO queryItemById(@PathVariable("id") Long id) throws InterruptedException {
Thread.sleep(500); //耗时500ms
return BeanUtils.copyBean(itemService.getById(id), ItemDTO.class);
}
修改cart-service模块的application.yml文件,开启Feign的sentinel功能:
feign:
sentinel:
enabled: true # 开启feign对sentinel的支持
默认情况下SpringBoot项目的tomcat最大线程数是200,允许的最大连接是8492,单机测试很难打满。所以我们需要配置一下cart-service模块的application.yml文件,修改tomcat连接:
server:
port: 8082
tomcat:
threads:
max: 50 # 允许的最大线程数
accept-count: 50 # 最大排队等待数量
max-connections: 100 # 允许的最大连接
接下来,点击查询商品的FeignClient对应的簇点资源后面的流控按钮:
在弹出的表单中填写下面内容:
这里勾选的是并发线程数限制,也就是说这个查询功能最多使用5个线程,而不是5QPS。如果查询商品的接口每秒处理2个请求,则5个线程的实际QPS在10左右。
我们利用Jemeter测试,每秒发送100个请求:
开启并发查询购物车信息的同时,手动增加和删除购物车商品仍然非常快,因此线程隔离使得不会影响到其它接口。
5. Fallback
fallback可以作为一种后备处理方案,当并发量过大导致请求失败时,可采取fallback的方法
(1)配置文件
在cart-service中添加,将FeignClient作为Sentinel的簇点资源:
feign:
sentinel:
enabled: true # 开启feign对sentinel的支持
(2)配置FeignClient的Fallback
代码:
@Slf4j
public class ItemClientFallbackFactory implements FallbackFactory<ItemClient> {
@Override
public ItemClient create(Throwable cause) {
return new ItemClient() {
@Override
public List<ItemDTO> queryItemByIds(Collection<Long> ids) {
log.error("查询商品失败");
return CollUtils.emptyList();
}
@Override
public void deductStock(List<OrderDetailDTO> items) {
log.error("扣减商品库存失败");
throw new RuntimeException(cause);
}
};
}
}
将ItemClientFallback
注册为一个Bean
:
在hm-api
模块中的ItemClient
接口中使用ItemClientFallbackFactory
:
测试:
当queryItemByIds和deductStock因为并发量过大调用接口失败时,返回一个备选方案,前端收到的均为200状态码,不过查询商品返回的可能是空集合。
6. 服务熔断
查询商品响应时间过高,从而导致查询购物车的响应时间也变的很长。对于商品服务这种不太健康的接口,我们应该停止调用,避免影响到当前服务。也就是将商品查询接口熔断。当商品服务接口恢复正常后,再允许调用。这其实就是断路器的工作模式了。
断路器的工作状态切换有一个状态机来控制:
状态机包括三个状态:
-
closed:关闭状态,断路器放行所有请求,并开始统计异常比例、慢请求比例。超过阈值则切换到open状态
-
open:打开状态,服务调用被熔断,访问被熔断服务的请求会被拒绝,快速失败,直接走降级逻辑。Open状态持续一段时间后会进入half-open状态
-
half-open:半开状态,放行一次请求,根据执行结果来判断接下来的操作。
-
请求成功:则切换到closed状态
-
请求失败:则切换到open状态
-
可以在控制台通过点击簇点后的熔断
按钮来配置熔断策略:
在弹出的表格中这样填写:
这种是按照慢调用比例来做熔断,上述配置的含义是:
-
RT超过200毫秒的请求调用就是慢调用
-
统计最近1000ms内的最少5次请求,如果慢调用比例不低于0.5,则触发熔断
-
熔断持续时长20s