Sentinel 隔离和降级

Sentinel 隔离和降级

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第11天,点击查看活动详情

如果有需要可以查看官方文档sentinelguard.io/zh-cn/docs/…

流量控制是一种预防措施,能够减缓雪崩发生的情况,但是无法完全隔绝。如果想要将雪崩问题控制在一定的范围,需要通过线程隔离和熔断降级这两个手段了。

  • **线程隔离:**消费者远程调用提供者方法的时候,为每个远程调用的请求分配独立线程池。当某一个提供者出现故障时,最多消耗该提供者对应请求的独立线程池资源,避免消费者的所有资源被耗尽。

    image-20220809162742696

  • **熔断降级:**在消费者远程调用提供者的过程中,加入一个断路器。断路器统计提供者响应失败的次数,若 失败次数/总请求次数 比例过高,则熔断该业务,不允许再访问该服务的提供者。

    image-20220809165555986

无论是线程隔离或者熔断降级都是对提供者的限制和监管,对消费者的保护。在提供者出现故障的时候对消费者通过线程隔离、熔断降级进行保护。

我们的微服务远程调用通过 Feign 完成,因此我们将 Feign 和 Sentinel 进行整合,在 Feign 中实现线程隔离和熔断降级。

1. FeignClient 整合 Sentinel

  • Feign 整合 Sentinel 步骤:
    1. 在application.yml中配置:feign.sentienl.enable=true
    2. 给FeignClient编写FallbackFactory并注册为Bean
    3. 将FallbackFactory配置到FeignClient

1.1 修改配置,开启 Sentinel 功能

  • 修改消费者的 application.yml 文件,开启 Feign 的 Sentinel 功能:

    feign:
      sentinel:
        enabled: true # 开启 feign 对 sentinel 的支持
    复制代码

1.2 编写失败降级逻辑

当提供者出现故障的时候,对应的请求返回的是异常信息。我们不能让异常信息赤裸裸的展示在用户面前,通常会给用户返回一个友好提示或默认结果。这个就是请求失败后的降级逻辑,也叫做失败降级逻辑。

  • 给 Feign 编写失败后的降级逻辑,通常有两种方法:

    1. FallbackClass,无法对远程调用做异常处理
    2. FallbackFactory,可以对远程调用的异常做处理

    我们通常选择第二种方法做失败降级处理

接下来我们将演示第二种方法 FallbackFactory 处理方法

  1. 在 Feign 里定义 FallbackFactory

    image-20220817224623517

    代码:

    package cn.itcast.feign.clients.fallback;
    
    import cn.itcast.feign.clients.UserClient;
    import cn.itcast.feign.pojo.User;
    import feign.hystrix.FallbackFactory;
    import lombok.extern.slf4j.Slf4j;
    
    /**
     * @author HGD
     * @date 2022/8/17 12:09
     */
    @Slf4j
    public class UserClientFallbackFactory implements FallbackFactory<UserClient> {
        @Override
        public UserClient create(Throwable throwable) {
            return new UserClient() {
                @Override
                public User findById(Long id) {
                    log.error("查询用户失败", throwable);
                    return new User();
                }
            };
        }
    }
    复制代码
  2. 将上面的 FallbackFactory 注册成 Bean

    package cn.itcast.feign.config;
    
    import cn.itcast.feign.clients.fallback.UserClientFallbackFactory;
    import feign.Logger;
    import org.springframework.context.annotation.Bean;
    
    public class DefaultFeignConfiguration {
        @Bean
        public Logger.Level logLevel(){
            return Logger.Level.BASIC;
        }
    
        /**
         * 将 FallbackFactory 注册到 Bean 中
         * @return new UserClientFallbackFactory();
         */
        @Bean
        public UserClientFallbackFactory userClientFallbackFactory() {
            return new UserClientFallbackFactory();
        }
    }
    
    复制代码
  3. 在 Feign 中提供者的接口中添加 @FeignClient(fallbackFactory = FallbackFactory.class)注解

    image-20220817225235842

    代码:

    package cn.itcast.feign.clients;
    
    
    import cn.itcast.feign.clients.fallback.UserClientFallbackFactory;
    import cn.itcast.feign.pojo.User;
    import org.springframework.cloud.openfeign.FeignClient;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    
    @FeignClient(value = "userservice", fallbackFactory = UserClientFallbackFactory.class)
    public interface UserClient {
        @GetMapping("/user/{id}")
        User findById(@PathVariable("id") Long id);
    }
    复制代码

