导读

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

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

动机

分布式系统的灵魂 :从产品思维的角度来看,好的产品都是有自己灵魂的。比如微信的产品灵魂被定位成“善良”,善有大善、上善、小善。《道德经》有言:“上善若水,水善利万物而不争,处众人之所恶(wù),故几于道。”,水善利万物而不与万物争,水无处不在,万物感觉不到水的存在又离不开水,不争故天下莫能与之争。每种好的产品背后都隐藏着自己的设计哲学,有自己的灵魂。而一个分布式系统的灵魂又应该怎么定义呢?认知层次不同,对分布式系统的理解也不同,度量一个分布式系统的灵魂,在我看来可以采用分布式系统的SLO图形指标来表达。好的分布式系统SLO指标也是很有规律很漂亮的,如下图所示,左图波形上跳下串,很明显不如右边波形来的漂亮。

系统抖动

波形上跳下串带来的后果是什么呢?想象一下在高速路开车的场景,如果一辆车一会快,一会慢,后面的车会发生什么事?这样开车是很容易出事故的,而开得很稳的车出故障的概率就较小。波形上跳下串,说明该系统里头没有解决好资源竞用性以及服务治理的问题。

可靠性与高性能的平衡 :我们知道要让一个水管里的水流的又快又多,一是给水管灌满水,二是水管通畅同时保证不炸裂水管。同样的道理,在分布式系统里要让系统跑出最好的性能和最可靠的效果,一方面压榨整个系统的资源,另一方面又要保证系统不出故障,在系统不出故障的前提下,尽量榨尽系统资源。

因此为了解决以上问题,这里提出了系统自适应模式。

系统自适应模式设计思路

资源平衡

服务治理与其说是分布式下的套路,不如说是控制论下的套路,其本质是资源的细粒度管控,精巧的平衡整个系统里的资源竞用,从而保证分布式系统对外提供高质量的服务。如下图《一根羽毛的力量》所示,其将平衡的思想用到极致,我们也希望在分布式系统里体现有限资源下的平衡。

 图片来源于网络版权归原作者所有

我们将平衡的思想融入分布式系统,在系统健康的前提下,极致的压榨系统的能力(capacity),同时又保证不会主动造成系统故障,如果发现系统内部出现故障,又会自动调整下发的压力,这就是系统自适应保护。

最佳衡量指标

如何确定最佳的衡量指标?通常比较的原始情况下,是以工作负载比如1分钟、5分钟、15分钟的CPU负载作为系统衡量指标,但是这并不大正确。比如假设 CPU load > 2 就 触发一个系统保护,如果这个 时候系统的CPU load是在下降的,它虽然此时刻大于2, 但是趋势却是下降,因此没必要触发系统保护。还有就是干扰性,比如 按 1分钟负载&& 5分钟负载&&15分钟负载,全都是满足大于2的条件就触发系统保护。但是实际上,也许 5分钟负载是不大于2的,因此这个条件就不成立,并不会触发系统过载保护, 这种行为我称之为负载干扰性。

参考水管的流量算法只依赖于管的截面大小以及流速,我们定义系统的自适应算法依赖的参数为系统入口处的 QPS或TPS ,以及请求的返回时间(RT),这里我将这个公式定义为 System Balance Capacity = QPS * RT。

最佳值

如上图,最好的情况就是即满足 最大的QPS 又满足最小的RT,通过一个时间窗口计算QPS 和 RT ,自适应调整整个系统。

系统自适应算法

下图表示了一个自适应算法,造成系统负载过高以及故障传播的因素很多,比如不合适的线程数、TPS或QPS过大、返回时间过长都有可能,通过合适的算法可以自动调整下发的压力从而保持系统的内部资源平衡。

自适应算法

