分布式系统架构设计 – 第4式 - 服务治理之纵向限流模式

导读

日拱一卒,功不唐捐,分享是最好的学习,一个知识领域里的 “道 法 术 器” 这四个境界需要从 微观、中观以及宏观 三个角度来把握。微观是实践,中观讲套路,宏观靠领悟。本系列文章我把它命名为《分布式系统架构设计三十六式》,讲诉分布式系统里最重要的三十六个虚数的中观套路,而微服务的本质也是分布式,因此搞明白这三十六个最重要的知识点也就同时能搞明白微服务。

实现一个分布式系统通常会面临三大难题: 故障传播性、业务拆分与聚合以及分布式事务 。本系列中的服务治理章节主要是为了解决故障传播性的难题,它包括: 隔离、熔断、降级、限流、容错以及资源管控 ,本文将讲诉服务治理里的 “限流-纵向限流” 模式,下一篇将讲诉 “限流-横向限流” 模式。

动机

可靠性 : 在分布式系统里,每个系统都有自己的容量限制,它所能处理的业务请求能力是有限的,如果不控制这些输入的请求数,突发输入过多的请求量会造成过度的资源竞争从而引发系统故障降低系统的可靠性。

可用性 : 限流有利于控制系统资源的消耗速率有利于过载保护,有利于保护业务资源不被耗尽。例如,当服务A所依赖的下游服务B由于某种原因不稳定、响应增加、延迟增加,对于调用者服务A意味着吞吐量下降和更多的资源占用,极端情况下甚至导致资源耗尽造成服务可用性故障。

流量监管 : 流量监管就是对输入的请求流量进行细粒度的控制,通过监管输入的请求量速率,对超出的部分进行”惩罚”, 比如直接丢弃,使得进入系统里的请求量被限制在一个系统所能承受的合理的范围之内,流量监管比较适合对延时要求较高的业务。

流量整形 : 流量整形就是控制最大输出请求速率提供可能,以确保请求量符合系统容量配置的最大传输速率规定。请求的流量被整形,以使它符合下游服务的速率需求,流量整形比较适合可靠性要求较高的业务。

限流限的是什么

限流其原理是监控输入的请求量,当达到指定的阈值时对量进行控制,以避免系统被瞬时的请求量高峰冲垮,从而保障系统的高可用、高可靠。因此,限流的限的自然是“流”,对于不同的场景“流”是不同的:

  • 网络限流,流指的是带宽、流量;
  • I/O限流的“流”指的是TPS或QPS;
  • 并发限流的“流”指的是并发请求数;
  • 线程资源限流的“流”指的是线程数。

这些“流” 通常具有资源竞用性、延迟性、抖动性以及不可靠性的特征。资源竞用性以及不可靠性需要控制流的资源使用,延迟性、抖动性需要对“流”进行整形,削峰填谷,控制请求的指标波形图。

限流处理策略

  • 直接拒绝:当请求量超过阈值后,新的请求就会被直接拒绝,方式为直接返回或者抛出异常。这种方式比较适合于对分布式系统的负载容量已知的情况下,比如通过全链路压测已经确定了准确的系统处理能力及系统容量,对应固定窗口、滑动窗口算法。

  • 冷启动:当分布式系统长期处于低负载的情况下,请求量突发时,会把系统负载很快拉到很高的水准,这样就可能瞬间就把系统击垮。通过”冷启动”方式,让输入的请求量缓慢增加,在一个时间段内慢慢增加到系统所能承载的阈值上限,给冷系统一个预热的时间,避免系统被压垮,对应令牌桶算法。

  • 匀速排队:匀速排队的方式也就是控制请求以均匀的速率通过,对应的是漏桶算法。

限流模式设计思路

如下图所示,在分布式系统里,限流通常可以按空间维度划分为纵向限流以及横向限流,本文讲述纵向限流,下一篇将讲诉 横向限流。

流量控制

常用的纵向限流算法有两窗算法:固定窗口、滑动窗口以及两桶算法:令牌桶算法、漏桶算法,按其工作原理又可以划分为 保险丝模式以及变压器模式。

保险丝模式

在电路中保险丝主要是起电流过载保护作用,当电路中的电流过载时,保险丝自身就会烧坏从而切断电流,保护后续电路的安全运行,但是保险丝有个问题就是在切断电流后,需要人工或者自动更换保险丝后,电路才能继续运行。

