凌晨两点,告警短信突然连环响起。打开监控大盘,眼前的一幕令人心惊:Redis 集群的 CPU 利用率已飙升至 95%,响应延迟从平日的 1 毫秒暴涨到 50 毫秒,部分节点开始超时。业务侧反馈更为直接:商品详情页的加载时间从 200 毫秒拉长到 3 秒,用户流失率直线上升。
紧急排查后,真相浮出水面:一个上游服务刚发布的新版本中存在隐藏 Bug,导致其在循环中疯狂读取同一个 Key,单个实例每秒就对 Redis 发起了 5 万次请求。该服务共有 200 个实例,相当于每秒 1000 万次请求如洪水般砸向缓存集群,远超其承载极限。
问题的症结并非 Redis 不够强大,而在于整个系统缺乏一道关键的“保险丝”——缓存限流。任何一个上游服务的异常行为,都能轻易击穿共享的缓存资源,引发雪崩。
为什么缓存层也必须限流?
提到限流,许多工程师的第一反应是 API 网关层,用以保护后端服务。然而,缓存层的限流在系统规模从百万 QPS 迈向千万 QPS 的过程中,其重要性日益凸显。
缓存性能并非无限
许多人存在一种误区,认为 Redis 性能强大,无需额外保护。诚然,单个 Redis 实例可以轻松应对 10 万到 20 万 QPS,但这个能力存在明确的上限。在千万 QPS 的系统中,即使进行了充分的分片,每个分片承受的压力也常常在极限边缘徘徊。

为何系统规模越大,安全余量反而越低?因为分片数量不可能无限增长。分片越多,集群管理的复杂度呈指数级上升,跨分片操作的成本也急剧增加。在实际运维中,千万 QPS 的系统通常会严格控制分片数量,让每个分片的性能尽可能逼近上限。这意味着,系统规模越大,缓存的安全余量就越小,一次看似不大的流量尖刺就可能击穿整个缓存层。
缓存故障具有“传染性”
与数据库不同,缓存通常是多个业务共享的基础设施。一个业务的异常流量不仅会拖垮自身,更会波及共享同一 Redis 集群的所有业务,形成“一人生病,全家吃药”的窘境。

如图中所示,业务 B 的异常流量(10倍于正常)打满了所有分片,导致业务 A 和业务 C 的正常请求也受到严重影响。在千万 QPS 级别的大规模系统中,为每个业务完全隔离独立的缓存集群成本过高,共享集群几乎是必然选择,这使得隔离性的需求更为迫切。
API 限流无法替代缓存限流
或许你会问:在 API 网关层已经做了限流,为何缓存层还需要再来一次?
原因有二。第一,粒度不同。API 限流保护的是整个后端服务,粒度较粗。一个 API 请求背后可能触发 5 到 10 次缓存调用,API 层限流 100 万 QPS,缓存层实际承受的可能是 500 万到 1000 万 QPS。第二,视角不同。API 限流无法识别“缓存级别”的异常模式,例如某个热点 Key 被疯狂访问、某个 Lua 脚本耗尽 CPU、某个大 Value 序列化占用过多带宽等,这些在 API 层看来都是“正常请求”。
因此,API 限流好比大楼的门禁,防止外部冲击;而缓存限流则是每个房间的独立门锁,防止内部失控,两者缺一不可。
缓存限流的四个演进层次
缓存限流并非单一策略,而是一个从粗到细、由浅入深的多层防御体系。每一层解决不同维度的问题,层层递进,构成完整的保护网。
第一层:连接数限流——基础防线
这是最基础的限流方式,即限制客户端到 Redis 的连接总数。每个 Redis 连接都会占用文件描述符和内存,连接数过多会直接导致 Redis 内核事件循环处理效率下降。
实现方式通常是在客户端连接池设置上限。例如,每个应用实例最多持有 50 个 Redis 连接,超出的请求排队等待,若等待超时则直接拒绝。
其优点是简单直接,几乎无需额外基础设施。但缺点也显而易见:粒度太粗。同样 50 个连接,若每个连接每秒发送 1000 个命令,总 QPS 为 5 万;若切换到 Pipeline 模式,每个连接每秒发送 5000 个命令,总 QPS 就变成了 25 万。连接数未变,但对 Redis 造成的压力却天差地别。
第二层:请求速率限流——总量控制
为了更精确,我们需要控制每秒发往 Redis 的命令总量,而非连接数。核心思想是:无论使用多少连接,总请求速率不能超过设定的安全阈值。
实现主要有两种思路:
- 本地令牌桶:每个应用实例维护一个本地令牌桶,以固定速率生成令牌。每次发送 Redis 命令前须先获取令牌,获取失败则拒绝或排队。这种方式实现简单、无网络开销,但无法在多实例间协调全局总量。
- 分布式计数器:借助一个中心化的计数器(如另一个独立的 Redis 实例或 ZooKeeper)记录全局请求速率,各实例按比例分配配额。精度更高,但引入了额外的依赖和网络开销。