通过采用系统自适应算法在系统的入口处,实时采集QPS/TPS 以及RT, 然后跟最佳样本值进行比较,依据调节系数进行计算,再调节发送的请求量,发送请求后又采集造成的影响,再反馈在系统入口处。其中,样本值可以在系统启动时按动态采样的方式计算,逐渐增加QPS ,当发现时延发生转折时,我们就确定这个转折点为分布式系统自适应最佳平衡点,记下该值作为当前样本。

小结

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

作者简介

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

版权申明

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

导读

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

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

动机

出错重试 :在分布式系统里,系统里出现故障时需要进行出错处理,当执行熔断或降级处理策略时,通常也需要有相应的重试处理策略,而这些策略又需要根据不同的业务场景进行设计。

超时处理 :在分布式系统里,为了保证高可用以及高可靠性,也需要相应的超时处理策略,比如超时后怎么重试?超时后重试几次还是失败应该怎么处理?超时处理是让用户感知还是不让用户感知?



5F容错模式设计思路

这里借用Dubbo里的概念讲述5种容错处理策略,我定义它们为5F容错法,下图是一个简单的分布式系统逻辑架构图。

容错模式

Failover 失败切换

在分布式系统里,为了保证高可用性以及高可靠性,通常会对服务或者设备进行冗余,当一个服务或者设备出现故障时,就直接切换到另外一个服务或设备上,这种设计模式叫做 故障切换。

如上图所示,服务10本来是路由到服务20的,当服务20出现故障时,从服务10路由到服务20的请求,服务20并没办法处理,这时候服务10收到一个请求超时的返回,发现服务20没法处理这个请求,为了保证高可用性,服务10的请求就被路由到服务21,从而保证了服务的高可用与可靠性,这个过程用户是不感知的

Failfast 快速失败

快速失败是指当发现服务请求调用失败时,就立即上报故障,快速失败的一个重要目的是用于检测错误以便降低出错成本为系统提供足够的信息来保证高可用与高可靠,这个过程用户是感知的。

比如上图中服务20出现故障就快速上报故障给服务10,然后服务10就可以采用Failover策略将服务请求切换到服务21,从而避免更多的不可用时间。

Failback 失败恢复

Failback跟Failover有点类似,但是Failover是发现故障时就把请求切换到别的服务或设备上去,而Failback是在发现下游的故障后,把请求扔到一个临时的设备或者服务或者组件(比如队列)上,然后待下游故障修复后,重新同步数据以及请求,把这些数据或者请求还原到原来的服务或者设备上。比如上图所示,在服务20出现故障后,服务10发过来的请求被放到一个临时的队列里,然后在服务20在一定的时间内被恢复后,又把这些请求从队列中恢复发到服务20,这个过程用户时不感知的。

Failsafe 失败安全

FailSafe 是指系统出现故障时可以直接忽略这个故障,不进行相应的故障处理,在Failsafe的场景下,故障不会给系统带来伤害,对服务质量也不会有什么影响,简单的处理方式就是把故障的信息写到日志里保存。

Forking 请求分叉

在Forking策略下,将请求进行裂变下发,只要一个请求处理成功整体请求就成功。比如上图所示,一个读请求到网关后被分裂成同样的请求三份,然后这三个请求被下发到服务10,11,12,只要有一个请求处理成功就返回成功。

小结

本文讲诉了服务治理里的 “5F容错”模式,内容也没有多少,但是需要应用合适保证服务质量却并不容易,在应用的时候一般会根据实际的业务场景进行策略组合使用,在前一篇《分布式系统架构设计三十六式之服务治理-横向限流模式》里讲诉了分布式系统服务治理的横向限流模式。另作者能力与认知都有限,欢迎大家拍砖留念。

作者简介

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

版权申明

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

参考资料

[1]http://dubbo.apache.org/zh-cn/docs/source_code_guide/cluster.html

导读

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

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

动机

上一篇文章讲诉了纵向限流,那么为什么还需要横向限流呢?如下图所示:

流量控制

