微服务限流熔断方案

1、技术方案对比

常见的微服务限流组件有Hystrix 和 Sentinel等 。

Hystrix

官网地址: https://github.com/Netflix/hystrix/

Hystrix 是 Spring Cloud 框架中 Netflix 组件中的一个组件,提供了限流、熔断、降级等功能 。 Hystrix 的关注点在于以隔离和熔断为主的容错机制,超时或被熔断的调用将会快速失败,并可以提供 fallback 机制。不过就在2018年底,Netflix 宣布不再积极开发 Hystrix,该项目将处于维护模式,推荐开发者只用其它仍然活跃的开源项目Resilience4J。

Sentinel

源码地址:https://github.com/alibaba/Sentinel

中文文档地址:https://sentinelguard.io/zh-cn/docs/quick-start.html

Sentinel是阿里面向云原生开发的面向分布式服务架构的轻量级高可用流量控制、熔断降级的开源组件。 主要以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度来帮助用户保护服务的稳定性。

Sentinel

Hystrix

隔离策略

信号量隔离

线程池隔离/信号量隔离

熔断降级策略

基于响应时间或失败比率

基于失败比率

实时指标实现

滑动窗口

滑动窗口(基于 RxJava)

规则配置

支持多种数据源

支持多种数据源

扩展性

多个扩展点

插件的形式

基于注解的支持

支持

支持

限流

基于 QPS,支持基于调用关系的限流

有限的支持

流量整形

支持慢启动、匀速器模式

不支持

系统负载保护

支持

不支持

控制台

开箱即用,可配置规则、查看秒级监控、机器发现等

不完善

常见框架的适配

Servlet、Spring Cloud、Dubbo、gRPC 等

Servlet、Spring Cloud Netflix

本文档主要说明一下 Sentinel 的集成。

说明:Sentinel 存在一个坑,官方提供的源码包中流控规则配置都是基于内存存储的,Sentinel 控制台服务重启后配置会丢失,仅用于简单测试,不能用于生产环境。 Sentinel 支持 ZooKeeper, Apollo, Nacos 等动态数据源的实现,需要使用者修改源码实现配置的持久化和加载,官网没有例子。

官方提供了一个云上版本的控制台。通过这个版本,开发者可以看到一个完整的生产环境的控制台的功能全集。 详情请参考 AHAS Sentinel 控制台文档来参考如何在生产环境中使用控制台。

2、Sentinel 功能和设计理念

2.1 流量控制

什么是流量控制

流量控制在网络传输中是一个常用的概念,它用于调整网络包的发送数据。然而,从系统稳定性角度考虑,在处理请求的速度上,也有非常多的讲究。任意时间到来的请求往往是随机不可控的,而系统的处理能力是有限的。我们需要根据系统的处理能力对流量进行控制。Sentinel 作为一个调配器,可以根据需要把随机的请求调整成合适的形状,如下图所示:

流量控制设计理念

流量控制有以下几个角度:

  • 资源的调用关系,例如资源的调用链路,资源和资源之间的关系;
  • 运行指标,例如 QPS、线程池、系统负载等;
  • 控制的效果,例如直接限流、冷启动、排队等。

Sentinel 的设计理念是让您自由选择控制的角度,并进行灵活组合,从而达到想要的效果。

2.2 熔断降级

什么是熔断降级

除了流量控制以外,及时对调用链路中的不稳定因素进行熔断也是 Sentinel 的使命之一。由于调用关系的复杂性,如果调用链路中的某个资源出现了不稳定,可能会导致请求发生堆积,进而导致级联错误。

微服务熔断降级 微服务熔断限流_微服务

Sentinel 和 Hystrix 的原则是一致的: 当检测到调用链路中某个资源出现不稳定的表现,例如请求响应时间长或异常比例升高的时候,则对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联故障。

熔断降级设计理念

在限制的手段上,Sentinel 和 Hystrix 采取了完全不一样的方法。

Hystrix 通过 线程池隔离 的方式,来对依赖(在 Sentinel 的概念中对应资源)进行了隔离。这样做的好处是资源和资源之间做到了最彻底的隔离。缺点是除了增加了线程切换的成本(过多的线程池导致线程数目过多),还需要预先给各个资源做线程池大小的分配。

Sentinel 对这个问题采取了两种手段:

  • 通过并发线程数进行限制

