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

目录

    • [一、Sentinel 介绍](#一、Sentinel 介绍)
      • [1.1 什么是 Sentinel](#1.1 什么是 Sentinel)
      • [1.2 Sentinel 特性](#1.2 Sentinel 特性)
      • [1.3 限流、降级与熔断的区别](#1.3 限流、降级与熔断的区别)
    • 二、实战演示
      • [2.1 下载启动 Sentinel 控制台](#2.1 下载启动 Sentinel 控制台)
      • [2.2 后端微服务接入 Sentinel 控制台](#2.2 后端微服务接入 Sentinel 控制台)
        • [2.2.1 引入 Sentinel 依赖](#2.2.1 引入 Sentinel 依赖)
        • [2.2.2 添加 Sentinel 连接配置](#2.2.2 添加 Sentinel 连接配置)
      • [2.3 使用 Sentinel 进行流控(含限流)](#2.3 使用 Sentinel 进行流控(含限流))
        • [2.3.1 对接口添加 Sentinel 资源标记](#2.3.1 对接口添加 Sentinel 资源标记)
        • [2.3.2 Sentinel 的流控模式](#2.3.2 Sentinel 的流控模式)
        • [2.3.3 Sentinel 的流控效果](#2.3.3 Sentinel 的流控效果)
        • [2.3.4 直接流控演示](#2.3.4 直接流控演示)
        • [2.3.5 关联流控演示](#2.3.5 关联流控演示)
        • [2.3.6 根据调用源对接口限流](#2.3.6 根据调用源对接口限流)
          • [1. 给请求打标](#1. 给请求打标)
          • [2. 解析请求源](#2. 解析请求源)
          • [3. 下发限流规则](#3. 下发限流规则)
      • [2.4 使用 Sentinel 实现降级、熔断](#2.4 使用 Sentinel 实现降级、熔断)
        • [2.4.1 Sentinel 中的熔断策略](#2.4.1 Sentinel 中的熔断策略)
        • [2.4.2 实现降级、熔断](#2.4.2 实现降级、熔断)
          • [1. 核心思路](#1. 核心思路)
          • [2. 降级实现](#2. 降级实现)
          • [3. 熔断策略](#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包:

shell 复制代码
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 的依赖:

xml 复制代码
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
2.2.2 添加 Sentinel 连接配置

如果要想将后端微服务对接到 Sentinel 控制台,除了要添加 Sentinel 依赖以外,还需要在配置文件中添加上 Sentinel 控制台相关的配置信息,主要是为了配置Sentinel 控制台的访问地址,此处配置的本地访问端口号为8888。

yaml 复制代码
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个获取商品的接口为例演示下用法。

java 复制代码
	@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 属性绑定降级方法的方法名即可。

java 复制代码
	@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。

java 复制代码
@Configuration
public class SentinelInterceptor implements RequestInterceptor {

    @Override
    public void apply(RequestTemplate template) {
        template.header("SentinelSource", "coin-customer-src");
    }
}
2. 解析请求源

实现 Sentinel 提供的 RequestOriginParser 接口,获取Request中的请求源标记。

java 复制代码
@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 属性绑定我们实现了降级逻辑的降级方法。

java 复制代码
	@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 接口,可以看到确实是走了降级逻辑的。

相关推荐
fanly111 天前
Surging AI Agent 完整产品介绍
微服务·microservice
吃饱了得干活3 天前
Spring Cloud Gateway 微服务网关:路由、断言、过滤器
java·spring cloud
蝎子莱莱爱打怪8 天前
XZLL-IM干货系列 04|Netty 长连接实战:Pipeline 怎么排、心跳怎么跳、连接怎么管
后端·微服务·面试
SamDeepThinking9 天前
Java微服务练习方式
java·后端·微服务
米丘12 天前
微前端之 Web Components 完全指南
微服务·html
霸道流氓气质14 天前
领域驱动设计(DDD)在 Spring Boot 微服务中的实践指南
运维·spring boot·微服务
慧一居士14 天前
Feign的GET请求如何传递对象参数?
java·spring cloud
我登哥MVP14 天前
SpringCloud Alibaba 核心组件解析:服务链路追踪
java·spring boot·后端·spring·spring cloud·java-ee·maven
慧一居士15 天前
SpringCloud 微服务Feigin 用的完整调用端和被调用的示例
java·spring cloud
霸道流氓气质15 天前
Spring Boot 微服务性能优化完全指南
spring boot·微服务·性能优化