解决限流不均匀问题 : 如上图所示纵向限流只解决了网关-服务1,网关-服务2,网关-服务N的纵向路径的限流问题,但是并没有解决 这几个服务路径的限流是否均匀的问题,比如在某些情况下,网关-服务1 QPS 是 200,网关-服务2 QPS是 500,网关-服务N QPS 是 20,但是服务1-服务N的配置都是一样的,很明显,这里的限流并不均匀。

更细粒度的用户/租户的限流问题 : 如上图所示,用户1-用户N都发请求到网关,但是想限制每个用户可以进入系统的请求的个数,这里纵向限流并没有办法统计并控制每个用户的可以进入系统的请求数,纵向限流只能限制整体的进入网关的请求数,因此需要一个计数中心用于登记每个用户的请求数,从而进行更细粒度的流量控制,控制每个用户的请求数。


横向限流模式

如下图,通常采用一个类似配置中心或分布式事务中心的方式实现横向限流。

限流中心

  • 如左图所示,将集群限流服务中心实现在一个网关实例里,与网关一起提供服务,好处是无需再独立部署一个限流实例,缺点是网关如果挂掉,那么限流服务也会一起挂掉,而且无法对网关进行横向限流,只实现了网关底下的服务的横向限流;

  • 如右图所示,独立拉起一个集群限流服务中心实例,用于提供全局限流计数服务,好处是与业务解耦,缺点是在集群内增加了一个额外的服务实例,增加了系统复杂度。

横向限流模式设计思路

常用的横向限流算法有计数算法以及时间标签算法。

计数算法

如图所示,独立的限流服务中心,拉起一个独立的分布式配置中心/事务中心,在里头实现限流算法,比如固定窗口算法、滑动窗口算法、漏桶算法、令牌桶算法等用于全局计数,而且保证这个计数是全局唯一的,不管集群规模多大,保证每个服务所使用的计数器和计时器都是唯一的,服务拿到这个计数ID后在进行限流调度。


全局限流算法

  • CP模式:采用独立的限流中心,如果每个用户进入系统的请求都需要去远程的限流服务中心取一个计数返回,这就多了一个远程读取限流计数值的过程,很明显增加的这一步会影响请求的性能,但是在某些对限流可靠性比较苛刻的场景里,这是以牺牲请求性能的方式换取限流的可靠性;

  • AP模式:计数还是在限流服务中心,但在本地维护了一个限流计数的缓存,这样每个用户进来的请求并不是去远程读取计数值,而是直接在本地获取限流计数,而这个限流计数是通过一个独立的调度线程维护着的,这里这个本地的限流计数与远程限流服务中心的限流计数是不保证一致性的,这种方式牺牲了限流的可靠性,但是保证了请求的性能,在对限流要求不是很苛刻的场景下比较合适,而且配合纵向限流,还是可以解决绝大部分的系统的限流调度问题的。

时间标签算法

计数算法只是实现了限制用户或者服务请求量的最大值,并不能提供最小值保障,因此基于时间标签的算法被提出,例如DMCLOCK算法[1]。
在dmclock算法里,不只实现了限流,还实现了用户权重的划分以及最小值的预留。

例如在云服务里,用户1与用户2,付费不一样,因此给提供的最大限流上限是不一样的,但是采用计数限流算法,并不能保证付费多的用户就一定能得到最低的服务质量保证,在系统负载高的时候,付费高的用户与付费低的用户一样难以得到服务资源保底,这是不合理的,因此需要一个可以预留资源的算法。如下图所示,系统里流量资源的调度可以按 “预留、权重、上限” 这三个维度进行调度。


流量预留

例如,在时间标签算法里, 有三个用户:user1, user2,user3 根据付费的高低,给他们分别分配了不同的权重与预留值。如果系统里最大的QPS资源量是 4000,用户1与拥护2的QPS预留值是 1000,权重比例是 1:2:3.那么应当如何分配这些QPS资源? 按dmclock算法可以这样计算:

用户1 : (4000/(1+2+3)) 1 = 667 < 1000,但是保底的QPS是1000,因此分配了1000 QPS 给用户1.
用户2:( (4000-800) / (2+3))
2 = 1200 QPS
用户 3:4000-1200 – 667 = 2133 QPS

基本的计算思路是,先保证最低的预留值,再根据权重划分剩下的资源,并且保证不要超过最大值。

算法实践

  • 通常如非必要或者业务场景要求苛刻,纵向限流就够,实现横向限流会引入新的组件,增加复杂度,同时还影响系统性能;

  • 如果必须实现横向限流,那么性能要求高就采用AP模式,限流可靠性要求高就采用 CP模式;

  • 权衡利弊,根据业务场景合理组合纵向限流与横向限流,才是最佳实践。

小结

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

作者简介

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

版权申明

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

参考资料

[1]https://github.com/ceph/dmclock

导读

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

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

动机

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

可用性 : 限流有利于控制系统资源的消耗速率有利于过载保护,有利于保护业务资源不被耗尽。例如,当服务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

导读

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

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

动机

  1. 某些时候系统会遇到负载过高的问题,当系统外来的或内部的负载过高超过预先定义的阈值,为了保证更重要的更紧急的业务的服务质量,希望将一些非核心的、不紧急的业务降低服务质量,从而释放一些额外的资源给紧急业务使用。比如一个分布式系统里的读、写、数据校验、空间回收都比较消耗资源,在白天为了保证读和写的服务质量,可以把数据校验的服务通过限流或减少线程数之类的方式使得可以调用的资源配额减少,从而释放部分资源给读和写使用,保证读写的服务质量。同样在读和写业务不繁忙的时候,降低读和写的资源配额,从而释放资源给空间回收使用,通过这种方式动态调整局部业务的服务质量从而保证关键业务的服务质量,提升用户体验。

  2. 在云服务里“可用性”指标是一个非常重要的SLA指标,在可用性出现不达标的情况下需要根据SLA进行赔偿,因此,我们希望分布式系统不管出现怎么样的故障,比如服务器故障,磁盘故障,网络故障都能保持可用性,起码要保证单点故障不会造成系统故障,比如,在系统出现严重故障的时候,可以停止负载较高的写操作从而保证“读”或者“查询“服务。

降级模式

从故障处理角度来讲,服务降级简单来说就是这一功能或服务直接不能用,而在动态调整系统整体的服务质量的时候,降级是降低某些当前非重要或非核心业务的资源,从而释放部分资源给重要的或紧急的业务使用。

在故障处理的时候,对比“熔断”,降级是更严重的故障处理方式,最后拿来兜底用的。比如某个功能出故障,“熔断”是不管怎么样,都希望这个功能还能救活,降级是发现试着救了几次发现还是救不活,就下狠心砍掉这个部分,断臂求生,起码要保证整体是活的,这样整体还有救活的希望。

从系统的角度来说降级有 读功能降级,写功能降级以及级联组件降级,还有自动降级或者人工降级。比如,在云服务里,为了保证高可用性,在出现系统级的故障后,可以把写功能降级,就是这个服务只能读,只能查询不能写了,因此在设计的比较好的云服务里,按时间的维度来度量可用性已经没有太大的意义,因为不管怎么样它都是服务可用的,系统都是活着的,起码部分服务可用,因此在云服务里更合理的新的衡量可用性的指标方式是请求失败比率。

降级模式设计思路

降级触发策略

  • 超时降级:在超时重试的次数达到一个阈值后就触发降级;
  • 失败比率降级:当某个服务的失败的比率达到一定比率后就开始降级;
  • 系统故障降级:比如网络故障,硬盘故障,电源故障,服务器故障,数据中心故障等;
  • 限流降级:某些访问量太大的场景会触发限流,当达到限流阈值后,请求也会被降级;
  • 重要业务救急降级:比如为了保证读或者查询的功能,降低写或者数据校验的资源配额,从而实现读服务的质量保证。