本地令牌桶面临的核心挑战是配额分配。假设 Redis 集群安全容量为 20 万 QPS,有 100 个应用实例,为每个实例固定分配 2000 QPS 看似合理。但现实中各实例负载不均,固定分配会导致高负载实例被过早限流,低负载实例的配额却被浪费。
改进方案是动态配额调整:每个实例定期上报实际使用量,由中心调度器根据整体使用情况重新分配配额,让资源流向最需要的地方,类似于操作系统的 CPU 调度。
第三层:Key 级别限流——热点防护
前两层控制的是“总量”,但无法解决“热点 Key”问题。即使总请求量未超阈值,某个热点 Key 每秒被访问 10 万次,也足以压垮其所在的分片。
Key 级别限流的核心思想是:对单个 Key 的访问频率设置上限。实现上,通常在客户端维护 Key 维度的计数器。当某个 Key 访问频率超过阈值时,后续请求可直接从本地缓存返回或返回默认值,避免继续冲击 Redis。

难点在于如何高效追踪海量 Key 的访问频率。在千万 QPS 系统中,活跃 Key 可能多达数千万,为每个 Key 维护精确计数器开销巨大。
一种实用的折中方案是仅追踪“热点 Key”。通过近似算法(如 Count-Min Sketch 或 HeavyKeeper)实时检测访问频率最高的 Top-N Key,只对这些 Key 实施精确限流。对于非热点 Key,依靠前两层的总量限流来保护即可。Key 级别限流解决的核心问题是流量分布不均导致的局部过载。
第四层:业务维度限流——租户隔离
在多租户或多业务共享缓存的场景下,必须引入业务维度的限流,确保一个业务的异常不会“连坐”其他业务。
实现通常依赖于一个 Proxy 层。所有缓存请求先经过 Proxy,由其根据请求来源(通过 Key 前缀、客户端标识等识别业务)进行分业务的流量控制。

Proxy 层的限流策略通常包含三个维度:
- QPS 配额:每个业务每秒允许的最大请求数,根据业务重要性和历史用量分配。
- 带宽配额:每个业务每秒允许的最大数据传输量。这对于有大 Value 的业务至关重要,能防止其占满网络带宽。
- 命令类型配额:限制特定高危命令(如
KEYS、SCAN 或复杂度 O(N) 的集合操作)的使用频率。
业务维度限流的终极价值在于隔离性,它将“共享资源”转化为“配额化共享资源”,在保留成本优势的同时,有效避免了相互干扰。
限流策略的演进:从静态阈值到自适应反馈
静态限流:简单但僵化
最简单的限流就是设定一个固定阈值:每秒不超过 N 个请求。这在负载稳定的系统中尚可接受。
但在千万 QPS 系统中,流量波动是常态。日间高峰与凌晨低谷的流量可能相差十倍以上。若按高峰期设置阈值,低谷期的异常流量无法被有效识别;若按平均值设置,高峰期的正常流量又会被误杀。
动态限流:基于时间窗口的自适应
动态限流的思路是让阈值“活”起来,根据历史流量模式动态调整。
一种常见实现是基于滑动窗口的自适应限流。系统持续记录每个时间窗口(如每分钟)的实际流量,并根据历史同期数据计算出“正常流量范围”。当实时流量超出该范围一定比例(如 150%)时,触发限流。

这种方式能自动适应流量的周期性变化(如昼高夜低),同时对突发的异常流量保持高度敏感。
反馈式限流:基于 Redis 实时健康状态
更高级的策略不再仅仅盯着流量数字,而是直接根据 Redis 集群本身的健康状态来决策是否限流,实现“哪里不舒服,就限制哪里”。
核心监控指标包括:

当任一指标超过阈值,系统便自动触发限流,并根据严重程度调整力度。例如:
- CPU 利用率达 70%:限流 10% 非核心请求。
- CPU 利用率达 85%:限流 30% 请求。
- CPU 利用率达 95%:限流 50% 请求并触发紧急告警。
这种阶梯式、反馈式的限流策略,在系统正常与过载状态之间创造了缓冲区间,避免了“非0即1”的粗暴切换。它将限流决策从“预测流量会不会超标”转变为“感知缓存此刻是否健康”,更加贴近问题本质。
被限流的请求何去何从?
限流本身不是目的,保障系统整体可用性才是。被拒绝的请求需要有合理的“归宿”,不能一弃了之。
- 降级回源:尝试从本地缓存(L1 Cache)获取数据。若本地也未命中,可回源查询数据库,但必须严格控制回源并发,避免缓存没挂却把数据库打垮。
- 排队等待:对实时性要求不高的请求,可放入等待队列。设置合理的等待超时(如 100-200 毫秒),超时后再执行降级逻辑。此法能有效平滑短暂流量尖刺。
- 快速失败:对于查询类请求,直接返回友好错误提示或静态兜底数据。用户体验虽有折损,但系统核心功能保持稳定。
- 优先级调度:更精细的做法是区分请求优先级。核心链路(如下单、支付)的缓存请求享有最高优先级,确保不被限流;非核心请求(如推荐、排行榜)则在系统压力大时率先被限制。

