Uber 的全局限流系统:从零散 Redis 限流到服务网格里的自动化流量保护

发布时间:2026/7/8 5:54:15
Uber 的全局限流系统:从零散 Redis 限流到服务网格里的自动化流量保护 本文是对 Uber’s Rate Limiting System 的整理与翻译。内容结构概览本文会围绕 Uber 如何构建大规模全局限流系统展开主要包括为什么 Uber 需要统一限流系统早期微服务限流为什么碎片化、难维护、难扩展为什么 Redis 这种中心化计数器模型无法支撑 Uber 的规模Uber 为什么把限流能力嵌入 Service MeshGRL也就是 Global Rate Limiter 的三层架构数据面 client、zone aggregator、regional/global controller 分别做什么为什么热路径必须本地决策不能每个请求访问控制面最初的 token bucket 方案如何工作token bucket 在大规模生产中暴露出的公平性和突发流量问题drop-by-ratio 概念按全局超额比例概率丢弃请求为什么 Uber 最终从 token bucket 迁移到统一概率丢弃模型GRL 如何配置 caller、procedure、global/regional/zonal 限流规则shadow mode 为什么是大规模限流上线的关键GRL 上线后的收益延迟下降、Redis 依赖移除、可靠性提升一次关键服务如何扛住 15 倍流量突增GRL 如何在 DDoS 和 retry storm 中保护内部系统为什么手工 YAML 配置又成了新的问题RLC也就是 Rate Limit Configurator如何自动生成限流配置RLC 如何基于历史流量、预测模型和固定配额生成安全限额自动化配置如何把 GRL 从“静态保护层”变成“自维护控制系统”Uber 下一步如何继续缩小 blast radius、提高更新频率并结合应用层 throttler这套系统对普通后端工程师和平台团队有什么启发一、Uber 为什么需要全局限流系统Uber 是一个典型的大规模微服务系统。它的服务化架构每秒处理数亿级 RPC 请求背后是数千个服务之间的复杂调用关系。任何一个服务、调用方、客户端或者异常流量源如果没有控制都可能把下游服务打爆并进一步引发级联故障。限流在这里不是一个“锦上添花”的功能而是平台可靠性的基础设施。它要解决的问题包括防止单个调用方压垮被调服务 防止异常客户端消耗过多共享资源 防止 retry storm 放大故障 防止突发流量穿透到核心服务 在服务降级时保护关键路径 在事故处理中快速压制特定流量Uber 最终构建了 GRL也就是 Global Rate Limiter。它直接集成到 Uber 的 service mesh 中服务网格会转发 Uber 大多数服务之间的 RPC 请求因此在这一层做限流可以覆盖绝大部分内部服务流量。后来GRL 又继续演进出 RLC也就是 Rate Limit Configurator。RLC 会根据历史真实流量和策略自动更新限流配置让限流规则不再依赖大量人工维护。所以这篇文章讲的是两个阶段第一阶段用 GRL 统一限流执行 第二阶段用 RLC 自动化限流配置最终目标是让 Uber 的微服务平台在极大流量、突发峰值、重试风暴和攻击流量下仍然能保持稳定、低延迟和可控。二、为什么限流很重要在分布式系统里限流是一种基础保护机制。它的核心作用不是“拒绝用户”而是保护系统在压力下还能工作。没有限流时一个服务可能遇到这样的情况上游流量突然增加 下游延迟变高 上游开始超时 超时后上游重试 重试又带来更多请求 下游更慢 更多请求超时 系统进入雪崩限流就是要在系统还没彻底崩掉之前主动丢弃超出容量的请求让核心服务保持可用。普通限流系统通常会用一个分布式计数器比如 Redis。基本逻辑是每个请求到来 ↓ 根据 caller 或 endpoint 生成 key ↓ 在 Redis 中对这个 key 计数 ↓ 如果计数超过阈值 ↓ 拒绝请求对很多中小规模系统来说这个方案简单有效。但 Uber 的问题在于规模远远超过普通系统。三、早期 Uber 的限流为什么碎片化在 Uber 微服务架构早期并没有一个统一限流系统。不同团队根据自己的需求实现自己的 throttling 逻辑。有些写在业务代码里有些写在自定义 middleware 中有些使用 Redis 作为中心计数器。这些实现单独看都能解决局部问题但放在整个公司规模下就会出现系统性缺陷。1. 配置不一致不同团队对“限流”的理解不同。同样是每秒 1000 请求有的系统按 caller 限有的按 endpoint 限有的按实例限有的按 Redis key 限。结果就是同样的 quota在不同服务里行为不同 同样的调用方在不同服务里被保护方式不同 事故发生时很难判断到底是谁在限流2. 运维成本高Redis-based limiter 需要维护 Redis 集群。这意味着Redis 本身要部署要做分片要做高可用要做监控要处理延迟要处理跨区一致性要处理 Redis 客户端库升级风险限流本来是为了降低系统风险但它自己又引入新的基础设施风险。3. 修改限流规则需要发版或人工操作很多早期限流逻辑和业务代码绑定。如果要改限制值可能需要重新部署服务。这在事故处理中非常糟糕。当某个服务正在被打爆时你不可能慢慢改代码、走发布流程、等待部署完成。限流系统应该能做到无需改业务代码 无需重新部署服务 几秒内改变限流行为早期分散方案做不到这一点。4. 维护风险高碎片化限流逻辑最大的风险是事故发生时没人知道它在哪里。某个服务突然返回大量 429到底是业务限流middleware 限流Redis 限流网关限流调用方限流还是下游服务错误如果限流代码散落在不同仓库、不同框架、不同语言里排障就会非常困难。5. 覆盖不均匀一些大型服务可能认真做了限流。很多小服务可能完全没有限流。但在微服务系统中小服务也可能处在关键链路上。一个小服务被打爆也可能拖垮一条核心业务路径。所以平台需要统一覆盖而不是靠每个团队自觉实现。6. 可观测性不统一每个限流器上报指标方式不同。有的报 drop count有的不报有的报 error有的报 429有的记录 caller有的不记录 procedure。这会导致一个问题当请求失败时很难判断失败是不是由限流导致的。统一限流系统必须提供统一指标、统一日志、统一错误语义和统一排障入口。四、为什么 Redis 中心计数器无法支撑 Uber 规模Redis 作为限流计数器在很多系统里很常见。但 Uber 发现它无法支撑自己的全局规模。原因很直接Uber 有数十万 hosts每秒处理数亿级请求并且跨多个 region。如果每个请求都要访问 Redis 做计数那么热路径就会变成请求到达服务网格 ↓ 访问 Redis ↓ 读取/更新计数器 ↓ 判断是否超过 quota ↓ 继续转发或拒绝这有几个严重问题。1. 每个请求多一次网络往返限流本来应该是极轻量的热路径逻辑。但 Redis 计数意味着每个请求都多一次远程访问。在低延迟 RPC 系统里这个成本非常高。2. Redis 集群数量会爆炸如果要支撑数亿请求每秒的实时计数就需要大量 Redis 集群。还要按 region、zone、caller、endpoint 分片。这会带来巨大的运维复杂度。3. 跨 region 一致性几乎不可接受限流经常需要“全局视角”。比如某个 caller 在多个 region 发流量系统希望限制它的全局请求量。但 Redis 计数器如果分布在多个 region就会遇到跨地域一致性问题。实时一致太贵不实时又不准。4. 定期同步计数也不够一种折中方案是每个地方本地计数然后定期同步。但 Uber 也排除了这个方案。因为定期同步会导致数据过时。如果某个 caller 突然暴涨系统可能几秒甚至更久后才感知到。这对突发流量和 retry storm 不够快。最终 Uber 得到结论中心化计数器无法同时满足低延迟、全局规模和实时性。唯一可行方向是完全分布式架构本地代理在热路径做决策控制面只负责聚合和下发策略。五、愿景统一的基础设施级限流器Uber 最终选择把限流能力集成到 service mesh。这个选择非常关键。Service mesh 位于服务间 RPC 流量的基础设施层。绝大多数服务间请求都会经过它。把限流放在这一层带来几个好处语言无关不管服务用什么语言都能被保护 框架无关不依赖具体业务框架 无需业务改代码服务只需要配置不需要改逻辑 覆盖面广所有经过 mesh 的 RPC 都可以被评估 热路径可控可以在 proxy 内本地做快速决策 统一观测指标、错误、drop ratio 都能统一上报Uber 的目标是为任意服务提供统一限流能力让团队可以按 caller、procedure 或两者组合配置 quota而且不需要改业务代码。但这个目标必须满足巨大规模每秒数亿请求 数万个 service pair 跨多个地理区域 对延迟影响极小 控制面故障时不能自毁这就是 GRL 的设计背景。六、GRL 的三层反馈架构GRL 的核心是一个三层反馈循环Rate-limit clients ↓ Zone-level aggregators ↓ Regional / global controllers可以理解成数据面负责本地执行 聚合层负责区域汇总 控制层负责计算全局限流策略1. Rate-limit clients服务网格数据面中的本地执行者Rate-limit client 运行在 service mesh data plane 中。它直接面对每个请求负责在热路径上做本地判断。它会根据请求匹配限流 bucket根据控制面下发的 drop ratio 决定是否丢弃本地快速放行或拒绝请求上报本机每秒请求计数给 zone aggregator关键点是每个请求的决策都是本地完成的不需要同步访问控制面。这保证了热路径低延迟。2. Aggregators每个 zone 的流量聚合器Aggregator 按 zone 部署。它负责收集本 zone 内所有 clients 上报的请求计数。然后计算 zone-level usage并把聚合结果继续发送给 controllers。为什么要有 aggregator因为如果所有 clients 直接向 global controller 上报控制面会被海量连接和指标打爆。Aggregator 相当于中间汇总层减少控制面的压力。3. Controllersregion/global 控制器Controller 位于 regional 或 global 层。它负责聚合各个 zone 的使用量与配置的限额比较计算当前 drop ratio把新的 drop directive 下发给 aggregators 和 clients也就是说复杂计算不在请求热路径上而是在控制面异步完成。七、为什么这种架构能同时兼顾低延迟和全局协调GRL 的核心取舍是请求热路径只做本地轻量判断 全局视角通过异步聚合形成这样带来两个效果。第一低延迟。每个请求不需要访问 Redis不需要访问控制面只需要本地随机采样和配置匹配。第二全局协调。虽然请求本地决策但 drop ratio 来自全局聚合结果因此系统仍然可以按 global/regional/zonal scope 控制整体流量。控制面更新可能有几秒延迟。但 Uber 认为这比每个请求访问中心计数器更可接受。另外如果控制面不可用clients 会 fail open。这点非常重要。fail open 的意思是控制面挂了 ↓ 数据面继续放行请求 ↓ 避免限流系统自己导致大面积故障限流系统是保护系统的不能因为控制面故障就把所有流量都拒绝掉。这就是基础设施系统里常见的设计原则控制面可以短暂不可用但数据面必须尽量继续服务。八、最初的执行算法Token BucketGRL 项目一开始实现的是 token bucket。Token bucket 是经典限流算法。它有一个桶桶里有 tokens。系统按一定速率往桶里补 token。每来一个请求消耗一个 token。如果桶里有 token请求通过。如果没有 token请求被限流。在 GRL 早期方案里每个 proxy 本地维护 token 状态。token rate 根据 proxy 本地负载与全局 limit 的关系计算。大致逻辑是token refill rate ratio × limitRPS如果请求到来时 token 可用就放行并消耗一个 token。否则标记为 rate-limited。为了处理短暂 burstclient 会把未使用 token 存在 circular buffer 中让它们在短时间突发流量里使用。默认情况下token 可以保留最多 10 秒。对于更突发的服务可以配置到 20 秒。这个方案在早期测试中能工作但到了生产环境它暴露出几个问题。九、Token Bucket 的生产问题1. caller 数量超过限制时不够公平如果一个 caller 有很多实例或者某个限流 bucket 下有很多请求来源token bucket 在每个 proxy 本地独立执行就很难做到全局公平分配。某些实例可能 token 充足某些实例却已经开始 drop。系统整体看可能还没超过全局限额但局部 proxy 已经发生过早拒绝。2. 实例级 burst 会导致提前丢弃假设某个 caller 的整体流量没超过全局 limit但它某些实例短时间流量很高。本地 token bucket 可能认为这个实例超了于是开始丢请求。但从全局看它其实还没超。这会导致合法流量被误杀。3. 大规模网关类服务场景不友好一些 gateway-style service 有成百上千个 caller instances。每个实例本地 token accounting 无法准确反映全局分布。在这种情况下基于全局聚合结果的统一 drop ratio 更适合。所以 Uber 开始引入 drop-by-ratio。十、Drop-by-ratio根据超额比例概率丢弃drop-by-ratio 的思想很简单如果全局流量超过 limit就按超出的比例概率丢弃请求。公式是drop_ratio (actual_rps - limit_rps) / actual_rps举个例子。某个 caller 的 limit 是 1000 RPS。实际全局流量是 1500 RPS。那么drop_ratio (1500 - 1000) / 1500 500 / 1500 0.333...也就是说所有该 caller 的实例大约丢弃 33% 请求。这样一来系统不再依赖每个 proxy 本地 token 状态而是用全局超额比例统一控制所有实例。这个方案有几个优点。第一超额流量被均匀地分布式丢弃。不是某个实例先被打爆而是所有实例根据同一个 drop ratio 共同执行。第二更适合大型 gateway-style 服务。当一个 caller 有大量实例时drop ratio 比本地 token bucket 更能反映全局状态。第三热路径更简单。每个请求只需要根据配置做一次概率判断。十一、从 Token Bucket 迁移到统一概率模型随着 GRL 成熟Uber 最终完全废弃 token bucket转向 control-plane-driven probabilistic dropping。原因是同时维护两套机制成本太高。Token bucket 需要维护本地 token、补充速率、burst buffer。drop-by-ratio 又需要控制面聚合和下发 drop 指令。两套机制并存会带来配置复杂 行为难解释 控制面带宽增加 客户端状态复杂 不同场景下决策不一致最终Uber 选择统一为概率丢弃模型。这意味着所有限流决策都基于控制面聚合出来的全局负载 客户端只执行概率丢弃它的代价是限流决策依赖全局聚合数据而这些数据每秒更新一次所以可能滞后 2 到 3 秒。但 Uber 认为这个延迟在绝大多数 workload 下可以接受。只有极短、极高的瞬时 burst 可能不够精确。相比之下避免本地 token bucket 误杀合法流量以及简化系统模型更重要。十二、最终设计控制面指导的概率丢弃GRL 当前模型可以这样理解控制面负责算 drop ratio 数据面负责按 drop ratio 丢请求当请求到达 client proxy 时会执行以下步骤1. 根据 caller、procedure 或两者组合匹配限流 bucket 2. 如果该 bucket 当前有 drop ratio 指令就按该比例概率丢弃 3. 如果没有 drop 指令就正常转发所有复杂计算都在请求热路径之外完成。Aggregators 和 controllers 负责收集请求计数 聚合全局流量 和配置 limit 比较 计算 drop ratio 每秒下发新指令Client 热路径只做匹配 bucket 读取当前 drop ratio 随机采样 放行或拒绝这非常轻量。它不需要本地 token accounting。不需要 per-request 访问控制面。不需要 per-request 访问 Redis。也没有中心瓶颈。这就是它能支撑 Uber 数亿请求每秒的关键。十三、限流规则如何配置GRL 的限流规则由 service owners 通过配置文件定义。每个 rate-limit bucket 可以指定几个维度。1. Scope生效范围可以是global regional zonalglobal 表示全局限流。regional 表示区域级限流。zonal 表示可用区级限流。不同 scope 对应不同 blast radius。比如某个配置如果作用在 global 层影响范围很大。如果作用在 regional 或 zonal 层影响会更局部。后来 Uber 也进一步引入 regional rate limits目的就是降低配置变更的影响范围。2. Matching rule匹配规则可以按caller names procedures caller procedure也就是说可以限制某个调用方访问某服务的总量 某个 procedure 的总量 某个调用方访问某个 procedure 的总量这种组合很有用。比如某个 caller 对大多数接口都正常但只对某个昂贵 procedure 访问过高就可以只限制这个组合而不是把 caller 全部打掉。3. Behavior执行或影子模式配置可以是deny真正执行限流 allow / shadow mode只观察不实际拒绝shadow mode 非常关键。限流配置一旦错误可能直接影响线上流量。因此在真正 enforce 前应该先观察如果这条规则生效会丢多少请求 会不会误伤正常流量 哪些 caller 会被影响 哪些 procedure 会被影响shadow mode 让团队可以先看假想 drop 行为再决定是否正式启用。十四、GRL 上线后的第一类收益降低延迟和开销GRL 一个非常直接的收益是移除了 Redis-backed limiter 的网络往返。以前很多服务做限流时每个请求要访问 Redis。迁移到 GRL 后限流决策在 service mesh data plane 本地完成。这带来明显延迟改善。原文给了一个关键服务迁移后的请求延迟分布变化2–3 ms从 2.5K RPS 增加到 30K RPS提升 1100% 3–4 ms从 50K RPS 增加到 170K RPS提升 240% 4–5 ms保持约 120K RPS 5–6 ms从 80K RPS 降到 34K RPS下降 57.5% 6 ms从 100K RPS 降到 20K RPS下降 80%这说明迁移后大量请求从更高延迟区间移动到了更低延迟区间。在关键 API endpoint 上改善也很明显P50 延迟下降约 1ms P90 延迟下降几十毫秒 P99.5 从几百毫秒下降到几十毫秒 最慢响应最多改善约 90%这里的核心原因不是概率丢弃本身而是把 per-request Redis 网络访问从热路径里拿掉了。对于低延迟 RPC 系统来说一次额外网络 hop 的成本非常明显。十五、GRL 上线后的第二类收益简化运维和节省资源统一 GRL 之后服务不再需要自己维护 Redis-based limiter。这带来几个好处不需要为 quota 维护独立 Redis 集群 不需要每个团队维护不同 limiter 不需要处理 Redis 客户端库升级风险 不需要排查各种自定义限流行为 不需要在业务代码里嵌入复杂限流逻辑原文提到迁移带来了明显的计算效率收益释放了大量原本用于限流工作负载的 Redis instances。这类收益很容易被低估。很多基础设施优化不只是请求延迟下降也包括少维护一批集群少处理一类故障少一层依赖少一套监控少一批运维 runbook降低 incident 期间排障复杂度统一平台能力的价值往往就在这里。十六、GRL 上线后的第三类收益可靠性和事故响应GRL 部署后多次在 spike、failover、retry storm 中保护服务。原文给了一个例子一个关键服务经历了 15 倍流量突增22K RPS → 367K RPS在 GRL 保护下服务没有发生性能退化。这说明 GRL 不只是平时省延迟而是在极端情况下能保护系统不被打爆。原文还提到GRL 能在 DDoS 攻击到达内部系统之前吸收掉流量。这点非常关键。在大型系统里最怕异常流量穿透到内部核心服务。如果能在 service mesh 层就进行概率 shedding就可以把异常流量挡在更靠前的位置。同时GRL 也成为 Uber Production Engineering 团队的事故处理工具。在 incident mitigation 期间工程团队可以快速对高流量 caller 或 procedure 应用 targeted rate limit。因为控制面更新每秒传播GRL 可以在几秒内响应过载情况不需要服务重新部署。这对事故处理非常重要。事故中最宝贵的是时间。几秒内压制特定流量和等一次发布完成是完全不同的恢复能力。十七、GRL 的规模原文提到GRL 在 full scale 下已经处理约 8000 万 requests per second 覆盖超过 1100 个服务它动态执行 quota同时保持端到端低延迟。这个数字非常重要因为它说明 GRL 不是实验性系统而是 Uber 可靠性平台的核心组件。不过统一执行只是第一步。当 GRL 覆盖越来越多服务后新的问题出现了限流配置仍然需要人维护。这就引出 RLC。十八、问题二静态配置会过期GRL 统一了限流执行但早期限流配置仍然依赖人工 YAML。Service owners 需要定义 per-caller、per-procedure quota。当流量变化时还要手动调整。在 Uber 这种规模下这很快成为问题。1. 太严格如果配置太低健康流量峰值也会被限流。这会误伤正常业务。比如某个服务增长了但 limit 没跟着增长。结果业务正常高峰时被 GRL 丢请求。2. 太宽松如果配置太高限流就失去保护作用。服务真正过载时limit 远高于实际承载能力GRL 不会及时触发。这种配置看起来“安全”其实只是摆设。3. 太依赖人工工程师要看 dashboard分析流量修改 YAML走 review发布配置。但服务流量不断变化新功能上线城市扩张节假日促销活动调用关系改变服务拆分或合并failover 后流量转移人工配置很难长期保持准确。所以 Uber 需要把“限流配置管理”本身也自动化。十九、RLCRate Limit ConfiguratorRLC 的目标是自动让 GRL 的限流配置保持准确、及时和安全。它会周期性分析真实流量指标并根据观察到的需求重写 rate-limit settings。RLC 的基本循环是1. 从 Uber observability platform 收集过去几周流量指标 2. 根据历史峰值和 buffer headroom 计算安全 limit 3. 将更新后的配置写入共享配置仓库 4. 通过现有控制面把新 limit 推送给 GRL这形成一个闭环真实流量 ↓ 观测指标 ↓ 自动计算限额 ↓ 更新配置 ↓ GRL 执行 ↓ 继续观测这样限流规则会随着流量演进自动变化而不是停留在某个手工设定的旧值。二十、RLC 的策略不是单一公式RLC 从一开始就被设计成可扩展的。默认策略基于历史 RPS 数据。也就是说根据过去几周的历史峰值再加上安全 buffer计算一个合理 limit。这种策略适合大量普通服务。但不是所有服务都适合只看历史。原文提到某些服务比如地图和位置数据相关服务会使用预测模型。这些模型会结合traffic forecasts pre-planned capacity也就是流量预测和预先规划的容量。这种策略不是简单对历史峰值加 buffer而是提前预测未来负载。比如某个地区即将有活动或者某个业务将上线新功能历史流量不能完全代表未来。预测模型就更合适。还有一些情况需要固定 quota。比如某些配额来自合同、组织约定或长期运营安排。这些 quota 不频繁变化需要稳定可预期。所以 RLC 支持多种 policy历史流量型 预测模型型 固定配额型 未来还可以扩展更多类型这比一个统一公式更现实。不同服务域的流量模型不同限流策略也应该不同。二十一、Shadow Mode 和验证自动生成限流配置非常强大但也很危险。如果 RLC 算错了 limit直接 enforce可能造成大面积误伤。所以 shadow mode 非常重要。在 shadow mode 下限流规则会被生成和监控但不会真正丢请求。系统会展示观察到的真实流量 如果规则生效会丢多少请求 哪些 caller 会被影响 哪些 procedure 会被影响 是否会在正常峰值误伤团队可以先通过 dashboards 和 alerts 验证行为再正式启用。这是一种非常重要的上线方式。在大型基础设施里自动化不等于直接放飞。正确做法是先自动生成 再影子验证 再逐步 enforce这样既减少人工又保证安全。二十二、RLC 带来的影响RLC 将 GRL 从静态保护层变成了自适应、自维护的控制系统。原文总结了几个收益。1. 操作简单数千条 rate-limit rules 可以自动更新不再依赖人工频繁修改。这减少了大量运维工作。2. 一致性所有服务使用相同公式和数据源生成配置。这避免了不同团队根据经验手工调参导致的不一致。3. 灵活性不同策略类型可以适配不同 workload。普通服务用历史流量模型。预测性服务用 forecast。合同或长期约束用固定 quota。4. 安全性shadow mode 让配置在执行前先被验证。这减少了误伤正常流量的风险。二十三、GRL 和 RLC 的关系可以把 GRL 和 RLC 看成一套系统的两个层面。GRL 负责执行每个请求来了 ↓ 是否匹配限流 bucket ↓ 当前 drop ratio 是多少 ↓ 按概率放行或丢弃RLC 负责配置生成过去几周流量是什么样 ↓ 应该给 caller/procedure 设置多大 limit ↓ 需要多少 buffer ↓ 是否用预测模型或固定 quota ↓ 写入配置并推送GRL 是执行系统。RLC 是配置智能系统。没有 GRLRLC 只是生成配置。没有 RLCGRL 仍然依赖人工配置。两者结合才形成了 Uber 当前的自动化限流体系。二十四、为什么概率丢弃比精确计数更适合 Uber很多人第一次看到概率丢弃会觉得它不如精确计数。直觉上精确计数更准确 概率丢弃有随机性但在 Uber 这种规模下精确计数反而不现实。要精确就要维护全局实时计数。这意味着巨大的网络通信、中心依赖、跨 region 一致性和控制面压力。概率丢弃的优势是每个请求热路径极轻 无中心计数器 没有 per-request 远程调用 天然分布式 能根据全局聚合结果协调 误差在大流量下会被平滑掉当流量规模足够大时概率模型非常有效。比如 drop ratio 是 33%。每个 proxy 独立随机丢弃约 33% 请求。在海量请求下总体丢弃比例会非常接近目标。这种方式牺牲的是短时间内的绝对精确性换来的是全局可扩展性和低延迟。这正是大规模系统常见取舍不追求每个请求级别的精确控制而追求整体统计意义上的稳定控制。二十五、为什么控制面允许 2 到 3 秒滞后GRL 的 drop ratio 来自全局聚合数据每秒更新。因此从真实流量变化到客户端执行新 drop ratio可能有 2 到 3 秒滞后。这听起来不完美。但 Uber 认为可以接受。原因是大多数流量变化不是毫秒级完成的多数服务不需要 sub-second 精确限流避免 per-request 中心计数更重要本地 token bucket 反而可能误杀合法流量几秒滞后只在极短、极高 burst 下影响较大事故响应中几秒级传播已经足够快这体现了工程系统里一个重要原则不是所有控制都需要强实时。只要反馈延迟相对于系统动态足够小就可以接受。对 Uber 的 RPC 限流来说几秒级全局协调已经能防止绝大多数过载。二十六、为什么 fail open 很重要GRL 的设计里如果控制面不可用clients 会 fail open。也就是说控制面挂了数据面继续放行流量。有人可能会问限流控制面挂了不应该 fail closed保护下游吗在这种基础设施系统里fail closed 非常危险。如果控制面出现故障而所有服务网格代理都开始拒绝请求那限流系统本身就会造成大面积故障。GRL 的设计原则是控制面帮助系统更安全 但控制面故障不能让系统立刻不可用所以 fail open 是合理选择。当然fail open 意味着在控制面故障期间限流精度会下降。但这比自我制造 outage 更可接受。这和很多基础设施系统类似配置服务挂了使用本地缓存服务发现挂了继续使用旧 endpoint认证策略更新失败保留旧策略限流控制面挂了继续按已有或放行策略执行核心思想是数据面要尽量独立存活。二十七、GRL 适合解决什么问题GRL 主要解决的是服务间 RPC 流量限速和过载保护。它适合限制某个 caller 对某个 service 的流量 限制某个 caller 对某个 procedure 的流量 防止异常 caller 压垮下游 防止 retry storm 放大故障 在事故中快速压制特定流量 保护服务免受突发高流量冲击 通过 shadow mode 验证限流规则但它不是所有过载问题的唯一解。原文最后也提到Uber 还有 throttler layer用于提供更靠近应用层的额外 overload protection。这说明 Uber 的可靠性体系是多层的服务网格层 GRL统一服务间 RPC 限流 配置自动化 RLC自动保持 limit 新鲜 应用层 throttler更靠近业务语义的保护 其他系统数据库层、存储层、队列层等各自保护这点很重要。在大规模系统中没有一个限流器能解决所有问题。不同层有不同上下文。Service mesh 层适合做通用 caller/procedure 限流。应用层适合做业务语义限流。存储层适合根据真实资源状态做 admission control。边缘网关适合做外部流量和安全防护。二十八、和数据库负载管理的区别Uber 另一篇文章讲的是从静态限流到智能数据库负载管理。那篇文章主要关注 Docstore / Schemaless 存储层强调 overload management 要靠近状态所在的 storage node。而这篇 GRL 文章关注的是 service mesh 中的 RPC 入口控制。两者相似但关注点不同。GRL 主要回答服务 A 调服务 BA 的流量是否超过 B 配置的限额 某个 caller/procedure 是否应该被概率丢弃 如何在全局范围内统一执行限流数据库 Load Manager 主要回答这个 storage node 是否真的过载 这个请求优先级高不高 是不是某个 tenant 或 partition key 导致过载 是否应该基于内存、goroutine、commit lag 等信号 shedding所以可以这样理解GRL 是服务网格层的全局 RPC 限流 数据库 Load Manager 是存储层的智能负载管理两者都是 Uber 可靠性体系的一部分但处理的问题层次不同。二十九、普通后端系统可以怎么借鉴即使你的系统没有 Uber 这么大也能从这篇文章里拿走很多经验。1. 不要让限流散落在业务代码里业务代码里的限流最容易碎片化。如果多个服务都需要限流最好抽象到统一 middleware、gateway、service mesh 或平台组件里。2. Redis 限流不是银弹Redis 计数器简单但它不是免费午餐。它会增加网络延迟引入中心依赖并在大规模下遇到一致性和扩展性问题。对中小系统Redis 可能足够好。对超大规模系统要考虑本地执行和异步聚合。3. 热路径一定要轻限流逻辑在每个请求上执行。如果每次限流都需要远程调用、复杂计算、锁竞争或大对象分配就会拖慢所有请求。Uber 最终选择概率丢弃就是为了让热路径极轻。4. 限流规则要支持 shadow mode限流配置错误会直接影响线上业务。上线前先 shadow观察 hypothetical drops是非常必要的。5. 配置会过期要考虑自动化手工 YAML 在早期可用但会随着系统规模变成负担。如果流量模式变化频繁限流配置应该根据真实指标自动调整。6. 限流要有 scopeglobal、regional、zonal 的区别很重要。全局配置影响最大风险也最大。区域级和可用区级限流可以降低 blast radius。7. 事故响应需要快速、定向限流限流系统不只是平时自动保护也应该是 incident playbook 的一部分。事故中能几秒内压制某个 caller/procedure比改代码发布强得多。8. 可观测性必须统一要能回答谁被限流了 为什么被限流 drop ratio 是多少 实际丢了多少请求 是否处于 shadow mode 限流规则来自哪里 配置何时更新 控制面是否健康没有这些信息限流系统很难被业务团队信任。三十、从系统设计角度看GRL 是一个分布式控制系统GRL 不只是一个限流库。它更像一个分布式控制系统。控制目标是让每个 caller/procedure 的流量保持在配置 limit 附近 避免过载穿透到下游服务 保持请求热路径低延迟系统结构是数据面 client 本地执行 zone aggregator 聚合局部指标 regional/global controller 计算控制信号 drop ratio 作为控制输出下发反馈周期是秒级。控制动作是概率丢弃。这和很多控制系统类似观测当前状态 和目标状态比较 计算调整量 下发执行指令 继续观测结果它的特别之处在于控制对象是服务间 RPC 流量执行动作是概率 drop热路径不能依赖中心状态系统必须跨 region、zone 和海量 hosts 扩展控制面故障时数据面必须 fail open从这个角度看GRL 的设计重点不是某个算法而是整套控制循环如何在超大规模下稳定运行。三十一、我的理解Uber 从“限流功能”做成了“可靠性平台能力”这篇文章最值得学习的地方不是 token bucket也不是 drop ratio 公式而是 Uber 对限流的定位变化。早期限流是某个服务自己写一点逻辑 或者用 Redis 记个 counter 超过就拒绝后来变成服务网格统一执行 控制面全局聚合 概率丢弃保护下游 配置不需要改代码 事故中可以几秒生效再后来变成RLC 自动分析历史流量 自动生成安全 limit shadow mode 验证 不同策略支持不同服务域 限流配置自维护这就是从功能到平台的演进。功能解决的是一个点。平台解决的是一类问题。Uber 不是给某个服务加了一个限流器而是让整个微服务体系具备统一、自动、可观测、可演进的流量保护能力。这对后端平台团队非常有启发当某个问题在多个团队、多个服务、多个语言栈里重复出现时不要让每个人各写一套。应该把它抽到基础设施层统一执行统一配置统一观测再逐步自动化。三十二、总结Uber 的限流系统经历了一个典型的大规模基础设施演进过程。最初Uber 内部服务各自实现限流。有的写在业务逻辑里有的用 middleware有的用 Redis-based counter。随着微服务规模增长这种模式暴露出配置不一致、运维成本高、修改慢、排障难、覆盖不均匀、可观测性差等问题。Redis 中心计数器方案在普通系统里可行但无法支撑 Uber 的规模。Uber 有数十万 hosts、每秒数亿 RPC、跨多个 region。每个请求都访问 Redis 会带来不可接受的延迟、巨大的 Redis 集群成本和跨区一致性问题。定期同步计数也会因为数据过期而无法快速应对突发流量。因此Uber 选择将限流能力集成到 service mesh 中构建 GRL也就是 Global Rate Limiter。GRL 使用三层架构Rate-limit clients运行在 service mesh data plane本地执行请求级决策 Aggregators按 zone 聚合请求计数 Controllers按 region/global 汇总流量计算 drop ratio并下发给 clients早期 GRL 使用 token bucket但在生产中遇到公平性和实例级突发问题。后来 Uber 引入 drop-by-ratio当某个 caller 的全局流量超过 limit 时根据公式计算概率丢弃比例drop_ratio (actual_rps - limit_rps) / actual_rps例如实际流量是 limit 的 1.5 倍就丢弃约 33% 请求。最终 Uber 废弃 token bucket统一采用控制面指导的概率丢弃模型。请求热路径只做本地 bucket 匹配和随机采样不访问 Redis也不访问控制面。控制面每秒聚合请求计数、计算 drop ratio并下发到 clients。虽然全局数据有 2 到 3 秒延迟但这种取舍相比中心化计数更适合 Uber 的规模。GRL 上线后效果明显移除 Redis-backed limiter 后大量请求从高延迟区间移动到低延迟区间 P50 延迟下降约 1ms P90 延迟下降几十毫秒 P99.5 从数百毫秒下降到数十毫秒最慢响应最多改善约 90% 释放了大量用于限流的 Redis instances 关键服务成功扛住 15 倍流量突增从 22K RPS 到 367K RPS 无性能退化 DDoS 流量能在进入内部系统前被吸收 GRL 在 full scale 下处理约 8000 万 RPS覆盖超过 1100 个服务但 GRL 统一执行后配置管理又成为新问题。手工 YAML 限流规则会过期太严格会误杀健康流量太宽松又失去保护作用频繁人工调整也不可持续。于是 Uber 构建 RLC也就是 Rate Limit Configurator。RLC 会周期性从观测平台收集过去几周的流量数据根据历史峰值和 buffer headroom 自动计算安全 limit写入共享配置仓库并通过控制面推送给 GRL。它还支持多种策略默认历史 RPS 策略、基于预测模型的策略以及固定 quota 策略。为了保证安全RLC 支持 shadow mode让团队先观察假想 drop 行为再决定是否正式 enforce。最终GRL RLC 将 Uber 的限流系统从零散、手工、依赖 Redis 的实现演进成统一、自动、可扩展、低延迟的可靠性平台能力。这篇文章给后端工程师的最大启发是限流不是简单的“超过 QPS 就返回 429”。在大规模微服务系统中限流是一个分布式控制系统必须同时考虑热路径延迟、全局协调、配置自动化、可观测性、故障模式、事故响应和长期运维成本。真正成熟的限流系统不只是能丢请求。它应该能在系统压力下快速、准确、低成本地保护关键服务同时让工程团队不再被手工配置和碎片化实现拖住。参考资料Uber’s Rate Limiting System