【微服务03】使用Sentinel实现流控和服务降级

1,345 阅读9分钟
  • 为什么需要学习使用 Sentinel

伴随着微服务的兴起,服务间的调用的可靠性也越发重要了起来,在下面的这种服务调用关系中:

01.png 1.服务调用关系图

A服务调用服务C,B服务调用服务D,服务C/D调用服务E,而服务会进行数据库读写,如果服务E在服务C/D的频繁调用下不堪重负,使得服务E宕掉了,则将导致整个服务集群不可用,这种现象简称为服务雪崩,因此为了避免这种情况,就需要额外的技术保证服务的可用性。如常用的hystrixsentinel 就是用来处理这种情况的。前者是 Spring Cloud Netflix 常使用的组件,而后者是 Spring Cloud Alibaba 经常使用的组件。本文以介绍 sentinel 为主。

本文主要有以下内容:

  • Sentinel组件的简单介绍
  • Sentinel流控规则
  • Sentinel服务降级方案

什么是Sentinel

随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 是面向分布式、多语言异构化服务架构的流量治理组件,主要以流量为切入点,从流量路由、流量控制、流量整形、熔断降级、系统自适应过载保护、热点流量防护等多个维度来帮助开发者保障微服务的稳定性。

服务雪崩现象的发生,一般有两种原因,一种是外部的大量请求调用,如在上面的例子中服务C/D频繁调用服务E,导致服务E不堪重负,另外一种是自身的原因,如服务E内部发生了不可预知的错误,如数数据库读写效率过低,亦或者自身业务逻辑代码抛出了异常等。

为了解决这两种情况,Sentinel提供了针对外部的流量整形和针对本身的服务异常服务容错治理。

针对外部原因:提供了三种流控规则和三种流控效果来进行服务限流。

  • 流控规则
    • 直接流控
    • 关联流控
    • 链路流控
  • 流控效果
    • 快速失败
    • Warm Up
    • 排队等待

针对内部原因:提供了服务降级方案和服务熔断的方式来保证服务的稳定性。

  • 服务降级:针对单次服务调用,如服务C调用服务E时,服务E的方法出现了异常导致无法完成正常的处理逻辑,如响应超时、服务异常,此时我们给服务C提供一个默认的返回值,保证此次调用得到处理。
  • 服务熔断:指服务的异常调用占到一定的数量或者比例,达到了我们的判定条件,如在一个统计的时间窗口内,服务异常比例占比到达70%,触发了服务熔断,即所有的后续请求直接进入降级逻辑,不再调用目标访问方法。

对外部流量的控制

首先搭建sentinel的运行环境:

Sentinel官网页面下载可执行的jar文件,版本sentinel-dashboard-1.8.2.jar。下载完成后通过命令 java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.8.2.jar启动 Sentinel 。通过 127.0.0.1:8080访问 sentinel控制台,默认的账户密码均为sentinel

接着在项目中集成sentinel

pom.xml添加下列依赖

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

application.yml中添加如下配置

    spring:
      cloud:
        sentinel:
          transport:
            # sentinel api端口,默认8719
            port: 8719
            # dashboard地址
            dashboard: localhost:8080

ConsumerController添加如下代码

    @GetMapping("sentinel")
    @SentinelResource(
        value = "sentinel",
        blockHandler = "consumerSentinelResource_Blocked",
        fallback = "consumerSentinelResource_fallback")
    public String consumerSentinelResource() {
      //  if (Math.random() > 0.6 ){
      //     System.out.println("执行fallback降级逻辑");
      //      throw new RuntimeException("RuntimeException");
      //  }
        return "正常返回值";
    }
    ​
    public String consumerSentinelResource_Blocked(BlockException blockException){
        System.out.println("执行BlockException降级逻辑");
        return new Date().toLocaleString() + "BlockException: 服务被降级";
    }
    ​
    public String consumerSentinelResource_fallback(){
        return new Date().toLocaleString() +  "通用异常解决方法: 服务被降级";
    }

@SentinelResource 注解表明这是一个Sentinel资源。

  • value属性:资源名称必须填写。
  • blockHandler:处理因触发流控规则的请求抛出的BlockException,执行的的降级方法

流控规则

启动 sentinel并登录控制台后,侧边栏初始状态下并没有我们的服务,这时需要我们访问一下服务,随便调用一个服务接口,侧边栏就会出现我们注册的服务。

02.png 2.sentinel控制台截图

点开我们的服务,在侧边栏有个流控规则按钮,点击流控规则,在页面的右上角有一个新增流控规则按钮,点击此按钮,就可以新增流控规则。

直接流控

直接流控是我们常用的流控,一般情况下直接添加即可。按照下图添加流控规则:

直接流控.png 3.直接流控截图

规则说明:在每秒请求数超过1个时,就触发流控规则,即执行consumerSentinelResource_Blocked()方法的逻辑,通过postman 调试接口即可得到下图的结果:

限流.png 4.触发直接流控规则

关联流控

如果两个资源之间存在竞争关系,如共享一个线程池或者数据库连接池,这时候就可以使用关联流控,此时在低优先级的资源上设置流控规则,进而使得高优先级的资源获得竞争优势,