优秀的限流策略不仅要决定“拒绝多少”,更要明智地选择“拒绝谁”,让不重要的流量为关键业务让路。
不同系统规模下的限流实践重点
缓存限流的复杂度和重点,与系统规模紧密相关。
十万 QPS:连接池管理 + 监控告警
此阶段缓存安全余量充足,限流需求不强烈。核心工作是做好连接池管理和基础监控。
- 核心措施:设置合理的客户端连接池上限;定义请求超时时间(50~100毫秒);建立慢查询监控与告警。
- 重点:此阶段的重点并非限流,而是监控。你需要摸清缓存的真实负载基线,为未来可能的限流策略提供数据支撑。没有准确的监控,任何限流都是盲人摸象。
百万 QPS:请求速率控制 + 热点防护
当系统达到百万 QPS,缓存安全余量明显收缩,需引入主动限流机制。
- 核心措施:实施本地令牌桶,控制单实例请求速率;部署热点 Key 检测与自动限流;按业务划分 Key 前缀,监控各业务用量。
- 风险:“一个业务拖垮整个集群”的风险开始显现,业务维度的监控和告警成为刚需。
千万 QPS:全链路精细化与自适应限流
千万 QPS 意味着缓存集群本身已成为一个需要精心运维的复杂系统,限流必须做到精细化、自动化、智能化。
- 核心措施:
- Proxy 层实现业务维度限流(QPS + 带宽 + 命令类型)。
- 实施反馈式限流,基于 Redis 实时健康指标动态调整。
- 引入请求优先级调度,保障核心链路。
- 实现热点 Key 自动发现与本地缓存分流。
- 建设全链路流量染色与追踪,精确定位异常源头。
- 体系化:这需要构建一个多层协同的防御体系。

如图所示的五层限流并非独立运作,而是层层递进、相互协同。连接数限流是最外层的粗粒度防线,反馈式限流是最内层的精细化保护。请求如同经过一道道安检,任何一层触发告警都会被拦截。
限流的代价与核心权衡
任何技术决策都有其代价,选择限流方案时,必须权衡以下几个关键点:
- 精度 vs 开销:越精细的限流(如 Key 级别),所需的计算和存储开销越大。如果限流机制本身消耗了10%的CPU,其“净收益”就需要重新评估。
- 实时性 vs 准确性:本地令牌桶实时性高但精度低;分布式限流精度高但引入延迟。在千万 QPS 场景下,限流组件自身绝不能成为性能瓶颈。
- 安全 vs 可用:阈值设得太低,误伤正常请求;设得太高,又来不及防御异常流量。一个常见经验是将限流阈值设为正常峰值流量的1.5到2倍,但具体数值需结合业务特性和历史数据反复调优。

限流方案的选择,并非越精细、越复杂越好,而是在当前系统规模与运维能力下,找到保护力度与实施成本之间的最佳平衡点。
结语:从被动应对到主动防御
回到文章开头的那个凌晨。如果当时已构建了一套完善的缓存限流体系,故事的结局将完全不同。
请求速率限流会在实例层面早期发现异常,将有 Bug 的实例流量限制在安全范围内。业务维度限流会形成隔离屏障,确保问题业务不会波及其他。反馈式限流则会在 Redis CPU 开始飙升的瞬间主动介入,为问题排查争取到宝贵的时间。
缓存限流的演进路径,与系统本身的成长轨迹高度重合。从十万 QPS 时做好监控,到百万 QPS 时引入主动限流,再到千万 QPS 必须建设精细化的多层限流体系。这本质上是从“相信一切都会正常运行”的乐观思维,转向“假设一切都可能出错”的防御性思维的转变。
在追求极致性能与稳定性的千万 QPS 世界里,系统不出问题并非因为运气,而是因为每一层都设计了可靠的兜底机制。缓存限流,正是这庞大防御体系中不可或缺、日益重要的一环。
你的系统当前处于哪个阶段?是在“裸奔”,还是已有了基础的限流保护?不妨在 云栈社区 与更多同行交流,当流量再增长一个数量级时,你现有的方案是否还能从容应对?