降级处理措施

  • 资源配额调度,调度不紧急的业务支援紧急的重要的业务;
  • 抛出异常,直接抛出异常,打印出出错日志,然后就不管了,请求会丢失,这在需要保证幂等性的请求里不合适;
  • 直接返回, 直接返回拒绝服务,这里请求也会丢失,这在需要保证幂等性的请求里不合适;
  • 调用回退方法,调用出现服务降级时对应的业务处理逻辑,不同场景降级处理的逻辑不同,比如可以把请求再挂到等待队列里继续重试之类,这里需要根据业务场景合理设计回退方法;

降级分级策略

一般可以把降级的等级分为几个层次,比如P0级,P1级,P2级,P3级,级别越高表示问题越严重, 比如:

  1. 重要业务救急降级可以定义为P0级降级,只是调度次要的资源去救急,并不会出现故障;
  2. 限流降级可以定义为P1级降级,只是为了保证服务质量,而且如果不限流可能会出现系统负载过高从而出现故障;
  3. 超时降级以及失败比率降级可以定义为P2级降级,出现小范围故障,触发P2级降级,保证小故障不蔓延不传播从而造成大范围的故障;
  4. 系统故障降级可以定义为P3级降级,系统出现大范围故障,从而触发P3级降级,比如,此时可以只保证最低资源的的读请求服务,写和其他业务全部被禁止。

配置中心

如下图所示是一个简单的配置中心物理架构图:

配置中心

在分布式系统里每个服务的配置信息会给保存在一个配置中心里,这个配置中心里有每个服务的开关信息以及一些重要的资源配置信息。通过动态调整服务的配置信息,比如降级触发策略、降级处理措施、降级分级策略或者开关信息可以实现服务降级功能。

小结

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

作者简介

常平,中科大硕,10年+数据相关经验,主要工作背景为分布式系统、存储、缓存、微服务、云计算以及大数据,现就职于DELL EMC。

版权申明

本文的版权协议为 CC-BY-NC-ND license:https://creativecommons.org/licenses/by-nc-nd/3.0/deed.zh

在遵循署名、非商业使用(以获利为准)以及禁止演绎的前提下可以自由阅读、分享、转发、复制、分发等。

参考资料

[1] https://medium.com/@felipedutratine/microservices-should-be-design-for-failure-b58bccdce0b6

[2] https://blog.risingstack.com/designing-microservices-architecture-for-failure

导读

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

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

动机

在分布式系统里经常会遇到这样的场景:

  1. 系统负载突然过高,比如突发的访问量、过多的请求并发数以及过多的IO等都会造成某个节点故障,比如节点A,然后节点A挂了,又把负载转给节点B,然后节点B又负载过高,接着B又挂了,就这样一连串的挂过去从单点故障造成系统级的级联故障。

  2. 当一个服务出现故障时,希望这个服务能在一个时间段内恢复,在请求被拒绝后隔一段时间再自动的去探测服务的可服务性。

对应这两个场景,我们希望在分布式系统里能避免级联故障、提供快速失败快速恢复服务的能力,因此,这里引出 “熔断模式”

熔断模式

熔断模式也称之为断路器模式,英文单词是“circuit breaker”,“circuit breaker”是一个电路开关,其基本功能是检测到电流过载就中断电路,在检测到电流正常时又能自动或手动恢复工作,从而保护断路器背后的电源设备安全。这里需要将”断路器“与 “保险丝”进行区分,断路器可以通过手动或自动的复位从而恢复正常工作,而保险丝是运行一次必须更换。

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

