文章目录
一、引言
微服务架构虽然解决了单体应用的诸多问题,但也带来了新的挑战,其中之一就是雪崩问题(cascade failure)。当一个或多个服务出现故障,会引发连锁反应,使得其他依赖这些服务的服务也出现问题,从局部故障变为整体故障,最后就像生活中的雪崩那样,整个系统崩溃。

1. 雪崩问题的产生原因
- 服务依赖链条过长:在微服务架构中,服务a可能依赖于服务b,而服务b又依赖于服务c,当服务c出现问题时,整个链条可能都会受到影响。
- 服务过载:某个服务的突然高负载或流量激增,可能导致下游服务的资源耗尽,从而导致更多的服务失败。
- 资源耗尽:某个服务由于内存泄漏或其他原因,导致资源耗尽(如内存、cpu、数据库连接等),从而影响整个系统的状态。
- 网络问题:网络延迟或断连可能导致服务间通信失败,从而引发雪崩效应。
- bug引起的失败:某个服务中存在未处理的异常,在运行时被触发,导致服务崩溃,依赖该服务的其他服务也因此出现问题。
2. 解决雪崩问题的思路
-
熔断机制(circuit breaker)
通过监控服务的调用状况,当检测到调用失败率过高时,自动断开与问题服务的调用连接,防止故障扩散。应用场景:服务依赖链条过长、网络问题、bug引起的失败
-
限流(rate limiting)
通过限制单个服务的最大请求数来防止因为流量过大而导致的资源耗尽,从而保护服务的可用性。应用场景:服务过载
-
服务降级(fallback)
设置备用方案或降级处理,在服务不可用时返回预设的降级响应。应用场景:服务依赖链条过长、服务过载
-
隔离(bulkhead pattern)
通过将服务实例的资源进行隔离,防止单个服务实例的故障影响到其他服务实例。应用场景:资源耗尽
-
健康检查和监控(health check and monitoring)
实时监控服务的运行状态,通过健康检查及时发现和解决潜在问题。应用场景:资源耗尽、网络问题、bug引起的失败
二、微服务保护
1. 服务保护方案
1.1 请求限流
请求限流是一种常用的保护服务的手段,当系统接收到的请求量超过预设的阈值时,限流器会拒绝或者延迟一些请求,以防止系统过载导致崩溃,是一种预防措施。
请求限流往往会有一个限流器,数量高低起伏的并发请求曲线,经过限流器就变的非常平稳。这就像是水电站的大坝,起到蓄水的作用,可以通过开关控制水流出的大小,让下游水流始终维持在一个平稳的量。

比如我们有一个订单服务,恰逢双十一购物节,订单服务在促销活动期间会受到大量用户的访问,导致系统负载急剧增加,甚至出现服务崩溃的情况。
而如果我们使用限流器限制每秒钟最多只能处理1000个请求,如果请求量超过这个阈值,多余的请求将被拒绝或者延后处理,这样我们可以有效防止因短时间内大量请求涌入而导致的系统过载和崩溃。
1.2 线程隔离
线程隔离是一种将不同的任务隔离运行在不同的线程池中的方法,防止某一任务的故障蔓延,影响其他任务的正常执行,是一种补救措施。
当一个业务接口响应时间长,而且并发高时,就可能耗尽服务器的线程资源,导致服务内的其它接口受到影响。所以我们必须把这种影响降低,或者缩减影响的范围。线程隔离正是解决这个问题的好办法。
线程隔离的思想来自轮船的舱壁模式:
就像一艘轮船被划分为若干个独立的空间(分隔开的水密隔舱),当船体破损时,只会导致损坏的部分隔舱进水,而其他隔舱由于隔离,并不会进水,以确保即使一个隔舱进水,其他隔舱不会进水,从而防止整艘船的沉没。泰坦尼克号沉没的主要原因之一,就是它的舱壁有一个设计上的失败,水可以通过舱壁顶部上的甲板注入,淹没整个船体。
同样,在微服务架构中,我们通过隔离不同的服务及其资源,防止一个服务出现问题时影响到其他服务。
于此类似,为了避免某个接口故障或压力过大导致整个服务不可用,我们可以限定每个业务能使用的线程数,避免耗尽整个tomcat的资源,因此也叫线程隔离。