和资源池隔离的方法不同,Sentinel 通过限制资源并发线程的数量,来减少不稳定资源对其它资源的影响。这样不但没有线程切换的损耗,也不需要您预先分配线程池的大小。当某个资源出现不稳定的情况下,例如响应时间变长,对资源的直接影响就是会造成线程数的逐步堆积。当线程数在特定资源上堆积到一定的数量之后,对该资源的新请求就会被拒绝。堆积的线程完成任务后才开始继续接收请求。

  • 通过响应时间对资源进行降级

除了对并发线程数进行控制以外,Sentinel 还可以通过响应时间来快速降级不稳定的资源。当依赖的资源出现响应时间过长后,所有对该资源的访问都会被直接拒绝,直到过了指定的时间窗口之后才重新恢复。

2.3 系统自适应保护

Sentinel 同时提供系统维度的自适应保护能力。防止雪崩,是系统防护中重要的一环。当系统负载较高的时候,如果还持续让请求进入,可能会导致系统崩溃,无法响应。在集群环境下,网络负载均衡会把本应这台机器承载的流量转发到其它的机器上去。如果这个时候其它的机器也处在一个边缘状态的时候,这个增加的流量就会导致这台机器也崩溃,最后导致整个集群不可用。

针对这个情况,Sentinel 提供了对应的保护机制,让系统的入口流量和系统的负载达到一个平衡,保证系统在能力范围之内处理最多的请求。

3、Sentinel的整合

3.1 Sentinel的使用方式

Sentinel 的使用可以分为两个部分:

  • 核心库(Java 客户端):不依赖任何框架/库,能够运行于 Java 8 及以上的版本的运行时环境,同时对 Dubbo / Spring Cloud 等框架也有较好的支持(见 主流框架适配)。
  • 控制台(Dashboard):Dashboard 主要负责管理推送规则、监控、管理机器信息等。
  • 核心库不依赖 Dashboard,但是结合 Dashboard 可以取得最好的效果。

3.2 Sentinel控制台的运行

下载Sentinel

jar包地址:https://github.com/alibaba/Sentinel/releases/download/1.8.6/sentinel-dashboard-1.8.6.jar

也可以下载下载源码自行编译,在sentinel-dashboard工程下生成sentinel-dashboard.jar

运行Sentinel

java -jar sentinel-dashboard.jar

访问Sentinel

访问地址:http://localhost:8080/,登录账户:sentinel,登录密码:sentinel

微服务熔断降级 微服务熔断限流_java_02

微服务熔断降级 微服务熔断限流_微服务_03

3.3 项目整合Sentinel

引入依赖

Sentinel作为Spring Cloud Alibaba的组件之一,在Spring Cloud项目中集成非常的简单。 maven项目中引入依赖:

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

修改配置文件

在项目的配置文件中引入:

spring:
  cloud:
    sentinel:
      transport:
        dashboard: http://localhost:8080

3.4 Sentinel菜单说明

项目集成Sentinel后,在控制台右侧会展示项目名。

微服务熔断降级 微服务熔断限流_微服务熔断降级_04

实时监控:过一会儿就看不到监控数据了,因为Sentinel 控制台中监控数据聚合后直接存在内存中,未进行持久化,且仅保留最近 5 分钟的监控数据。若需要监控数据持久化的功能,可以自行扩展实现 MetricsRepository 接口 。

微服务熔断降级 微服务熔断限流_微服务熔断降级_05

**簇点链路 **:展示服务中已经访问过的资源路径,可以在操作栏配置流控规则。

微服务熔断降级 微服务熔断限流_限流_06


流控规则:Sentinel 提供了5中方式的规则配置。

微服务熔断降级 微服务熔断限流_java_07

4、Sentinel流控规则

4.1 流控规则