完成所有步骤之后重启消费者的服务,访问一下涉及远程调用的业务,然后查看 Sentinel 控制台

image-20220817225440411

2. 线程隔离(舱壁模式)

2.1 线程隔离的实现方式

线程隔离通过两种方式实现:

  • 线程池隔离

    为每个远程调用的请求分配独立线程池,利用线程池本身实现隔离效果

    image-20220817231652668

  • 信号量隔离

    不创建线程池,而是计数器模式,记录业务使用的线程数量,达到信号量上限时,禁止新的请求

    image-20220817231231602

方式二是 Sentinel 默认方法

  • 两者优缺点

    优点 缺点 场景
    信号量隔离 轻量级,无额外开销 不支持主动超时,不支持异步调用 高频使用,高输出
    线程池隔离 支持主动超时,支持异步调用 线程的额外开销比较大 低扇出,调用较少的情况

    低扇出:一个类,尽可能不要去依赖别的类,就是所谓的low fan out

    主动超时:能主动控制方法执行的超时时间,如果超时了或有异常就抛出异常

2.2 Sentinel 的线程隔离

  • 配置规则

    image-20220817233216115

  • 案例

    1. 配置隔离规则

      选择 Feign 接口后面的流控按钮:

      image-20220817233416091

      填写表单:

      image-20220817233510142

    2. 使用 JMeter 测试

      选择《阈值类型-线程数<2》:

      image-20210716124229894

      一次发生10个请求,有较大概率并发线程数超过2,而超出的请求会走之前定义的失败降级逻辑。

      查看运行结果:

      image-20210716124147820

      发现虽然结果都是通过了,不过部分请求得到的响应是降级返回的null信息。

2.3.总结

线程隔离的两种手段是?

  • 信号量隔离

  • 线程池隔离

信号量隔离的特点是?

  • 基于计数器模式,简单,开销小

线程池隔离的特点是?

  • 基于线程池模式,有额外开销,但隔离控制更强

3. 熔断降级

现代微服务架构基本都是分布式的,整个分布式系统由非常多的微服务组成。不同服务之间相互调用,组成复杂的调用链路。前面描述的问题在分布式链路调用中会产生放大的效果。整个复杂链路中的某一环如果不稳定,就可能会层层级联,最终可能导致整个链路全部挂掉。因此我们需要对不稳定的 弱依赖服务调用 进行 熔断降级,暂时切断不稳定的服务调用,避免局部不稳定因素导致整个分布式系统的雪崩。熔断降级作为保护服务自身的手段,通常在客户端(调用端)进行配置。

熔断降级的思路就是由断路器统计服务调用的异常比例、慢请求比例,如果超出阈值则会熔断该服务,即拦截一切访问该服务的请求。当服务恢复时,断路器会放行访问该服务的请求。

  • 断路器控制熔断和放心是通过状态机来完成:

    image-20220817235337116

    状态机包括三个状态:

    • Closed:关闭状态。断路器处于这个状态会放行所有请求,并且开始统计异常比例、慢请求比例。比例超过阈值则将状态切换至 Open 状态。

    • Open:打开状态。这个状态下对应提供者的所有请求都会被拒绝、快速失败,直接走上述的失败降级逻辑。Open 状态 5 秒后进入 Half-Open 状态。

    • Half-Open:半开状态。这个状态下将放行一次请求,根据结果来判断接下来操作

      • 请求成功:进入 Closed 状态

      • 请求失败:进入 Open 状态

      慢请求:请求的响应时间大于规定的响应之间

断路器的熔断策略有三种:慢调用、异常比例、异常数。

