Jaccorot 日事日毕日清日高

微服务测试之断路器


背景

某日午休醒来,睡意朦胧之际告警群嗡嗡作响,查看记录心中默叹:优惠券中心这帮人今天没烧香啊,上个线搞出问题来了。

接杯热水开始下午工作吧,没多久收到反馈:间歇性页面响应慢,心中一阵纳闷,我这业务近期也没改动,优惠券这种边角业务,怎么就影响到我了,赶紧排查。

若干分钟后,核心业务开始受影响,RT变大,QPS下降明显,众架构师齐上阵,排查问题迅速解决,业务恢复正常。

分析

翻看日志、分析监控大致还原了当时的现场。

  • 核心服务A的某个接口A-1对非核心服务B有部分依赖;
  • 服务B部署失败,导致只有少量实例正常运行;
  • 随着业务的进行服务B无法及时处理,出现堆积;
  • 接口A-1阻塞等待,出现堆积;
  • 接口A-1耗尽服务A的线程池;
  • 服务A因无空余线程,全部阻塞;

这种现象统称为:雪崩效应

雪崩效应

是一种因服务提供者的不可用导致服务调用者的不可用,并将不可用逐渐放大的过程

雪崩图解

1.当服务都健康的时候如下图所示

normal

2.当其中一个服务出现延迟,将会阻塞整个用户的请求

fail

3.一个服务的延迟会导致单位时间内资源一直被占用,应用的其它请求进来也会延迟,紧接着队列开始堆积,线程还有其他系统资源不释放,甚至引发整个系统的级联失败,出现雪崩。

error

雪崩形成原因

1)服务提供者不可用    
     a)硬件故障:硬件损坏造成的服务器主机宕机, 网络硬件故障造成的服务提供者的不可访问

     b)程序Bug:

     c)   缓存击穿:缓存击穿一般发生在缓存应用重启, 所有缓存被清空时,以及短时间内大量缓存失效时. 大量的缓存不命中, 使请求直击后端,造成服务提供者超负荷运行,引起服务不可用

     d)用户大量请求:在秒杀和大促开始前,如果准备不充分,用户发起大量请求也会造成服务提供者的不可用

2)重试加大流量
     a)用户重试:在服务提供者不可用后, 用户由于忍受不了界面上长时间的等待,而不断刷新页面甚至提交表单

     b)代码逻辑重试: 服务调用端的会存在大量服务异常后的重试逻辑

3)服务调用者不可用
     a)同步等待造成的资源耗尽:当服务调用者使用同步调用 时, 会产生大量的等待线程占用系统资源. 一旦线程资源被耗尽,服务调用者提供的服务也将处于不可用状态, 于是服务雪崩效应产生了。

解决办法

服务熔断

一般是指软件系统中,由于某些原因使得服务出现了过载现象,为防止造成整个系统故障,从而采用的一种保护措施,所以很多地方把熔断亦称为过载保护。很多时候刚开始可能只是系统出现了局部的、小规模的故障,然而由于种种原因,故障影响的范围越来越大,最终导致了全局性的后果。

服务降级

当服务器压力剧增的情况下,根据当前业务情况及流量对一些服务和页面有策略的降级,以此释放服务器资源以保证核心任务的正常运行。

业界方法:hystrix

hystrix是一个帮助解决分布式系统交互时超时处理和容错的类库, 它同样拥有保护系统的能力。

    对外依赖包括第三方类库的依赖提供延迟和失败保护
    阻断传递失败,防止雪崩
    快速失败并即时恢复
    合理的fallback和优雅降级
    提供近实时的监控

Hystrix应对

solve

  • hystrix把每个依赖都进行隔离,对依赖的调用全部包装成HystrixCommand或者HystrixObservableCommand
  • 对依赖的调用耗时设置阀值,如果超过阀值直接判定超时
  • 对每个依赖维护一个连接池,如果连接池满直接拒绝访问
  • hystrix评估调用失败,调用超时,线程拒绝,调用成功的比例,如果超过指定的阀值直接走熔断处理,对依赖的访问直接走fallback逻辑(fallback逻辑使用者自己实现)
  • 熔断生效后,会在设定的时间后放出一个请求来探测依赖是否恢复,依赖的应用恢复后关闭熔断

原理

1. 隔离:

Hystrix隔离方式采用线程/信号的方式,通过隔离限制依赖的并发量和阻塞扩散

1)线程隔离
    Hystrix在用户请求和服务之间加入了线程池。

    Hystrix为每个依赖调用分配一个小的线程池,如果线程池已满调用将被立即拒绝,默认不采用排队.加速失败判定时间。线程数是可以被设定的。

    原理:用户的请求将不再直接访问服务,而是通过线程池中的空闲线程来访问服务,如果线程池已满,则会进行降级处理,用户的请求不会被阻塞,至少可以看到一个执行结果(例如返回友好的提示信息),而不是无休止的等待或者看到系统崩溃。

隔离前

separete1

问题时

separete2

隔离后

separete3

2)信号隔离