在分布式系统里 “熔断模式”的设计思想来源于此,当系统里响应时间或者异常比率或者异常数超过某个阈值时,比如超时次数或重试次数超过某个阈值就会触发熔断,接着所有的调用都快速失败,从而保证下游系统的负载安全,在断开一段时间后,熔断器又打开一点试着让部分请求负载通过,如果这些请求成功那么断路器就恢复正常工作,如果继续失败,则继续关闭服务走快速失败通道,接着继续这个过程直到重试的次数超过一定的阈值从而触发更为严重的“降级模式”

熔断模式设计思路

下图是一个熔断模式的设计思路:

熔断器

图片来源于引文[2],版权归原作者所有

  1. 首先熔断器是处于闭合(closed)状态的,如果请求超时次数,异常数或者异常比率超过一定的阈值则熔断器会被打开;

  2. 接着熔断器处于打开(Open)状态,所有走到这个路径里的请求会走快速失败通道从而避免负载下行,但是这里不会一直都是打开的,过一个时间周期会自动切换到半打开(Half-open)状态;

  3. 在接下来是半打开(half-open)状态,在这里认为之前的错误可能被修复了,因此允许通过部分请求试着看看能不能处理成功,如果这些请求处理成功,那么就认为之前导致失败的错误已被修复,此时熔断器就切换到闭合状态并且将错误计数器重置。如果这些试着发送的请求还是处理失败,则认为导致之前失败的问题仍然存在,熔断器切回到打开方式,然后开始重置计时器给系统一定的时间来修复错误。半打开状态能够有效防止正在恢复中的服务被突然而来的大量请求再次打挂;

  4. 接着重复以上过程,直到半打开状态重复的次数达到一定的阈值发现故障还没被修复,从而触发”降级“状态

小结

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

作者简介

常平,中科大硕,10年+数据相关经验,主要工作背景为分布式系统、存储、缓存、微服务、云计算以及大数据,现就职于DELL EMC。

版权申明

本文的版权协议为 CC-BY-NC-ND license:https://creativecommons.org/licenses/by-nc-nd/3.0/deed.zh

在遵循署名、非商业使用(以获利为准)以及禁止演绎的前提下可以自由阅读、分享、转发、复制、分发等。

参考资料

[1] https://en.wikipedia.org/wiki/Circuit_breaker

[2] https://martinfowler.com/bliki/CircuitBreaker.html

导读

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

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

动机

在分布式系统里通常将进程容器化以进行资源隔离,然后在同一个进程里的各种业务都共享线程池对外提供服务,这就经常会遇到这样的问题:

  1. 业务A负载较高,抢占了线程池里的大部分线程资源,从而造成其他业务的服务质量下降;
  2. 同一个进程内新加入一个业务,这个业务会抢占其他业务的资源,从而造成系统的不稳定,比如业务性能抖动;
  3. 难以调试,比如同一个进程里的10个业务共享同一个线程池,当出现故障时难以通过简单的日志判断是哪个业务出了问题。

因此,我们希望找出一个机制解决这样的问题。

隔板模式

首先我来看一个英文单词“Bulkhead”,翻译成中文就是“舱壁”‘或“隔板”,在分布式系统里有个资源隔离的设计模式叫做”舱壁模式”或者“隔板模式”。

模式来源: 通过万能的Wiki百科我们可以了解到轮船里的两个舱位之间的挡板就是隔板/舱壁(Bulkhead),如下图:


bulkhead

图片来源于引文[1],版权归原作者所有




在造船的时候,”船农们“(对应码农)通常会把一个大的船舱用隔板分成N个小的空间,以便万一船体破裂或着火的时候,只有这个被分割开的小船舱受到影响,而其他的船舱是被隔离而不受影响的,从而提高整个船只的安全度。

同样这种隔板模式可以应用在分布式系统的资源隔离设计里,在分布式系统里,资源隔离通常按业务分为进程级别的隔离和线程级别的隔离,某些简单的服务质量要求不高的业务场景下实现进程级别的隔离就够了,但是在某些对服务质量要求较高的分布式场景下需要线程级别的细粒度隔离。


