压测、限流和故障演练是稳定性建设的「三把斧」,然而在实践中,限流方案的设计往往非常复杂,实际效果却通常不尽如人意,这又是为什么呢?

限流是有损的

限流的根本目的是保护己方服务不被打垮,这也就意味着超出阈值的流量会被服务端直接丢弃,这对用户体验来说是灾难性的。最明显的例子当属秒杀场景下的限流,触发限流后用户的正常请求总是被随机拒绝,这也是我们抢购券或商品时需要「拼手速」的根因之一。

而正是由于存在这样的用户体验和调用成功率问题,限流的阈值设定也就尤为重要,阈值配置准确则能在服务可用性和用户体验上找寻到一个平衡点,反之即使保护了服务也仍然算是造成了线上影响。

捉摸不透的限流阈值

限流的阈值虽然如此重要,但往往阈值方案的制定却是「拍脑袋」来定的。

我们通常会根据两个指标来确定阈值,即「单机极限容量」和「集群极限容量」,两者都需要结合日常峰值流量中服务的表现和压测的结论来得出。其中,集群容量的评估并不能简单地以单机容量乘以机器数得出。原因在于评估单机容量时采取的单机压测往往难以体现出 PaaS 层和上下游依赖的容量瓶颈,有可能集群容量达到同样的数值时,应用服务本身仍能运行,但外部依赖就已经先行无法响应了。

另一方面,高仿真的压测是困难的,其难点体现在以下几方面:

  • 压测流量很难完全模拟真实流量的请求分布。
  • 写逻辑的压测十分复杂,难以百分百还原真实场景,各类 mock 逻辑也会使得压测结论失真。
  • 压测往往在低峰期进行,因此除非是涉及全集团所有服务的全链路压测,否则宿主机上可能只有一两个容器在保持高水位运行,与真实流量发生时的情况差异很大。

因此大部分压测的结论只能作为一个参考,实际设置限流阈值时往往需要配置者根据经验预留出一部分的 buffer 。服务越基础、调用方越多,这个问题也就越严重。

此外,限流的阈值配置并不是件一劳永逸的事情,业务逻辑和受众的调整都有可能导致预期请求量的上升或下降,进而造成原来稳定运行的限流策略失效。

单机限流的局限性

无论大厂小厂,单机限流已俨然成为标配。单机限流简单易用,假设我们确实评估出了服务的单机容量极限,加上单机限流后,我们至少能保证服务在单机层面上可以应对极端流量。

但上面提到限流是有损的,因此对于流量大、业务方众多的服务来说,粗暴的单机限流策略的误伤比例是极高的。而当我们希望按照请求参数、业务方的身份信息来做细化的限流配置来解决这一问题时,往往会发现对于只有几十 QPS 的小业务来说分配到单机的流量配额只有个位数,流量稍有不均衡就会造成误限酿成事故。而如果一段时间没有对业务容量配额进行管理,那么各个业务方的配额总额很可能已经接近或超过了单机容量极限。这时我们就只能选择扩容,但扩容又意味着要对原有的策略逐个进行调整,过程不可谓不麻烦。

因此单机限流更适合只按单机容量极限进行简单配置,作为保障系统稳定运行的兜底策略,而不适合配置精细化的限流策略。

集群限流的困境

目前的集群限流思路一般有如下两种:

  1. 非精确集群限流:原理上与单机限流无异,只是增加了中心化的配置入口,可以自动化地将集群阈值与机器数量进行计算来动态调整单机限流阈值。
  2. 精确集群限流:通常基于 Redis 这类耗时低、TPS 容量高的 KV 实现,不同的限流策略请求 KV 组件的时机也不同。当然现在也出现了基于应用层机器的实现如 Sentinel ,但本质上都是通过中心化的节点来把控是否应对某个请求进行限制。

前者并没有很好地解决单机限流中存在的问题,只是把单机限流的操作简化了。而后者虽然能真正地根据请求特征做到细粒度限流,但无论是哪种实现都会造成一定的性能损失。可见集群限流虽然能真正满足各类限流场景,但目前的技术实现并不适用于所有类型的业务,对于 TP99 只有一两毫秒的业务来说就需要对性能和稳定性进行权衡了。

应用层限流的风险

以上的内容都限定在了应用层的限流。而实际上在突发的秒级大流量之下,即使配置了应用层的限流,大量的请求堆积很有可能会将线程池、TCP 队列、堆(对于 Java 应用)瞬间打满造成服务挂掉,进而导致限流无法发挥出实际作用。

因此我们不能完全依赖应用层的限流,而应该在流量入口层,如处理 HTTP 请求常用的 Nginx 、Service Mesh 架构下的 SideCar 组件中也进行粗粒度、有 buffer 预留的限流配置。这些组件的处理流程耗时非常低、吞吐量很高且常常部署于高配容器或独立宿主机中,能够承载更高量级的突发流量,进而为应用层限流方案补位。

限流不是银弹

精确限流其实是较危险且当前仍缺乏最佳实践的操作,需要有科学的实验、细致的分析和周密的方案,同时需要由底层基础组件提供有效的能力支撑。而当上述条件得不到满足时,过于追求精确限流反而更容易导致事故。

我们必须要认识到限流并不是银弹,也不是稳定性建设的唯一「抓手」,在能力和方案都不能满足精确限流要求的情况下,以粗粒度的限流方案进行稳定性兜底,着手建设弹性扩容、流量隔离等方向的措施才是更明智的选择。

原文作者: Eason Yang

原文链接: https://easonyang.com/2021/07/24/why-backend-flow-control-is-so-difficult/

许可协议: 知识共享署名-非商业性使用 4.0 国际许可协议