限流算法里的固定窗口算法以及滑动窗口算法应用原理与此类似,在拒绝请求后,需要重新设置计数,因此我定义它们为限流保险丝模式。

固定窗口

固定窗口算法类似人工保险丝模式,在切断流量后需要等很久才能重新工作。固定窗口算法将时间线划分成一个个固定大小的时间窗口,并且每个窗口都有一个计数器用于统计这一时间窗口内的访问次数,如果访问的次数超过了一个预先定义的阈值,则拒绝接下来的请求直到下一个时间窗口开始重新计数,又超过则继续拒绝,再在下一个时间窗口重新设置计数器继续计数,依次类推。

固定窗口

如上图所示,如果我们将时间线的窗口大小设置为5秒,上图里的窗口有[0, 5), [5, 10), …。假设限制是每5秒500个请求,如果在这个5秒内 计数器没超过 500就继续,超过500就拒绝后续的请求进入,直到下一个[5,10]的时间窗口内计数器被重新置0 再继续开始计数服务,再超过500,就继续拒绝服务,依次类推。

很明显,固定窗口的优点很明确,那就是实现很简单,一个计数器就可以实现。但是缺点也很明显,例如:

  1. 边界场景,在第一个[0,5]的时间窗口内,第1秒就把计数器打到超过500,则后续的4秒将无法服务,得等到下一个[5,10]的时间窗口内计数器被重新置0,才可以对外提供服务。

  2. 跨窗口场景,当在第一个时间窗口的 [4,5]计数器的计数是300,没有超过阈值,然后第二个时间窗口的[5,6]计数器是320,也没超过阈值,但是 在 [4,6]的时间窗口内计数器的计数是 300+320=620,很明显超过阈值,因此,固定窗口的缺陷也很明显。

滑动窗口

滑动窗口算法类似自动保险丝模式,在切断流量不像固定窗口那样需要等较长的时间才能重新工作。滑动窗口的计数器也类似固定窗口的计数器,但是将时间线做了进一步的细分,每次往后移动一个细分单元,再每一次都对一个小的窗口进行计数统计实现流量控制,这种方法可以很好的解决之前的固定窗口的跨窗口问题。

滑动窗口

如上图所示,还是定义请求的阈值为500,我们将[0,5]划分为5个窗口,则每个窗口对应1s。假设还是在[4,5]有300个请求和下一秒的[5,6]有320个突发请求,按照滑动窗口的原理,此时统计的将是[1,6]窗口,很明显 300+320=620 > 500,超出了阈值,从而触发拒绝服务,避免了固定窗口算法的请求量突增的问题。

但是对于边界场景,例如[0,5]秒的窗口内,因为是按1s的时间单元进行窗口划分的,假设在第1ms的时间内,请求就超过500,然后就拒绝服务,然后需要等到下一个1s才可以继续出发服务,这很明显有59ms的时间窗式不能提供服务的,因此体现出来请求的指标也不大平滑。

变压器模式

因为保险丝模式都不能解决请求的边界问题,因此引出变压器模式,变压器是电路中将某一等级的电压或电流转换成另外一种同频率的电压或电流的设备,有利于稳流稳压。限流算法里的漏桶算法以及令牌桶算法工作原理与此类似,因此我定义它们为变压器模式。

漏桶

漏桶算法

上图显示了漏桶算法在流量整形和速率限制中的用法,突发的不均匀的请求到达后被扔到一个桶里,这个桶底下有个固定大小的孔,请求按固定大小稳定的输出。

漏桶算法

如上图所示,漏桶算法工作步骤:

  • 请求随意的被输入,有突发的请求量也有比较小的请求量,有快的请求也有慢的请求,然后这些请求进入系统后不是立马被处理,而是被扔到一个桶里;
  • 当桶里缓冲的请求超过设定的水位时,输入的请求将被拒绝进入,从而丢失的后续请求
  • 这个桶以恒定的速率将输入的请求输出;
  • 对比窗口算法,漏桶算法多了一个缓冲。

优点:

  • 漏桶算法里,桶的存在有利于削峰填谷,且输出总是按恒定的速率输出的,因此有利于流量整形,从而平滑了突发的请求量。