3.1 慢调用

  • 慢调用

    慢调用比例 (SLOW_REQUEST_RATIO):选择以慢调用比例作为阈值,需要设置允许的慢调用 RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用。当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断。

    就是说,业务的响应时长(RT)大于指定时长的请求被认定为慢调用请求。在指定时间内,如果请求数量超过 QPS 且慢调用比例大于设定阈值,则触发熔断。

    我认为就是请求超量且响应缓慢就熔断。

  • 规则配置

    image-20220818233438786

    RT 超过 50ms 的调用是慢调用。统计最近 1000ms 内的请求,如果 QPS 超过 5 次且慢调用比例不低于 0.4 则触发熔断,熔断时长为 5 秒。然后进入 Half-Open 状态,放行一次请求做测试

  • 案例

    需求:给 UserClient的查询用户接口设置降级规则,慢调用的RT阈值为50ms,统计时间为1秒,最小请求数量为5,失败阈值比例为0.4,熔断时长为5

    1)设置慢调用

    修改user-service中的/user/{id}这个接口的业务。通过休眠模拟一个延迟时间:

    image-20210716150234787

    此时,orderId=101的订单,关联的是id为1的用户,调用时长为60ms:

    image-20210716150510956

    orderId=102的订单,关联的是id为2的用户,调用时长为非常短;

    image-20210716150605208

    2)设置熔断规则

    下面,给feign接口设置降级规则:

    image-20210716150654094

    规则:

    image-20210716150740434

    超过50ms的请求都会被认为是慢请求

    3)测试

    在浏览器访问:http://localhost:8088/order/101,快速刷新5次,可以发现:

    image-20210716150911004

    触发了熔断,请求时长缩短至5ms,快速失败了,并且走降级逻辑,返回的null

    在浏览器访问:http://localhost:8088/order/102,竟然也被熔断了:

    image-20210716151107785

3.2 异常比例、异常数

  • 异常比例 (ERROR_RATIO):当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%。

    异常数 (ERROR_COUNT):当单位统计时长内的异常数目超过阈值之后会自动进行熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。

    也就是说,统计指定时间内的调用,如果调用次数超过指定请求数,并且出现异常的比例达到设定的比例阈值(或超过指定异常数),则触发熔断

    我认为就是请求超量且异常过多就熔断

  • 规则配置

    • 异常比例

      image-20220818234215367

      统计最近 1000ms 内的请求,如果请求次数超过 10 次且异常比例高于 0.4 则触发熔断

    • 异常数

      image-20220818234332384

      统计最近 1000ms 内的请求,如果请求量超过 10 次且异常比例不低于 2 次则触发熔断

  • 案例

    需求:给 UserClient的查询用户接口设置降级规则,统计时间为1秒,最小请求数量为5,失败阈值比例为0.4,熔断时长为5s

    1)设置异常请求

    首先,修改user-service中的/user/{id}这个接口的业务。手动抛出异常,以触发异常比例的熔断:

    image-20210716151348183

    也就是说,id 为 2时,就会触发异常

    2)设置熔断规则

    下面,给feign接口设置降级规则:

    image-20210716150654094

    规则:

    image-20210716151538785

    在5次请求中,只要异常比例超过0.4,也就是有2次以上的异常,就会触发熔断。

    3)测试

    在浏览器快速访问:http://localhost:8088/order/102,快速刷新5次,触发熔断:

    image-20210716151722916

    此时,我们去访问本来应该正常的103:

    image-20210716151844817

3.3 总结

  • 慢调用触发情况:统计最近时间段内的请求,如果请求数量超过最小请求数且慢调用比例高于阈值则熔断。
  • 异常比例\异常数触发情况:统计最近时间对内,如果异常请求的数量超过 指定异常数\异常比例 则熔断。
  • 通常的使用场景
    • 分布式系统中降级:假设存在应用A需要调用应用B的接口(特别是一些对接外部公司或者业务的接口时候),那么一般用于A调用B的接口时的防护;
    • 数据库慢调用的防护: 假设应用需要读/写数据库,但是该读写SQL存在潜在慢SQL的可能性,那么可以对该读写接口做防护,当接口不稳定时候(存在慢SQL),那么基于熔断器做降级。
    • 也可以是应用中任意弱依赖接口做降级防护(即自动降级后不影响业务核心链路)。

猜你喜欢

转载自juejin.im/post/7133245270782902279