进程隔离

进程级别隔离通常指的是容器化隔离,比如通过使用docker实现业务进程之间的资源隔离。

线程隔离

线程级别隔离是指给每个跑在进程里的业务都按业务类型创建一个线程池,从而实现线程级别细粒度的资源隔离,线程隔离具有以下优势:

  1. 提高业务可靠性,减少业务受其他业务影响的程度,当一个业务耗尽自身的线程资源后也不会影响另外一个业务的服务质量;
  2. 降低新加入的业务的给系统带来的风险,比如当前系统的一个进程用例中有10个业务。当新加入一个业务时,必然会抢占此前10个业务的线程资源,从而给系统带来不稳定,比如性能抖动;
  3. 利于调试,给每一个业务都分配一个线程池名称,当业务出故障时,通过线程池名称可以很方便地定位是哪个业务出了故障,并且通过监控线程池的请求失败次数、超时次数、拒绝请求次数等可以实时的反应当前业务服务质量。

事物都有二元性,线程池隔离,有利自然也有弊,线程池隔离也会引入额外的一些开销,开销类型有:

  1. 对象分配,每个调用都会实例化一个新的线程对象及其中的关联对象,占用系统资源;
  2. 并发,共享数据结构,计数器等,也占用系统资源;
  3. 线程的执行开销:切换,调度,执行,同样也占用资源。

因此,线程池的隔离带来了好处但是也会引起一些顾虑,比如给每个业务都创建一个线程池是否会给系统带来太大的开销。通过Hystrix的数据分析可以得出结论是: “开销是有的,但是对比好处,通过权衡,其开销在一些要求不苛刻的场景可以忽略。”

线程池的开销分析

Hystrix官网[3],统计了线程池带来的开销成本,如下图表示在单个API实例上以60个请求/秒执行一个HystrixCommand:

Hystrix

图片来源于引文[3],版权归原作者所有

通过分析这个统计图(注意不同的颜色),我们可以看到:

  1. 中位数(P50)和更低的场景下,对比不使用线程池隔离模式,隔离线程池基本没有成本开销。
  2. 在P90的场景下,对比不使用线程池隔离模式,隔离线程池的耗时差距为3毫秒。
  3. 在P99的场景下,对比不使用线程池隔离模式,隔离线程池的耗时差距为9毫秒。

但是从上图可以看出,成本增加的幅度远小于单独一个线程的执行时间增加的幅度,当未使用线程池隔离的线程执行时间从2ms跳到28ms时,线程池隔离的耗时成本从0ms跳到9ms。

因此,对于大多数的使用场景而言,在P90及以上的线程池隔离带来的开销被认为是可接受的,从而获得资源隔离带来的好处。

但是在某些场景这样的开销可能过高,比如缓存场景,在这种情况下,可以选用信号量来进行隔离,缺点是信号量不允许设置超时,难以实现熔断、降级之类的服务治理行为。

小结

本文讲诉了服务治理里的 “隔板”模式,在下一篇将讲诉分布式系统服务治理的熔断模式。另作者能力与认知都有限,欢迎大家拍砖留念。

作者简介

常平,中科大硕,10年+数据相关经验,主要工作背景为分布式系统、存储、缓存、微服务、云计算以及大数据,现就职于DELL EMC。

版权申明

本文的版权协议为 CC-BY-NC-ND license:https://creativecommons.org/licenses/by-nc-nd/3.0/deed.zh

在遵循署名、非商业使用(以获利为准)以及禁止演绎的前提下可以自由阅读、分享、转发、复制、分发等。

参考资料

[1] https://en.wikipedia.org/wiki/Bulkhead_(partition)

[2] http://writing.engr.psu.edu/uer/bassett.html

[3] https://github.com/Netflix/Hystrix/wiki/FAQ%20:%20General