基于滑动窗口的消息静默限流

1、背景


在物流详情的数据监控应用中,会通过消息中间件来接收包裹物流的变更消息(qps均值在1w,峰值在4w),接收到消息之后会调用一个后端的定时触发的服务(TMQ)用来检测物流消息是否及时回传。

正常情况下,后端TMQ服务的接口rt在20ms左右,整个设计和架构上运行正常。但是由于TMQ是一个公共的服务,上面承载了很多其他业务方的应用,特别是一些营销类的应用,在活动期时TMQ的定时任务被大量触发,而物流详情是一个最大的使用方。多种场景的叠加导致TMQ变得非常不稳定,反应到业务上就是接口rt飙升,处理能力下降,TMQ维护同学为了避免自己的应用被压死会选择性的降级掉我们的接口。

基于滑动窗口的消息静默限流_第1张图片
TMQ接口rt

因此在方案上考虑通过检测TMQ接口性能,当发现其接口性能下降时进行统计分析,达到一定阈值则自动降级掉对TMQ的调用,同时在降级之后对metaq采取静默限流,利用metaq的抗堆积能力,在限流期间不把流量压到后端,为TMQ的恢复争取一点时间。静默限流期过后,再恢复对TMQ的调用请求,并继续监测接口性能,如果到阈值则再次降级并进行静默限流,如此反复,直到后端的服务恢复到正常状态。

2、服务依赖的稳定性


在互联网服务中,一个稍微复杂点的应用都需要依赖很多后端的服务,服务端基本都是分布式的,大量的服务支撑起整个系统,服务之间也难免有大量的依赖关系。而依赖的后端服务并不是100%稳定,也不是所有依赖都是核心依赖,在依赖的后端服务不稳定时,如果避免影响到自身服务,防止被拖死,需要在设计上有所考虑。

在系统的高可用设计中,针对后端服务的依赖,有非常重要的两条:

  • 接口超时
  • 服务熔断

接口超时大家都非常的熟悉,任何一个通过网络进行的请求都必须设置正确的超时时间,因为一次网络请求都对应了一个线程或者进程。如果请求被处理的太慢而又没有对这次请求设置超时,那么自身应用的线程就会被拖死,短时间得不到释放,线程是昂贵的系统资源,进而导致服务资源被耗尽,自身服务不可用。

服务熔断的灵感来源去于断路器或者保险丝。断路器或者保险丝的作用是当电流过载或者短路的时候会自动断开,避免由于电路不断开,电线升温,造成火灾,引起更大的灾难。

应用到系统设计中,如果我们自己的应用依赖的某个后端服务不稳定,有大量的超时,再让新的请求去访问已经没有太大意义,那只会无谓的消耗现有资源。例如即使已经设置超时1秒了,那明知依赖不可用的情况下再让更多的请求,比如100个,去访问这个依赖,也会导致100个线程1秒的资源浪费。

断路器可以避免这种资源浪费,在自身服务和依赖之间放一个断路器,实时统计访问的状态,当访问超时或者失败达到某个阈值的时候(如50%请求超时,或者连续20次请失败),就打开断路器,那么后续的请求就直接返回失败,不至于浪费资源。断路器再根据一个时间间隔(如5分钟)尝试关闭断路器(或者更换保险丝),看依赖是否恢复服务了。

2.1 Hystrix

方案上需要对后端服务进行监控,在后端服务发生问题时进行隔离。
这里就涉及到两个个问题:

  • 如何发现问题,监控后端的什么指标来判断后端服务发生了问题。
    这里是采用监控后端服务接口的rt,并记录调用后端服务接口的次数,以及rt超过阈值(1000ms)的次数, 如果在一定时间内rt超时的次数和请求次数的比值大于一个阈值则认定后端服务有问题,需要对其进行隔离。

  • 如何恢复,在监控到产生问题,对后端服务进行隔离之后,何时开始恢复。
    同上,也是监控接口请求次数以及超时次数,如果一定时间内容两者的比值小于一个阈值,则认为后端服务开始恢复,撤销对其的隔离。

Hystrix是Netflix开源的一个服务隔离组件,通过服务隔离来避免由于依赖延迟、异常,引起资源耗尽导致系统不可用的解决方案。

Hystrix的官方介绍:https://github.com/Netflix/Hystrix

在具体的方案实现上没有直接接入Hystrix,原因是Hystrix的使用还是稍显复杂,接入成本偏高。最终实现上借鉴了Hystrix滑动窗口,用来监控和处理一定时间内接口的性能

2.2 Hystrix滑动窗口

Hystrix滑动窗口设计的关键在于其高效的无锁统计。

在统计指标项时,如果每个周期都从零开始统计,那么会得到一个周期性出现锯齿的统计曲线,在系统层面上会表现为对依赖的服务造成herd effect(羊群效应/从众效应)。

因此,Hystrix将一个统计周期分解为更小的段(bucket),通过移动时间窗口淘汰最老的bucket。

基于滑动窗口的消息静默限流_第2张图片
Rolling Window

每当需要开始一个新的bucket时,牺牲可容忍的准确性,通过tryLock由一个线程去更新,其他线程依然使用最近的bucket来更新计数。

基于滑动窗口的消息静默限流_第3张图片
Rolling Window

每个bucket使用LongAdder而不是AtomicLong进一步降低写的并发,减少执行CAS时循环的次数。

3、静默限流


3.1 metaq静默限流

方案上,后端服务进行隔离之后,由于消息无法得到处理也不能丢失,因此需要对metaq进行控速,在隔离期间不让流量打过来而是堆积在metaq中,等服务恢复之后再进行处理。

使用的方法是在发现接口被熔断之后,使metaq的消费线程sleep一段时间,sleep时间过去之后再次请求后端服务,同时在之后的流程中再次确定接口是否被熔断,知道后端服务完全恢复。

3.2 最终的实现

handleLecMsg(...)是metaq消费线程处理消息的方法,其中会记录对TMQ接口请求的次数以及超时的次数,达到阈值则熔断开始静默限流。


基于滑动窗口的消息静默限流_第4张图片
最终实现

你可能感兴趣的:(基于滑动窗口的消息静默限流)