【系统架构设计】限流与过载保护:保命的最后一道防线

💡 原文中文,约37200字,阅读约需89分钟。
📝

内容提要

本文讨论入站过载保护,即服务作为被调用方在流量超过自身处理能力时的自我保护。文章区分限流与熔断,介绍令牌桶、漏桶等限流算法,并阐述分层部署、负载削减、自适应控制及多租户公平性等策略。同时指出限流配置不当可能引发的故障,并提供工程实践清单。

🔎

延伸解读

限流与熔断:方向相反,互为补充

文章明确指出,限流保护的是服务自身,熔断保护的是调用方,两者解决的是方向相反的问题。一个健康的下游完全可能被合法流量打垮,此时熔断器无法触发,唯一有效的手段是在入口控制流量。理解这一区别,才能在“下游故障”和“入站过载”两种场景下选对工具,避免用错预案导致处置效率大打折扣。

算法选择:突发容忍度是关键

令牌桶允许突发,漏桶强制整形,固定窗口有边界突刺,滑动窗口用复杂度换精度。选择哪种算法取决于下游资源对突发的容忍度:有状态、扩容慢的资源(如数据库连接池)适合漏桶,无状态、能快速消化的资源(如异步任务)适合令牌桶。实际系统常在不同层次搭配多种算法,不存在全场景最优解。

并发限制优于QPS限制

QPS限制隐含假设每个请求消耗的资源稳定,但过载时处理时间会恶化,导致并发数暴涨而QPS不变。利特尔法则(L=λR)表明,并发数才是与线程池、连接池同量纲的指标。Netflix的concurrency-limits库因此直接限制并发数,能更直接反映资源耗尽风险,避免为每个下游试算QPS阈值。

状态码语义:429与503不可混用

限流应返回429并携带Retry-After,系统性过载返回503。若混用,客户端可能将503误判为实例不健康,触发不必要的摘除;若不带Retry-After,客户端固定间隔重试会形成新的突发。正确语义能引导客户端退避,避免限流本身成为重试放大的源头。

Q&A

限流和熔断有什么区别?

限流保护的是服务自身,防止过量的入站请求耗尽自己的资源;熔断保护的是调用方,防止调用方因为下游故障而被拖垮。两者方向相反,互补而非互斥。

常见的限流算法有哪些?它们各自有什么特点?

常见限流算法包括固定窗口计数器、滑动窗口日志、滑动窗口计数器、令牌桶和漏桶。固定窗口简单但有边界突刺;滑动窗口日志精确但内存开销大;滑动窗口计数器用近似换效率;令牌桶允许突发;漏桶强制整形为恒定速率。

如何区分入站过载和下游故障?

可以通过监控信号区分:看出站调用错误率是否平稳、自身资源水位是否快速上升、入站QPS或并发数是否阶跃上升、熔断器是否触发。若出站错误率平稳而自身资源水位高,则多为入站过载。

限流应该部署在哪些层次?

限流应分层部署:客户端层(自适应节流)、网关层(粗粒度全局控制)、服务端层(细粒度按资源)。各层分工不同,缺一不可。

为什么并发限制比QPS限制更有效?

因为QPS限制假设每个请求消耗资源稳定,但过载时延迟升高,相同QPS下并发数会暴涨,导致线程池、连接池等资源耗尽。并发限制直接限制在途请求数,与资源容量同量纲,更直接反映风险。

限流时应该返回什么HTTP状态码?为什么?

限流场景应返回429(客户端超限)并携带Retry-After;系统性过载返回503并携带Retry-After。避免使用500或200,以免客户端误判重试或监控误判成功。

分布式限流有哪些挑战?如何解决?

分布式限流面临一致性与延迟的权衡:本地限流精度随副本数漂移,集中式限流引入网络往返和竞态。解决方案包括使用Redis原子操作(Lua脚本)、按区域分配配额、或采用论文中的三种算法(全局令牌桶、全局随机丢弃、流量比例分享)。

如何防止限流本身引发故障?

防止限流引发故障需注意:正确使用状态码和Retry-After,避免客户端同步重试;设置合理阈值并定期复核;对多租户设置独立配额;监控限流器自身状态;进行故障演练。

🏷️

标签

➡️

继续阅读