目录
- 一、Sentinel 介绍
- 1.1 什么是 Sentinel
- 1.2 Sentinel 特性
- 1.3 限流、降级与熔断的区别
- 二、实战演示
- 2.1 下载启动 Sentinel 控制台
- 2.2 后端微服务接入 Sentinel 控制台
- 2.2.1 引入 Sentinel 依赖
- 2.2.2 添加 Sentinel 连接配置
- 2.3 使用 Sentinel 进行流控(含限流)
- 2.3.1 对接口添加 Sentinel 资源标记
- 2.3.2 Sentinel 的流控模式
- 2.3.3 Sentinel 的流控效果
- 2.3.4 直接流控演示
- 2.3.5 关联流控演示
- 2.3.6 根据调用源对接口限流
- 1. 给请求打标
- 2. 解析请求源
- 3. 下发限流规则
- 2.4 使用 Sentinel 实现降级、熔断
- 2.4.1 Sentinel 中的熔断策略
- 2.4.2 实现降级、熔断
- 1. 核心思路
- 2. 降级实现
- 3. 熔断策略
一、Sentinel 介绍
1.1 什么是 Sentinel
- Sentinel 对开发者的大概印象应该是阿里开源的一个SpringCloud 组件,用来做微服务的限流、降级和熔断。这也是本文从概念和实战上主要的展开点。
- 官方点的话说就是: Sentinel 是面向分布式、多语言异构化服务架构的流量治理组件,主要以流量为切入点,从流量控制、流量路由、熔断降级、系统自适应保护等多个维度来帮助用户保障微服务的稳定性。
1.2 Sentinel 特性
官方总结了 Sentinel 组件的4大特性,概括了 Sentinel 组件提供的能力:
- 丰富的应用场景:阿里巴巴 10 年双十一积累的丰富流量场景,包括秒杀、双十一零点持续洪峰、热点商品探测、预热、消息队列削峰填谷等多样化的场景;
- 易于使用,快速接入:简单易用,开源生态广泛,针对 Dubbo、Spring Cloud、gRPC、Zuul、Reactor、Quarkus 等框架只需要引入适配模块即可快速接入;
- 多样化的流量控制:资源粒度、调用关系、指标类型、控制效果等多维度的流量控制;
- 可视化的监控和规则管理:简单易用的 Sentinel 控制台;
1.3 限流、降级与熔断的区别
- 限流是在流量进入到系统内之前,先进行一个统计计算,如果已经超过设定的阈值,则直接拒绝当前的请求,那么当前流量不会进入服务的内部;
- 降级指的是如果在执行一个请求的调用时,如果发生了异常,那么请求就会继续执行指定的降级逻辑。此时,请求已经进入到了服务内部;
- 熔断是对一个时间窗口内处理的请求的结果进行统计后,如果这批请求的错误率达到了阈值(或者慢接口比例达到了阈值),则会触发一个指定时间段长度的熔断;
服务的限流、降级、熔断这三板斧的结合在很大程度上抑制了服务雪崩的发生。
二、实战演示
2.1 下载启动 Sentinel 控制台
我们到 Sentinel 官方的 Github 上下载 sentinel-dashboard 服务的 Jar 包,Sentinel 服务的限流、熔断等规则的下发就是通过 sentinel-dashboard 来进行的,下载链接。当前的最新版本是1.8.7。
sentinel-dashboard 的 Jar 包下载完毕后,把它放在合适的目录,使用如下命令启动Jar包:
java -Dserver.port=8888 -Dcsp.sentinel.dashboard.server=localhost:8888 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.8.7.jar
然后在浏览器的地址框内输入 http://localhost:8888/
会进入 Sentinel 控制台的登陆界面:
默认的用户名和密码都是 sentinel
,输入后即可进入 Sentinel 控制台主界面:
2.2 后端微服务接入 Sentinel 控制台
2.2.1 引入 Sentinel 依赖
在我们需要使用 Sentinel 组件的后端微服务中先引入 Sentinel 的依赖:
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
2.2.2 添加 Sentinel 连接配置
如果要想将后端微服务对接到 Sentinel 控制台,除了要添加 Sentinel 依赖以外,还需要在配置文件中添加上 Sentinel 控制台相关的配置信息,主要是为了配置Sentinel 控制台的访问地址,此处配置的本地访问端口号为8888。
spring:
cloud:
sentinel:
transport:
# sentinel 的默认端口号为 8719
port: 8719
# dashboard地址
dashboard: localhost:8888
2.3 使用 Sentinel 进行流控(含限流)
2.3.1 对接口添加 Sentinel 资源标记
对于 Sentinel 组件来说,一切的 API 接口都是资源,Sentinel 的职责就是在流量到来时,根据这些 Sentinel 资源的负载情况对流量进行管理。所以,我们需要先对代码中定义的 API 接口打上 Sentinel 资源的标记。Sentinel 是通过 @SentinelResource
注解对 API 接口打标记的。下面以2个获取商品的接口为例演示下用法。
@GetMapping("/getGoods")
@SentinelResource(value = "getGoods")
public CoinGoodsInfo getGoods(@RequestParam("id") Long id){
log.info("Get goods, id={}", id);
return coinGoodsService.getGoodsInfo(id);
}
// 批量获取
@GetMapping("/getBatch")
@SentinelResource(value = "getGoodsInBatch")
public Map<Long, CoinGoodsInfo> getGoodsInBatch(@RequestParam("ids") Collection<Long> ids) {
log.info("getGoodsInBatch: {}", JSON.toJSONString(ids));
return coinGoodsService.getGoodsInfoMap(ids);
}
2.3.2 Sentinel 的流控模式
在使用 Sentinel 演示具体的流控之前,先简单的介绍下 Sentinel 的流控。Sentinel 的进行流量控制时分为3种模式:直接流控,关联流控,链路流控。
- 直接流控:进行流量管理时直接作用于目标 Sentinel 资源,如果当前的访问压力大于预先设定的阈值,直接拦截当前的请求;
- 关联流控:在关联流控中,Sentinel 资源中存在一个优先级的概念,同时他们会共享某个资源,比如都依赖某个接口或者共享一个数据库连接池。当高优先级的 Sentinel 资源的访问流量达到了设定阈值时,会对低优先级的 Sentinel 资源接口进行限流。
- 链路流控:当指定链路上的访问量大于某个阈值时,对当前资源进行限流。
2.3.3 Sentinel 的流控效果
Sentinel 目前提供了 3 种流控效果,分别是:快速失败,Warm-up,排队等待。
- 快速失败:当某个 Sentinel 资源的访问流量超过了设定的阈值, 后面的请求会直接被拦截住;
- Warm-up:该模式下系统承载的流量有一个缓慢拉高的过程,而不是一开始就将系统承载的流量设置为最高可承受的流量。假设我们设置的系统QPS阈值是100,预热时间为5秒,那么 Sentinel 会在 5 秒内逐渐的将限流阈值从33 拉高到 100。因为 Sentinel 内部有一个冷加载因子,取值为3。100/3=33。因此初始的限流阈值为33。
- 排队等待:如果当前的流量已经超过了接口设定的阈值,超出部分的请求会被放到队列中,等到系统的请求量下来,再将请求交给系统处理。同时,注意被放入到队列的请求有一个超时等待时间,如果被放入到队列的请求在队列中滞留的时间过长,该请求会被丢弃。
2.3.4 直接流控演示
首先,我们需要在代码中先指定请求被限流时,要执行的限流逻辑。该限流部分的逻辑指定,需要我们先定义对应的降级方法,然后在 @SentinelResource
注解中的 blockHandler
属性绑定降级方法的方法名即可。
@GetMapping("/getGoods")
@SentinelResource(value = "getGoods", blockHandler = "getGoods_block")
public CoinGoodsInfo getGoods(@RequestParam("id") Long id){
log.info("Get goods, id={}", id);
return coinGoodsService.getGoodsInfo(id);
}
public CoinGoodsInfo getGoods_block(Long id){
log.info("Calling limited, id={}", id);
return null;
}
// 批量获取
@GetMapping("/getBatch")
@SentinelResource(value = "getGoodsInBatch",
fallback = "getGoodsInBatch_fallback",
blockHandler = "getGoodsInBatch_block")
public Map<Long, CoinGoodsInfo> getGoodsInBatch(@RequestParam("ids") Collection<Long> ids) {
log.info("getGoodsInBatch: {}", JSON.toJSONString(ids));
return coinGoodsService.getGoodsInfoMap(ids);
}
如上的 /getGoods
接口,该接口因限流调用失败时,会执行 blockHandler
属性绑定的 getGoods_block
方法,返回 null
。
下面我们在代码中演示下直接流控。直接流控的配置如下图:
资源名:就是我们使用
@SentinelResource
注解时,value 属性指定的值。
阈值类型:选QPS,表示请求的QPS(Query per second),这里我们设置的阈值为1。
流控模式:选“直接”
流控效果:可以选“快速失败”,也可以选择其他选项,“快速失败”简单直接,用postman 等工具测试时可以直接看到效果。
点击“新增”,这条流控规则就新增完毕了。
在 postman 中调用相应的接口,点的快一点,保证1秒内超过1次,可以看到打印的日志,确实是走了限流逻辑的。
2.3.5 关联流控演示
接下来我们还是利用在 2.3.4
中的代码实现。假设2个接口/getGoods
与/getBatch
存在资源竞争关系,然后我们在 Sentinel 控制台中配置关联流控的规则,其具体的配置项如下图:
这里设置流控规则时,设置的资源名为 getGoodsInBatch
,关联到的sentinel资源是getGoods
,设置的 QPS=1。意思就是 Sentinel 资源 getGoods
(/getGoods
接口)的访问QPS为1时,就会触发对 Sentinel 资源getGoodsInBatch
(/getBatch
接口)的限流。
2.3.6 根据调用源对接口限流
背景:这种情况就是,假设有3个微服务A,B,C,微服务A,B 都依赖微服务C,都需要调用微服务C 的接口。而且对于A, B 这2个微服务来说,A 是核心业务,B 是边缘业务。现在我们在使用Sentinel 对微服务 C 中的某接口进行限流时,需要识别这个请求是来自微服务A 还是 B。在业务的高峰期,我们需要对来自微服务B 的请求进行限流。
核心思路主要分为3个步骤:
- 服务消费者在下发请求时,对请求打标,表明请求来源。在微服务内使用拦截器,对所有的请求加上统一的
header
即可; - 服务提供者在处理请求是需要处理Request,识别Request 来源。实现 Sentinel 提供的
RequestOriginParser
接口,获取Request中的请求源标记; - 在下发限流规则时需要指定请求源服务注册发现时上报的服务名,并配置其它的限流规则;
下面我们来一一实现。
1. 给请求打标
因为我微服务间调用使用的是 Feign
,所以这里我实现 Feign
的 RequestInterceptor
接口完成一个拦截器,为所有的请求统一加上 Header。
@Configuration
public class SentinelInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
template.header("SentinelSource", "coin-customer-src");
}
}
2. 解析请求源
实现 Sentinel 提供的 RequestOriginParser
接口,获取Request中的请求源标记。
@Component
public class SentinelOriginParser implements RequestOriginParser {
@Override
public String parseOrigin(HttpServletRequest request) {
return request.getHeader("SentinelSource");
}
}
3. 下发限流规则
下发新的限流规则,针对来源不再是default
变成了 microservice-A
,这意味着当请求流量达到设定阈值时,限流的拦截只对该微服务生效。
2.4 使用 Sentinel 实现降级、熔断
2.4.1 Sentinel 中的熔断策略
Sentinel 中有3种熔断策略指标:慢调用比例,异常比例,异常数。
- 慢调用比例:该场景下会统计每个调用的RT(Response Time),是否会超过设定的阈值,在统计的时间窗口内如果慢调用的比例超过了阈值,则会进行熔断;
- 异常比例:选择了异常比例策略,Sentinel 会统计每个接口调用发生异常的情况,如果,如果调用发生异常的情况占总调用的比例超过设定阈值,就会触发熔断;
- 异常数:就是会计算当前时间窗口内所有接口调用的异常情况,如果调用失败次数超过了设定的阈值,就会触发熔断;
2.4.2 实现降级、熔断
1. 核心思路
因为我们在选定了熔断策略之后,当服务触发了熔断,熔断期间,接口就会走降级策略。所以,首先,需要在代码中实现降级逻辑;其次,需要在 Sentinel 控制台添加熔断策略。
2. 降级实现
接下来我们在2个接口的 @SentinelResource
注解中,使用 fallback
属性绑定我们实现了降级逻辑的降级方法。
@GetMapping("/getGoods")
@SentinelResource(value = "getGoods", fallback="getGoods_fallback", blockHandler = "getGoods_block")
public CoinGoodsInfo getGoods(@RequestParam("id") Long id){
log.info("Get goods, id={}", id);
return coinGoodsService.getGoodsInfo(id);
}
public CoinGoodsInfo getGoods_block(Long id){
log.info("Calling limited, id={}", id);
return null;
}
public CoinGoodsInfo getGoods_fallback(Long id) {
log.info("Calling fallback, id={}", id);
return null;
}
// 批量
@GetMapping("/getBatch")
@SentinelResource(value = "getGoodsInBatch",
fallback = "getGoodsInBatch_fallback",
blockHandler = "getGoodsInBatch_block")
public Map<Long, CoinGoodsInfo> getGoodsInBatch(@RequestParam("ids") Collection<Long> ids) {
log.info("getGoodsInBatch: {}", JSON.toJSONString(ids));
return coinGoodsService.getGoodsInfoMap(ids);
}
public Map<Long, CoinGoodsInfo> getGoodsInBatch_fallback(Collection<Long> ids) {
log.info("Calling fallback, ids={}", ids.toString());
return new HashMap<>();
}
public Map<Long, CoinGoodsInfo> getGoodsInBatch_block(
Collection<Long> ids, BlockException exception) {
log.info("calling is limited, ids: {}, exception: ", ids, exception);
return new HashMap<>();
}
3. 熔断策略
我们在 Sentinel 控制台中添加熔断规则如下,对 Sentinel 资源 getGoods
添加熔断规则,熔断策略选择“异常比例”,设置的比例阈值为 0.4 ,熔断时长为10秒,最小请求数为50,统计时长为10000毫秒。结合起来就是在 10000毫秒的时间窗口内统计所有的请求,当时间窗内的请求数大于50个,且请求失败率大于40%时,会触发一个10秒的熔断。 熔断期间会执行我们代码中定义的降级逻辑。
启动服务,调用/getGoods
接口,可以看到确实是走了降级逻辑的。