例如:资源A和资源B存在竞争关系,此时在资源A上设置流控规则和判断阈值,此时当高优先级的请求数量达到判断阈值时,就会对低优先级的资源进行流控,

ConsumerController添加如下代码:

    @SentinelResource(value = "condition")
    @GetMapping("condition")
    public String condition() {
        return "condition: 我是高优先级的资源";
    }

在控制台添加如下规则

关联流控规则.png 5.配置关联流控规则

ps:通过postman工具怎么都无法出现低优先级服务降级的响应,于是乎我使用了一个python脚本模拟http请求,关联流控规则就起作用了!脚本内容如下

    import requests
     
    url = "http://127.0.0.1:10001/consumer/condition"
    url2 = "http://127.0.0.1:10001/consumer/sentinel"
    while(True):
        res = requests.get(url=url)
        res2 = requests.get(url=url2) 
        print("condition response:",res.text)  # 返回请求结果
        print("sentinel response:",res2.text)  # 返回请求结果
        print("------------------------------")

响应结果如下图

关联流控.png 6.关联流控规则

链路流控

在一个应用中,对同一个资源有多条不同的访问路径时,如果需要对访问路径限流,则可以选用链路流控。如下图:

链路流控示意图.png 7.链路流控示意图

query 和 query2 都能访问到 resource, 如果想要对api/query2进行流控,则此时可以选择链路流控的方式。具体配置如下

链路流控规则配置.png 8.配置链路流控规则

流控效果

快速失败

在前面的示例中,所有的流控效果都是快速失败,快速失败是sentinel默认的流控效果,即到达我们的设置的阈值之后就失败。

Warm Up

这种方式实现了预热的效果,即在设定的预热时间内,阈值从起始阈值逐渐提升到我们设定的阈值,起始阈值的值为设定的 单机阈值 / 冷加载因子sentinel默认的冷加载因子为 3。

流控效果warnup.png 9.Warm Up配置

以上图的配置,则流控效果在 10s100 / 3 = 33 缓慢提升到 100。

内部降级逻辑

降级方案

流控规则和流控效果是作用在外部流量的,而对自身服务而言,当服务调用出现足够多的异常时,为保证服务的稳定性,可采取服务熔断策略,服务熔断是多次异常调用的累积结果

在上面的示例代码中,我们在使用@SentinelResource注解时,配置了blockHandler、 fallback这两个属性, 这两个属性的值都是编写降级逻辑的,但是稍有不同。

blockHandler 只能处理服务抛出的异常时BlockException的情况,BlockExceptionSentinel组件配置的流控规则起作用时抛出的异常,即被当请求被流控规则拦截时,抛出的异常。

    public String consumerSentinelResource_Blocked(BlockException blockException){
        String s = new Date().toLocaleString() + "BlockException: 服务被降级";
        return s;
    }

在编写blockHandler的方法时,需要在参数列表的最后添加一个BlockException类型的参数,否则降级逻辑不会起作用。

fallback使用场景是当调用的方法抛出了除BlockException类型的其他运行时异常,如代码中分子为 0 抛出的java.lang.ArithmeticException: / by zero

fallback适用于编写通用的降级逻辑,fallback方法的编写不需要添加额外参数,和 controller 中的方法形参保持一致即可。

熔断策略

sentinel中,定义了三种熔断策略:

异常比例

在统计时长指定的时间内,异常调用比例超过设置的比例阈值,且请求数满足设置的最小请求数,则进行服务熔断,直接进入降级逻辑。如果请求数没有满足设置的最小请求数,则即使高于这个比例也不会熔断。如最小请求数是 10,比例为 0.5,即使前 9 次都失败了,也不会熔断。

异常数

在统计时长指定的时间内,异常调用比例大于设置的请求数,且请求数满足设置的最小请求数,则进行服务熔断,直接进入降级逻辑。这里大于指的是加入设置的异常数是3,则熔断发生在第四次失败调用之后。

慢调用比例

在统计时间窗口内,请求响应的时间大于了设置的 最大RT 时间的请求数所占全部请求数的比例超过了设置的比例阈值,且总请求数大于等于最小请求数,则触发熔断规则。

最大RT是判断慢请求的条件,不是触发熔断的条件。

熔断状态的转换

Sentinel 的熔断器有三个状态:关闭,全开,半开,在没有达到触发熔断的条件时,熔断器处于关闭状态,当达到熔断开始的条件时,熔断器出处于全开状态,当熔断时长结束时,熔断器不会立马关闭,而是处于半开状态,此时,下一个请求的状态决定了是关闭还是全开,如果下一个请求请求成功,则关闭熔断器,退出熔断状态,如果失败,则立马进入熔断状态,不需要再次满足设置的条件。

熔断状态转换.png 10.熔断状态转换示意图

需要说明的是:开源的Sentinel组件的配置的规则并没有被持久化,因此当我们重启应用或者重启sentinel组件,都需要重新配置相关规则。

  • 让我看看是谁在520宅在家敲代码啊?是我啊,那没事了!

dsg.jpg 11.手动🐶保命

微服务相关的其他文章:

微服务01:Nacos + OpenFeign + LoadBalancer 的入门级用法

微服务02:Nacos配置中心的入门级用法