缺点:

  • 很明显,漏桶里的请求超过水位后,后续请求会被丢弃,在需要保证幂等性请求的场景不适合使用。

  • 漏桶总是按恒定速率输出请求,这是在假设后续的服务能承接这个速率的前提下的,它无法保证这些输出的请求能够稳定地在一个固定的时间内处理完,假如后续的服务出现资源抢用,或者故障,那么将无法处理这个很定的输出速率,从而引发更大的级联故障。

如上图所示,如果服务2变慢,就会一直占用线程资源不释放,从而导致无法响应服务1的请求,而服务1还是以恒定的速率处理漏桶的请求,而其下游资源不够,因此也会引起级联故障。

令牌桶

下图显示了令牌桶的主要工作步骤:

令牌桶算法

如上图所示,令牌桶算法工作步骤:

  • 在这个桶中有按一定时间周期定期生成的令牌,令牌按预先定义的时间周期进行填充;
  • 令牌桶有最大的令牌个数限制;
  • 如果请求到来时,必须从令牌桶中取得令牌,之后才可以对这个请求进行处理,并且从令牌桶中删除这个被获取的令牌;
  • 如果令牌桶中没有令牌,则无法发送请求,请求必须稍后重试。

优点

  • 如果令牌桶中令牌已满,则丢令牌而不是丢请求。
  • 可以支持突发的请求。

缺点

  • 令牌被耗光后需要等下一次令牌填充,这意味着需要等待一段时间令牌填充后后续请求才可以使用。
  • 对请求的处理速率没做限制,这意味着输入的请求处理速率有可能高过设置的阈值从而引发故障。

漏桶VS令牌桶[3]

  • 漏桶算法控制输出的请求量,输入的请求量可以变化,但输出的请求量保持恒定不变。令牌桶算法控制输入的令牌量,但不限制输出的请求量,输出的请求量可以根据突发的大小而变化。

  • 漏桶算法不依赖令牌。令牌桶算法是令牌依赖的。

  • 在漏桶算法中,如果桶已满,则丢弃请求。在令牌桶中,如果桶已满,则丢弃令牌但不会丢弃该请求。

  • 在漏桶中,请求不断被输出。在令牌桶中,只有在拿到令牌时请求才能通过。

  • 漏桶以恒定速率发送请求。令牌桶允许在恒定速率之后以更快的速率发送突发请求。

算法实践

  • 固定窗口与滑动窗口实现都比较简单,性能较好,但是在超出限流阈值后,请求都会被直接拒绝,因此适用于非幂等性的请求场景;

  • 漏桶算法,有利于控制输出的请求速率,但是在超出桶的水位后请求也会被丢失,也不适用于幂等性请求的场景;

  • 令牌算法,可以支持突发的请求量,但是不控制输出的请求速率,在超出阈值后,只丢失令牌但不丢失请求,因此可以结合在幂等性请求的场景使用;

  • 权衡利弊,根据业务场景合理组合以上4个算法,才是最佳实践。

小结

本文讲诉了服务治理里的 “纵向限流”模式,在前一篇《分布式系统架构设计三十六式之服务治理-降级模式》里讲诉了分布式系统服务治理的降级模式。另作者能力与认知都有限,欢迎大家拍砖留念。

作者简介

常平,中科大硕,10年+数据相关经验,主要工作背景为分布式系统、存储、缓存、微服务、云计算以及大数据,现就职于DELL EMC。个人技术博客:https://changping.me

版权申明

本文的版权协议为 CC-BY-NC-ND license:https://creativecommons.org/licenses/by-nc-nd/3.0/deed.zh ,可以自由阅读、分享、转发、复制、分发等,限制是需署名、非商业使用(以获利为准)以及禁止演绎。

参考资料

[1]https://tech.domain.com.au/2017/11/protect-your-api-resources-with-rate-limiting
[2]https://github.com/alibaba/Sentinel/wiki/%E6%B5%81%E9%87%8F%E6%8E%A7%E5%88%B6
[3]https://www.quora.com/What-is-the-difference-between-token-bucket-and-leaky-bucket-algorithms
[4]https://en.wikipedia.org/wiki/Rate_limiting
[5]https://en.wikipedia.org/wiki/Bandwidth_throttling
[6]https://en.wikipedia.org/wiki/Bandwidth_management
[7]https://en.wikipedia.org/wiki/Token_bucket
[8]https://en.wikipedia.org/wiki/Leaky_bucket