微服务熔断降级 微服务熔断限流_java_08

  • 资源名:一般是我们的请求路径
  • 针对来源:是指来自于哪个应用,default表示所有请求来源
  • 阈值类型:分为QPS(每秒查询率 )或并发线程数
  • 单机阈值:单个节点的QPS或线程数阈值
  • 是否集群:被请求的服务是否集群部署
  • 流控模式:
  • 直接,就是直接对该资源进行控制
  • 关联,关联某一个资源(/app2),被关联的资源/app2达到阈值2,则限制当前资源/test的访问
  • 链路,记录指定链路上的流量
  • 流控效果:(线程数阈值类型不存在此属性)
  • 快速失败 ,是默认的流量控制方式,当QPS超过任意规则的阈值后,新的请求就会被立即拒绝,并抛出FlowException
  • Warm Up,即预热/冷启动方式。当系统长期处于低水位的情况下,当流量突然增加时,直接把系统拉升到高水位可能瞬间把系统压垮。通过"冷启动",让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮。
  • 排队等待,会严格控制请求通过的间隔时间,也即是让请求以均匀的速度通过,对应的是漏桶算法。 注意:匀速排队模式暂时不支持 QPS > 1000 的场景。

4.2 熔断规则

Sentinel 提供以下几种熔断策略:

  1. 慢调用比例 (SLOW_REQUEST_RATIO):选择以慢调用比例作为阈值,需要设置允许的慢调用 RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用。当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断。
  2. 异常比例 (ERROR_RATIO):当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%。
  3. 异常数 (ERROR_COUNT):当单位统计时长内的异常数目超过阈值之后会自动进行熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。

  • 最大RT:慢调用比例时,当请求的响应时间大于RT的值时,就认为是一次慢调用。
  • 比例阈值:慢调用模式下,表示在统计时长内慢调用占总请求数的比例。
    异常比例模式下,表示异常请求占总请求的比例。
  • 异常数:异常数模式下,表示统计时长内,异常请求的阈值。
  • 熔断时长:触发熔断策略后,服务不可用的时长,之后会尝试请求恢复服务。
  • 最小请求数:统计时长内,所有调用的次数大于最小请求数,才会统计。
  • 统计时长:一次统计周期。

4.3热点规则

热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效。

**注意:**热点规则需要使用@SentinelResource(“app”)注解,否则不生效,并且参数必须是7种基本数据类型才会生效。

@GetMapping("/getValue")
// value为热点规则的资源名,如果不指定value值的时候,是通过反射拿的方法名
// com.module.system.controller.SysConfigController:getValue(java.lang.String)
@SentinelResource(value = "get_value")
public Result<String> getValue(@RequestParam(value = "key") String key) {
    return Result.okResult(sysConfigService.getValueByKey(key));
}

微服务熔断降级 微服务熔断限流_java_09

  • 参数索引:从0开始,表示代码中热点参数的位置。
  • 单机阈值:请求的QPS。
  • 统计窗口时长:统计周期。
  • 参数例外项:在此处可以添加一条例外项目,针对参数的值做不一样的限流处理。如图中表示当参数值=”dingtalk_worknotice_app_secret“时,在1s内,请求数超过100,则限流当前接口。

4.4系统规则

Sentinel 系统自适应限流从整体维度对应用入口流量进行控制,结合应用的 Load、总体平均 RT、并发线程数、入口 QPS 、CPU 使用率等几个维度的监控指标,通过自适应的流控策略,让系统的入口流量和系统的负载达到一个平衡,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。

微服务熔断降级 微服务熔断限流_java_10

系统规则支持以下的模式:

  • Load 自适应(仅对 Linux/Unix-like 机器生效):系统的 load1 作为启发指标,进行自适应系统保护。当系统 load1 超过设定的启发值,且系统当前的并发线程数超过估算的系统容量时才会触发系统保护(BBR 阶段)。系统容量由系统的 maxQps * minRt 估算得出。设定参考值一般是 CPU cores * 2.5。
  • RT:当单台机器上所有入口流量的平均响应时间达到阈值即触发系统保护,单位是毫秒。
  • 线程数:当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护。
  • 入口 QPS:当单台机器上所有入口流量的 QPS 达到阈值即触发系统保护。
  • CPU 使用率:当系统 CPU 使用率超过阈值即触发系统保护(取值范围 0.0-1.0),比较灵敏。

4.5授权规则

很多时候,我们需要根据调用来源来判断该次请求是否允许放行,这时候可以使用 Sentinel 的来源访问控制(黑白名单控制)的功能。来源访问控制根据资源的请求来源(origin)限制资源是否通过,若配置白名单则只有请求来源位于白名单内时才可通过;若配置黑名单则请求来源位于黑名单时不通过,其余的请求通过。