如图所示,我们给查询购物车业务限定可用线程数量上限为20,这样即便查询购物车的请求因为查询商品服务而出现故障,也不会导致服务器的线程资源被耗尽,不会影响到其它接口。
1.3 服务熔断
服务熔断是一种保护机制,当某个服务的故障率超过预设阈值时,自动断开对该服务的调用,避免故障影响到更多的服务,是一种补救措施。
假如一个商品服务(故障服务)在一段时间内连续超过一定数量的失败请求,我们可以触发熔断,避免购物车服务(服务调用方)频繁调用故障的商品服务导致购物车服务也故障。
所以,我们要做两件事情:
- 编写服务降级逻辑:就是服务调用失败后的处理逻辑,根据业务场景,可以抛出异常,也可以返回友好提示或默认数据。
- 异常统计和熔断:统计服务提供方的异常比例,当比例过高表明该接口会影响到其它服务,应该拒绝调用该接口,而是直接走降级逻辑。
2. sentinel
sentinel和hystrix对比:
| sentinel | hystrix | |
|---|---|---|
| 隔离策略 | 信号量隔离 | 线程池隔离/信号量隔离 |
| 熔断降级策略 | 基于响应时间或失败比率 | 基于失败比率 |
| 实时指标实现 | 滑动窗口 | 滑动窗口(基于 rxjava) |
| 规则配置 | 支持多种数据源 | 支持多种数据源 |
| 扩展性 | 多个扩展点 | 插件的形式 |
| 基于注解的支持 | 支持 | 支持 |
| 限流 | 基于 qps,支持基于调用关系的限流 | 有限的支持 |
| 流量整形 | 支持慢启动、匀速器模式 | 不支持 |
| 系统负载保护 | 支持 | 不支持 |
| 控制台 | 开箱即用,可配置规则、查看秒级监控、机器发现等 | 不完善 |
| 常见框架的适配 | servlet、spring cloud、dubbo、grpc 等 | servlet、spring cloud netflix |
sentinel 的使用可以分为两个部分:
- 核心库(jar包):不依赖任何框架/库,能够运行于 java 8 及以上的版本的运行时环境,同时对 dubbo / spring cloud 等框架也有较好的支持。在项目中引入依赖即可实现服务限流、隔离、熔断等功能。
- 控制台(dashboard):亦称 sentinel 服务器。基于 spring boot 开发,打包后可以直接运行,不需要额外的 tomcat 等应用容器,dashboard 主要负责管理推送规则、监控、管理机器信息等。
为了方便监控微服务,我们先把sentinel的控制台搭建出来。
2.1 安装
- 下载jar包
下载地址:release v1.8.7 · alibaba/sentinel (github.com)

- 运行
然后在命令行运行如下命令(端口如果冲突可以换其他端口):
java -dserver.port=8090 -dcsp.sentinel.dashboard.server=localhost:8090 -dproject.name=sentinel-dashboard -jar sentinel-dashboard.jar
其它启动时可配置参数可参考官方文档:
启动配置项 · alibaba/sentinel wiki (github.com)

- 访问
访问http://localhost:8090页面,就可以看到sentinel的控制台了:

输入账号和密码,默认都是:sentinel
登录后,即可看到控制台,默认会监控sentinel-dashboard服务本身:

2.2 微服务整合
-
添加
sentinel依赖<!--sentinel--> <dependency> <groupid>com.alibaba.cloud</groupid> <artifactid>spring-cloud-starter-alibaba-sentinel</artifactid> </dependency>- 添加配置文件
spring: cloud: sentinel: transport: dashboard: localhost:8090 # sentinel控制台地址,用于与控制台链接,便于控制台推送规则,监控和管理应用程序的流量控制和熔断策略。-
访问添加
sentinel的服务端口,之后查看sentinel控制台
簇点链路
如果你的项目的api接口风格采用的是restful风格,由于请求路径一般都相同,这会导致点资源名称重复,因此需要修改配置。
修改配置,把请求方式+请求路径作为簇点资源名称:
spring:
cloud:
sentinel:
transport:
dashboard: localhost:8090
http-method-specify: true # 开启请求方式前缀
再次访问该服务的各个接口,之后查看sentinel控制台:

2.2.1 请求限流
在簇点链路后面点击流控按钮,即可对其做限流配置:

分析:
-
资源名
- 这是限流规则所作用的具体资源。图中,资源名为
/carts,表示该限流规则是针对/carts这个资源设置的。资源名一般是服务的 url 地址或业务方法名。
- 这是限流规则所作用的具体资源。图中,资源名为
-
针对来源
- 表示该限流规则针对的是哪个调用来源。
default,表示不限具体的调用来源,也即对所有调用该资源的请求都适用此规则。
- 表示该限流规则针对的是哪个调用来源。
-
阈值类型
- 这里可以选择限流的类型,通常有 qps(每秒查询次数)或并发线程数。默认的是类型是
qps,表示采用每秒最大请求数来进行限流控制。
- 这里可以选择限流的类型,通常有 qps(每秒查询次数)或并发线程数。默认的是类型是
-
单机阈值
- 设置每秒最多允许的请求数(或并发线程数)。如果实际请求数超过该阈值,多余的请求将被拒绝或延迟处理。例如填入1000表示每秒最多允许1000次请求。
-
是否集群
- 表示是否启用集群流控。如果启用,threshold 将按集群模式进行分布式统计和计算。
-
高级选项
- 可以展开设置一些高级参数,比如流控模式、冷启动时间等。
这里我们使用qps限流策略,每秒最多请求数设为6,使用jmeter测试后查看控制台:

可以看到10个请求其中有4个被拒绝,符合我们的qps限流策略。
2.2.2 线程隔离
①openfeign整合sentinel
修改服务调用方的application.yml文件,开启feign的sentinel功能:
feign:
sentinel:
enabled: true # 开启feign对sentinel的支持
重启服务会发现要服务调用方要调用的接口feignclient自动变成了一个簇点资源:

②配置线程隔离

再次使用jemeter进行测试:
很容易就能发现端口的qps完全不同,服务调用方本身的接口的qps为100,而被调用的接口的qps为10(接口每秒处理2个请求,则5个线程的实际qps在10左右)。

