编辑推荐: |
本文来自于infoq,本文分享了已经在微信生产环境中运行了五年的过载控制系统
DAGOR 的设计方案。 |
|
微信
此时的微信后端由 3000 多个移动服务组成,包括即时消息、社交网络、移动支付和第三方授权。平台每天的外部请求达到了
10^10 到 10^11 个。每个这样的请求都可以触发更多内部的微服务请求,从整体来看,微信后端需要每秒处理数亿个请求。
微信微服务系统的 3000 多项服务运行在 20,000 多台机器上,随着微信的广泛普及,这些数字在不断增加…随着微信的不断发展,微服务系统一直在快速进行服务更新迭代。从
2018 年的 3 月到 5 月,微信微服务系统平均每天发生近千次的变更。
微信将微服务分为“入门跳板”服务(接收外部请求的前端服务)、“共享跳板”服务(中间层协调服务)和“基本服务”(不再向其他服务发出请求的服务,也就是充当请求的接收器)。
在典型的一天,峰值请求率约为日常平均值的 3 倍。在一年中的某些时候(例如中国农历新年期间),峰值工作负载可以上升到日常平均值的
10 倍。
基于大规模微服务平台的过载控制挑战
过载控制对于大规模在线应用程序来说至关重要,这些应用程序需要在不可预测的负载激增的情况下实现 24×7
服务可用性。
传统的过载控制机制是为具有少量服务组件、相对狭窄的“前门”和普通依赖关系的系统而设计的。
现代在线服务在架构和依赖性方面正变得越来越复杂,远远超出了传统过载控制的设计目标。
由于发送到微信后端的服务请求没有单一的入口点,因此传统的全局入口点(网关)集中负载监控方法并不适用。
特定请求的服务调用图可能依赖于特定于请求的数据和服务参数,即使对于相同类型的请求也是如此。因此,当特定服务出现过载时,很难确定应该限制哪些类型的请求以缓解这种情况。
过多的请求中止浪费了计算资源,并由于高延迟而影响了用户体验。
由于服务 DAG 极其复杂,而且在不断演化,导致有效的跨服务协调的维护成本和系统开销过高。
由于一个服务可能会向它所依赖的服务发出多个请求,并且还可能向多个后端服务发出请求,因此我们必须特别注意过载控制。作者发明了一个术语,叫作后续过载,用于描述调用多个过载服务或多次调用单个过载服务的情况。
后续过载给有效的过载控制带来了挑战。当服务过载时随机执行减载可以让系统维持饱和的吞吐量。但后续过载可能会超预期大大降低系统吞吐量…
试想一个简单的场景,其中服务 A 两次调用服务 B,如果 B 开始拒绝一半传入的请求,那么 A 的成功概率就降到了
0.25。
DAGOR 概览
微信的过载控制系统叫作 DAGOR。它旨在为所有服务提供过载控制,因此具有服务无关性。由于集中式全局协调成本过高,因此过载控制是以单台机器的粒度运行的。不过,它使用了一种用于处理后续过载所需的轻量级机器间协作协议。最后,当发生过载不得不进行减载时,DAGOR
应该尽可能维持服务的成功率,并尽量减少在失败的服务任务上花费过多的计算资源(例如 CPU、I/O)。
我们需要完成两个基本的任务:过载检测,并在检测到过载之后决定如何处理它。
过载检测
对于过载检测,DAGOR 使用了待处理队列中的请求的平均等待时间(即排队时间)。排队时间可以抵消调用图中较低的延迟所带来的影响(与请求处理时间相比)。即使本地服务器本身没有过载,请求处理时间也会增加。DAGOR
使用基于窗口的监控,每个窗口是一秒钟,或者 2000 个请求,以先达到者为准。
对于过载检测,假设 WeChat 中每个服务任务的默认超时为 500 毫秒,那么用于表示服务器过载的平均请求排队时间的阈值被设置为
20 毫秒。这种配置已经在微信业务系统中应用了五年多,微信业务活动的稳健性已经证实了这种配置的有效性。
准入控制
一旦检测到过载,我们就必须决定如何处理它,或者直接丢弃某些请求。我们发现,并非所有的请求都是平等的:
微信的操作日志显示,当微信支付和即时消息遇到服务不可用时,用户针对微信支付服务的投诉是针对即时消息服务的
100 倍。
为了以服务无关的方式处理这个问题,每个请求在首次进入系统时都会被赋予业务优先级。这个优先级会随所有下游请求一起流动。用户请求的业务优先级由请求的操作类型来决定。虽然有数百个入口点,但只有几十个具有显式优先级,其他入口点具有默认(较低)优先级。优先级保留在复制的哈希表中。
当过载控制被设置为业务优先级 n 时,将删除所有级别为 n + 1 的请求。这对于混合工作负载来说非常有效,但是假设我们有大量的付款请求,所有这些请求都具有相同的优先级(例如
p)。系统将出现过载,这个时候将过载阈值降至 p-1。一旦过载消失,过载阈值再次增加到 p,于是我们又处于过载状态。要在相同优先级的请求发生过载时避免这种翻转,我们需要比业务优先级更细粒度的控制级别。
微信提供了一个很好的解决方案,它增加了基于用户 ID 的第二层准入控制。
入口服务通过以用户 ID 作为参数的哈希函数来动态生成用户优先级。每个入口服务每小时更改其哈希函数。因此,来自同一用户的请求很可能在一小时内被分配了相同的用户优先级,但在几小时内被分配了不同的用户优先级。
这提供了一种公平性,同时还在相对长的时间段内为个人用户提供一致的体验。它还有助于解决后续过载问题,因为具有高优先级的用户请求更有可能在整个调用图中得到处理。
通过组合业务优先级和用户优先级,可以为每个业务优先级提供 128 个用户优先级准入级别。
由于每个业务优先级的准入级别具有 128 个用户优先级,因此复合准入级别数量可以达到数万。复合准入级别的调整是按照用户优先级的颗粒进行的。
这里有个有趣的问题,为什么使用会话 ID 代替用户 ID 就不起作用:在遇到糟糕的服务时,这样会导致用户登录登出,那么在过载问题之上又会多出来一个用户登录问题!
DAGOR 为每个服务器维护一个请求的直方图,用于跟踪超过准入优先级的请求的大致分布情况。当在窗口期间检测到过载时,DAGOR
移动到第一个桶,将使预期负载减少 5%。如果没有发生过载,它会移动到第一个桶,将使预期负载增加 1%。
服务器将准入优先级嵌入到发送给上游服务器的每个响应消息中。通过这种方式,上游服务器获知下游服务的当前准入控制设置,可以在发送请求之前对请求执行本地准入控制。
DAGOR 过载控制系统的端到端视图如下所示:
实验
DAGOR 在微信的生产环境已经运作五年了,这是对它的设计可行性的最好证明。但我们并没有为学术论文提供必要的图表,所以我们进行了一组模拟实验。下面的图表突出显示了基于排队时间而非响应时间的过载控制的好处。在发生后续过载的情况下,这些好处最为明显(图
(b))。
与 CoDel 和 SEDA 相比,在进行一次后续调用时,DAGOR 的请求成功率提高了 50%,随后出现过载。后续请求数量越多,好处就越大:
最后,在公平性方面,CoDel 在压力下更倾向于使用较小的扇出服务,而 DAGOR 在各种请求中表现出大致相同的成功率。
三个经验教训
即使你不使用 DAGOR,作者还是为你总结了三个宝贵的经验教训:
大规模微服务架构中的过载控制必须在每个服务中实现分散和自治;
过载控制应该要考虑到各种反馈机制(例如 DAGOR 的协作准入控制),而不是仅仅依赖于开环启发式;
应该通过分析实际工作负载来了解过载控制设计。 |