微服务熔断降级 微服务熔断限流_微服务熔断降级_11

  • 资源名:就是受保护的资源,例如/sys/oss/file/list
  • 流控应用:是来源者的名单,
  • 如果是勾选白名单,则名单中的来源被许可访问。
  • 如果是勾选黑名单,则名单中的来源被禁止访问。

用我们目前的服务来说,/sys/oss/file/list 请求来源于gateway服务,如果配置白名单的话就要填写网关的服务名:gateway

如何获取origin

Sentinel是通过RequestOriginParser这个接口的parseOrigin来获取请求的来源的。

public interface RequestOriginParser {
    /**
    * 从请求request对象中获取origin,获取方式自定义
    */
    String parseOrigin(HttpServletRequest request);
}

这个方法的作用就是从request对象中,获取请求者的origin值并返回。默认情况下,sentinel不管请求者从哪里来,返回值永远是default,也就是说一切请求的来源都被认为是一样的值default。因此,我们需要自定义这个接口的实现,让不同的请求,返回不同的origin。

实际场景中微服务的请求来源基本上都是网关服务转发的,因此可以在网关服务中添加请求头origin,然后在服务测自定义实现RequestOriginParser接口,获取请求头的值。

@Component
public class HeaderOriginParser implements RequestOriginParser {
    @Override
    public String parseOrigin(HttpServletRequest request) {
        // 1.获取请求头
        String origin = request.getHeader("origin");
        // 2.非空判断
        if (StringUtils.isEmpty(origin)) {
            origin = "blank";
        }
        return origin;
    }
}

5、Sentinel配置持久化

5.1 服务端配置

在自己的工程的application.yml 中需要引入如下配置

spring:  
  cloud:
    sentinel:
      transport:
        dashboard: http://localhost:8080
      datasource:
        ds1-flow:  # 流控规则
          nacos:
            server-addr: 127.0.0.1:8848
            data-id: ${spring.application.name}-flow-rules
            namespace: *****************************
            group-id: SENTINEL_GROUP
            # 规则类型:流控
            rule-type: flow
            data-type: json
        ds2-flow:  # 熔断规则
          nacos:
            server-addr: 127.0.0.1:8848
            data-id: ${spring.application.name}-degrade-rules
            namespace: *****************************
            group-id: SENTINEL_GROUP
            rule-type: degrade
            data-type: json
        ds3-flow:  # 热点规则
          nacos:
            server-addr: 127.0.0.1:8848
            data-id: ${spring.application.name}-param-rules
            namespace: *****************************
            group-id: SENTINEL_GROUP
            rule-type: param-flow
            data-type: json
        ds4-flow:  # 系统规则
          nacos:
            server-addr: 127.0.0.1:8848
            data-id: ${spring.application.name}-system-rules
            namespace: *****************************
            group-id: SENTINEL_GROUP
            rule-type: system
            data-type: json
        ds5-flow:  # 授权规则
          nacos:
            server-addr: 127.0.0.1:8848
            data-id: ${spring.application.name}-authority-rules
            namespace: *****************************
            group-id: SENTINEL_GROUP
            rule-type: authority
            data-type: json

在nacos创建sentinel的命名空间,用于存放流控配置文件。

微服务熔断降级 微服务熔断限流_限流_12

5.2 Sentinel源码处理

Sentinel 的持久化需要修改源码才能支持,这里以nacos作为持久化的数据源。所有的修改都是在sentinel-dashboard工程下进行。

修改配置文件

application.properties文件添加nacos的相关配置

# 添加nacos数据源有关的配置项,持久化流控配置到nacos
server.port=8080
nacos.address=192.168.1.68:8848
nacos.namespace=414a50fb-a3c5-4755-a37d-079b62fcba75

pom.xml中修改sentinel-datasource-nacos依赖的作用域

微服务熔断降级 微服务熔断限流_限流_13

复制代码

复制test/…/nacos包下的类到main/java/…/rule/nacos包下

微服务熔断降级 微服务熔断限流_微服务_14

官方只提供了一个流控规则的处理类,其他类型的规则如果需要使用的话需要自行实现。最终所有规则的持久化修改的类有些多这里就不说了,具体去看代码吧:https://gitee.com/clantiana/sentinel-persistence-demo.git

微服务熔断降级 微服务熔断限流_微服务熔断降级_15