2.2.3 服务熔断
在前面,我们使用线程隔离对服务调用方进行隔离,保护了微服务的其它接口。但由于线程隔离导致被调用服务qps较低,导致接口吞吐能力有限,这也导致了下面几个问题:
第一,超出的qps上限的请求就只能抛出异常,从而导致上游服务中想要请求该接口获取资源的接口请求失败。但从业务角度来说,即便上游服务没有请求成功,上游服务也应该展示给用户,用户体验更好。也就是给请求失败设置一个降级处理逻辑。
第二,由于下游接口的延迟较高(模拟的500ms),从而导致上游服务接口的响应时间也变的很长。这样不仅拖慢了上游服务,消耗了上游服务的更多资源,而且用户体验也很差。对于下游服务中这种不太健康的接口,我们应该直接停止调用,直接走降级逻辑,避免影响到当前服务。也就是将下游服务的该接口熔断。
①编写降级逻辑
触发限流或熔断后的请求不一定要直接报错,也可以返回一些默认数据或者友好提示,用户体验会更好。
给feignclient编写失败后的降级逻辑有两种方式:
- 方式一:fallbackclass,无法对远程调用的异常做处理
- 方式二:fallbackfactory,可以对远程调用的异常做处理,我们一般选择这种方式。
这里我们演示方式二的失败降级处理。
步骤一:
在相应的feignclient所在模块定义降级处理类,实现fallbackfactory:
import feign.hystrix.fallbackfactory;
import org.springframework.stereotype.component;
@component
public class itemservicefallbackfactory implements fallbackfactory<itemserviceclient> {
@override
public itemserviceclient create(throwable throwable) {
return new itemserviceclient() {
@override
public item getitembyid(long id) {
// 降级之后返回的默认数据
item defaultitem = new item();
defaultitem.setid(id);
defaultitem.setname("默认item");
defaultitem.setdescription("由于系统负载,这是一个默认的item描述。");
return defaultitem;
}
};
}
}
分析:
itemservicefallbackfactory实现了fallbackfactory接口,而fallbackfactory接口允许我们创建一个 fallback 实例,此实例可以用于处理远程调用失败后的逻辑。create(throwable throwable)方法是fallbackfactory接口中的唯一方法,用于生成一个itemserviceclient的降级处理实现。在调用失败时,该方法会接收到一个throwable对象,表示调用失败的原因。- 在
create方法中,返回了一个匿名内部类实现itemserviceclient接口的降级处理逻辑。当 feign 客户端的远程调用失败时,将调用这个降级处理器的方法。 - 这里是
getitembyid方法的降级实现,当请求远程getitembyid方法失败时,返回一个默认的item对象。
通过这种方式,即使远程服务不可用,上游服务仍然可以返回友好的默认数据,避免直接暴露问题,并提升用户体验。
步骤二:
注册配置,在 feignclient 接口中添加注解,指明降级处理类:
import org.springframework.cloud.openfeign.feignclient;
import org.springframework.web.bind.annotation.getmapping;
import org.springframework.web.bind.annotation.pathvariable;
@feignclient(name = "item-service", fallbackfactory = itemservicefallbackfactory.class)
public interface itemserviceclient {
@getmapping("/items/{id}")
item getitembyid(@pathvariable("id") long id);
}
这样上游服务就不会受到下游服务的影响,延迟会变得很低,这就是给请求失败设置的降级处理逻辑。
②配置熔断规则
sentinel中的断路器不仅可以统计某个接口的慢请求比例,还可以统计异常请求比例。当这些比例超出阈值时,就会熔断该接口,即拦截访问该接口的一切请求,降级处理;当该接口恢复正常时,再放行对于该接口的请求。
断路器的工作状态切换有一个状态机来控制:

状态机包括三个状态:
- closed:关闭状态,断路器放行所有请求,并开始统计异常比例、慢请求比例。超过阈值则切换到open状态
- open:打开状态,服务调用被熔断,访问被熔断服务的请求会被拒绝,快速失败,直接走降级逻辑。open状态持续一段时间后会进入half-open状态
- half-open:半开状态,放行一次请求,根据执行结果来判断接下来的操作。
- 请求成功:则切换到closed状态
- 请求失败:则切换到open状态
接下来配置熔断规则,在上游服务下拉栏中选择下游接口进行配置:


- 熔断策略
- 有三种熔断策略可供选择:
- 慢调用比例:根据调用响应时间来触发熔断。
- 异常比例:根据调用异常比例来触发熔断。
- 异常数:根据调用异常数来触发熔断。
- 有三种熔断策略可供选择:
- 最大 rt
- 最大 rt 是指响应时间,如果请求的响应时间超过这个值即认为是慢调用。
- 例如,设为
500毫秒,表示响应时间超过 500 毫秒的请求被视为慢调用。
- 比例阈值
- 当慢调用的比例达到这个阈值时触发熔断。取值范围在
[0.0, 1.0]之间。 - 例如设为
0.5,表示慢调用比例达到 50% 时触发熔断。
- 当慢调用的比例达到这个阈值时触发熔断。取值范围在
- 熔断时长
- 设置熔断持续的时间,即熔断发生后的休眠期,在此期间请求将会直接失败。
- 例如设为
5秒,表示一旦触发熔断,这个接口将在接下来的 5 秒内直接返回降级处理结果。
- 最小请求数
- 表示触发熔断机制的最小请求数量。如果请求数不足这个数量,即使比例达到阈值也不会触发熔断。
- 默认设置为 5,表示在熔断检测计算的时间窗口内,至少必须有 5 个请求才会进行熔断判断。
- 统计时长
- 表示进行统计的时间窗口大小,以毫秒为单位。
- 默认设置为 1000 毫秒,也即 1 秒。这是统计时间窗口的长度,熔断策略将在这个时间窗口内计算和统计。
比如:
如果配置成下面这样:

实际效果:
如果该接口在 1 秒内超过 50% 的请求响应时间超过 200 毫秒,同时请求数至少有 5 个,则触发熔断。此时,接口将在接下来的 20 秒内直接返回降级处理结果,相当于在这 20 秒内停止向目标接口发送真实请求,从而保护下游服务避免进一步的压力。
2.2.3 规则持久化
①添加依赖:
<!-- sentinel 持久化相关 -->
<dependency>
<groupid>com.alibaba.csp</groupid>
<artifactid>sentinel-datasource-nacos</artifactid>
</dependency>
②修改 bootstrap.yml 配置文件
spring:
application:
name: cart-service
profiles:
active: dev
cloud:
nacos:
server-addr: 192.168.56.101:8848
config:
file-extension: yaml # 文件后缀名
shared-configs: # 共享配置
- dataid: shared-jdbc.yaml # 共享mybatis配置
- dataid: shared-log.yaml # 共享日志配置
- dataid: shared-swagger.yaml # 共享日志配置
# 添加以下内容------------------------------------
sentinel:
datasource:
ds0:
nacos:
server-addr: 192.168.56.101:8848 # nacos 服务地址
dataid: ${spring.application.name}-degrade-rules.json # 数据 id
groupid: default_group # 分组
data-type: json # 数据格式
rule-type: degrade # 规则类型,这里指定为 degrade,表示是熔断降级规则
③在nacos中添加共享配置
[
{
"resource": "get:http://item-service/items",
"count": 0.5,
"grade": 0,
"timewindow": 20,
"minrequestamount": 5,
"statintervalms": 10000,
"slowratiothreshold": 0.5
}
]

字段解释:
resource:- 资源名称,表示熔断规则应用于的具体资源。
count:- 触发熔断的阈值,数值的含义取决于熔断策略的类型(grade)。
- 如果
grade是 0(慢调用比例),则count表示慢调用占比的阈值,即当慢调用比例达到 50% 时触发熔断。 - 如果
grade是 1(异常比例),则count表示异常请求占比的阈值。 - 如果
grade是 2(异常数),count表示触发熔断的异常请求数阈值。
- 如果
- 触发熔断的阈值,数值的含义取决于熔断策略的类型(grade)。
grade:- 熔断策略类型:
0:表示按异常比例触发熔断。1:表示按慢调用比例触发熔断。2:表示按异常数触发熔断。
- 熔断策略类型:
timewindow:熔断持续的时间窗口,单位为秒。表示当熔断条件满足时,该资源将进入熔断状态,并在20秒内直接拒绝请求或执行降级处理。minrequestamount:触发熔断判定的最小请求数。统计时窗口内请求数量需至少达到该值时才会根据熔断策略进行判定。statintervalms:统计时间窗口,单位为毫秒。slowratiothreshold:慢调用比例触发熔断的阈值,这个值跟count是同样用途,用于定义慢调用比例(如果grade是 1)。一般情况下这个值和count值一致。
重新启动服务,测试。请求一下相应的接口,然后查看sentinel 控制台,发现控制台多出来了一个熔断规则:

3 . 总结
通过本篇文章,我们简单地了解了如何使用 alibaba sentinel 进行微服务保护,并且详细讲解了三种主要的服务保护策略——请求限流、线程隔离以及服务熔断。其实sentinel相关的知识内容还有很多很多(太多了!),所以这里就简单讲解一下,提供一些基本的用法,希望对大家有所帮助!
参考文章:
为故障设计微服务架构 - risingstack engineering
微服务之微服务保护(sentinal) - martin8866 - 博客园 (cnblogs.com)
dashboard | sentinel (sentinelguard.io)
微服务架构 | 5.2 基于 sentinel 的服务限流及熔断-阿里云开发者社区 (aliyun.com)
发表评论