【SpringCloud】使用 Spring Cloud Alibaba 之 Sentinel 实现微服务的限流、降级、熔断

时间:2024-02-26 10:26:32

目录

    • 一、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 控制台;

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个步骤:

  1. 服务消费者在下发请求时,对请求打标,表明请求来源。在微服务内使用拦截器,对所有的请求加上统一的header即可;
  2. 服务提供者在处理请求是需要处理Request,识别Request 来源。实现 Sentinel 提供的 RequestOriginParser 接口,获取Request中的请求源标记;
  3. 在下发限流规则时需要指定请求源服务注册发现时上报的服务名,并配置其它的限流规则;

下面我们来一一实现。

1. 给请求打标

因为我微服务间调用使用的是 Feign,所以这里我实现 FeignRequestInterceptor 接口完成一个拦截器,为所有的请求统一加上 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种熔断策略指标:慢调用比例,异常比例,异常数。

  1. 慢调用比例:该场景下会统计每个调用的RT(Response Time),是否会超过设定的阈值,在统计的时间窗口内如果慢调用的比例超过了阈值,则会进行熔断;
  2. 异常比例:选择了异常比例策略,Sentinel 会统计每个接口调用发生异常的情况,如果,如果调用发生异常的情况占总调用的比例超过设定阈值,就会触发熔断;
  3. 异常数:就是会计算当前时间窗口内所有接口调用的异常情况,如果调用失败次数超过了设定的阈值,就会触发熔断;
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 接口,可以看到确实是走了降级逻辑的。

在这里插入图片描述