信号隔离也可以用于限制并发访问,防止阻塞扩散, 与线程隔离最大不同在于执行依赖代码的线程依然是请求线程(该线程需要通过信号申请, 如果客户端是可信的且可以快速返回,可以使用信号隔离替换线程隔离,降低开销。信号量的大小可以动态调整, 线程池大小不可以。

2. 熔断:

如果某个目标服务调用慢或者有大量超时,此时,熔断该服务的调用,对于后续调用请求,不在继续调用目标服务,直接返回,快速释放资源。如果目标服务情况好转则恢复调用。

熔断器:Circuit Breaker
熔断器是位于线程池之前的组件。用户请求某一服务之后,Hystrix会先经过熔断器,此时如果熔断器的状态是打开(跳起),则说明已经熔断,这时将直接进行降级处理,不会继续将请求发到线程池。熔断器相当于在线程池之前的一层屏障。每个熔断器默认维护10个bucket ,每秒创建一个bucket ,每个blucket记录成功,失败,超时,拒绝的次数。当有新的bucket被创建时,最旧的bucket会被抛弃。
  • Closed:熔断器关闭状态,调用失败次数积累,到了阈值(或一定比例)则启动熔断机制;

  • Open:熔断器打开状态,此时对下游的调用都内部直接返回错误,不走网络,但设计了一个时钟选项,默认的时钟达到了一定时间(这个时间一般设置成平均故障处理时间,也就是MTTR),到了这个时间,进入半熔断状态;

  • Half-Open:半熔断状态,允许定量的服务请求,如果调用都成功(或一定比例)则认为恢复了,关闭熔断器,否则认为还没好,又回到熔断器打开状态;

测试

众架构师集成了hystrix后就提测了,作为测试做了简单梳理

测试期望:

  • 当业务受阻时,业务响应时间会变大
  • 当触发熔断时,会极大地降低业务响应时间,返回预配置的fallback内容,熔断器处于open状态
  • 当触发熔断后,单位间隔内会放行一小部分量,熔断器处于half-open状态
  • 当触发熔断后,业务恢复时,会正常关闭熔断器,返回正常业务数据,熔断器处于closed状态

测试拆解

1.确定待测试的服务和测试内容

通过模拟阻塞、正常、由正常变阻塞、由阻塞变正常的环境,来确保熔断机制正常生效

2.配置hystrix相关参数;

  • circuitBreakerSleepWindowInMilliseconds = 5000; //如果熔断开关处于打开状态,且在一个时间窗口内,则允许一次访问进行测试

  • circuitBreakerErrorThresholdPercentage = 50;//如果当前采样的错误率小于阀值,则不进行熔断

  • circuitBreakerRequestVolumeThreshold = 20; //如果当前采样的总请求数小于阀值,则不进行熔断

  • and so on

3.设计测试方案

  • 大力出奇迹:恒定线程压测consumer,大力压测service provider,待provider出现瓶颈后,观察consumer的各项指标

业务中做过大量优化设计,provider性能奇好无比,单台工作机没法压出性能瓶颈,又找不到空闲压力机,放弃

  • 控制zk: 恒定线程压测consumer,通过手动启用、禁用zk中的provider,模拟阻塞状态,观察consumer的各项指标

zk关闭后主动通知consumer,导致触发业务中的异常兼容代码,模拟失败,放弃

  • 控制服务器:恒定线程压测consumer,通过手动start、stop服务器中的provider,模拟阻塞状态,观察consumer的各项指标

服务开启、关闭主动上报zk,zk主动通知consumer,结果同上一方案,模拟失败,放弃

  • 自己动手丰衣足食: 修改provider业务代码,增加条件模拟阻塞状态,观察consumer的各项指标

就它了!Let’s do it! 通过hard code强加sleep逻辑,模拟阻塞情况,每分钟前30秒阻塞,后30秒恢复,压测观察5分钟

    /**
     * @description 每一分钟的前30秒阻塞,后30秒放开
     */
    @Override
    public long  getContent(long id) {
        logger.info("测试熔断:start" + Thread.currentThread().getName());
        Timestamp time = new Timestamp(new Date().getTime());
        int currentSeconds = time.getSeconds();
        try {
            if (currentSeconds <= 30) {
                Thread.sleep(hystrixSleepTime);
            }
        }catch (Exception e){
            e.printStackTrace();
        }

        logger.info("测试:over" + Thread.currentThread().getName());
        #业务代码

4.祭出jmeter测试压测之

测试结果

1.少量请求未触发熔断

采样频率小于设置阈值,未触发熔断,服务间隔性阻塞 5

2.适量请求延后触发熔断

开始阻塞后,响应时间变长 ①当经过一段时间符合熔断条件时,触发熔断,响应时间大幅减少 ②每隔熔断间隔段熔断器会处于半打开状态进行请求,响应时间小幅度上升 ③当服务恢复后,熔断器关闭,响应时间略微产生波动 10

3.海量请求瞬间触发熔断

开始阻塞后,迅速触发熔断,响应时间立即减少 50 响应结果断言周期性pass与fail,结合上一张图表明: 熔断触发立即返回预设结果; 熔断关闭立即返回正常结果; 50-1

结论

基本符合预期

参考文章

Netflix 官方文档 https://github.com/Netflix/Hystrix/wiki

熔断机制HYSTRIX https://segmentfault.com/a/1190000012338949

服务熔断、降级、限流、异步RPC – HyStrix https://blog.csdn.net/chunlongyu/article/details/53259014

Hystrix熔断框架介绍 https://www.cnblogs.com/yawen/p/6655352.html


Content