LiteLLM网关与Redis缓存故障复盘:从内存溢出到限流崩溃的排查与治理

发布时间:2026/11/4 8:11:06
LiteLLM网关与Redis缓存故障复盘:从内存溢出到限流崩溃的排查与治理 大约周五下午 14 点我正在调整新上线的模型路由策略同事在 IM 里丢过来一句“LiteLLM 网关的错误率不对499 和 504 变多了。”我打开 Grafana第一眼看到的不是网关本身的 CPU 有多高而是 Redis 的内存曲线像一条被拉直的钓鱼线——从 8GB 一路快速爬升离 maxmemory 只剩不到 2GB。那台 Redis 我们用了近一年一直承载 LiteLLM 代理的响应缓存、限流计数还兼着其他应用的分布式锁和会话缓存。谁也没想到它会在一个看似普通的周五下午成为整个事故的起点。这篇文章是这次生产故障的完整复盘。从告警出现开始到一步步溯源到根因再到 Redis 内存治理和后续防复发设计我会把所有关键细节都写出来。如果你正在用 LiteLLM 统一接入 Bedrock、OpenAI 这类上游又把它和 Redis 绑在一起当缓存和限流后端这篇内容应该能帮你少踩几个坑。1. 故障时间线与第一现场1.1 周五下午的告警风暴先把时间线拉出来方便你对照当时现象一步步理解后面的判断。整个过程大约 55 分钟从出现异常到完全不可用节奏非常快。时间现象我们当时的判断14:05某业务方上线离线批量任务网关整体 QPS 从 30 冲到 400正常业务高峰未触发告警14:32网关延迟 p99 从 1.2s 变为 4s部分客户端开始收到 429怀疑 LiteLLM 路由配置或上游限流14:47499/504 激增3 个网关副本中有 2 个健康检查失败怀疑网关实例资源不足准备扩容15:00Redis 写入开始直接报 OOM所有走网关的请求大面积失败终于发现共享 Redis 内存已经打满这个顺序很典型先出现轻微变慢然后错误率抬升紧接着健康检查失败最后底层存储爆掉。但如果当时第一眼就去查 LiteLLM 的配置文件或者去压测上游供应商的限流大概率会绕很久。1.2 影响范围与第一时间行动这次故障影响的不只是单个业务。我们内部几乎所有需要调用大模型的服务都挂在同一个 LiteLLM 网关上包括客服摘要、数据分析、内部知识库问答还有那个刚上线的批量任务。走同一个网关共享同一个 Redis。当时现场的行动其实只有三步。第一步拉了一个三人小群网关负责人、Redis 负责人、业务方负责人。第二步由业务方确认批量任务可以暂停立刻暂停该任务的调度——这起到了止损作用。第三步我们在群里达成共识先不碰 Redis不搞重启先观察内存曲线是否回落。为什么强调“先别动 Redis”因为那台实例上还挂着其他业务的分布式锁和会话缓存一旦重启 Redis 或者执行FLUSHALL锁全部丢失会造成比网关超时更严重的重复调度和脏数据问题。这也是后面我们坚持拆分实例的重要原因。2. 从网关日志到 Redis线索的完整链路2.1 LiteLLM 网关日志里最有价值的三类报错事故中最容易踩的坑是一上来就打开全量日志漫无目的地刷。正确做法是按错误类型聚合先看 error code 的分布再看每种类型对应的原始日志片段。我们当时过滤后发现真正有价值的报错只有三类第一类Redis 连接异常来自 LiteLLM 的 cache 层cache: ConnectionError: Error while connecting to Redis第二类限流相关的 429即使当前流量远没达到网关配置的 RPM 上限ratelimit: RateLimit exceeded, token limit: 0 / 100000第三类上游调用超时litellm.proxy: TimeoutError: Request timed out after 120s这三类报错如果单独看很容易得出三个完全错误的结论Redis 网络有问题、限流配置配错了、上游供应商响应慢。但放在一起看有一个共同的交汇点——所有异常都发生在与 Redis 交互的环节。限流计数需要写 Redis缓存读写需要 Redis甚至连上游调用超时的根因也是因为请求在 LiteLLM 内部线程池里排队等 Redis 操作完成根本没到上游那一步。2.2 Redis 内存指标与 key 分布给的第一击确认方向后我们用redis-cli INFO memory看了一眼数字非常吓人used_memory: 15664209920 # 14.6GB used_memory_rss: 23674880000 # 22GB mem_fragmentation_ratio: 1.51 maxmemory: 16106127360 # 15GBused_memory 已经超过 14.6GB距离 maxmemory 只剩不到 500MB。更让人警觉的是 mem_fragmentation_ratio 到了 1.51说明除了实际数据内存碎片也在占用大量 RSS。再看 keyspacedb0: keys1843201, expires1081344, avg_ttl1023s接近 184 万个 key但只有 108 万个设置了过期时间这意味着有大量 key 是“永生”的。avg_ttl 只有 17 分钟说明很多 key 其实已经可以淘汰但由于过期 key 清除是惰性的如果没有命中和写入触发它们会一直躺在内存里占地方。我们随后用redis-cli --bigkeys扫了一遍发现字符串类型占据绝对大头里面混着大量 LiteLLM 的响应缓存 key。最大的 key 超过 2MB。单个 key 本身不算致命但几十万个 100KB 以上的大 key 叠加在一起内存涨起来是肉眼可见的。2.3 上游与业务方流量为什么被排除在锁定 Redis 之前我们花了大约 10 分钟排查上游和业务方。过程不复杂但值得说一下排除的逻辑。上游 Bedrock 侧我们通过供应商控制台和网关的 upstream metrics 同时观察延迟曲线和错误率都很平稳说明上游没有成为瓶颈。业务方那边确认批量任务的 QPS 虽然高但远低于网关设计的承载上限所以不是流量攻击也不是网关并发能力不够。网络设备层面也做了丢包检查确认没有传输问题。到这一步范围已经收窄到“网关 Redis 的联动关系”上。后面复盘时我们发现实际上还有一个隐藏因素批量任务的 prompt 内容高度重复但每次请求的 metadata 不同导致 LiteLLM 生成的缓存 key 不完全一致响应缓存几乎没有命中率却在持续写入新 key。这是典型的“缓存穿透式写入”——不是读不到而是不断写新值。3. “级联”是怎么炼成的三层放大机制复盘3.1 第一层缓存写满后限流计数和计量先崩我们需要先理解 LiteLLM 网关在正常情况下的 Redis 角色。它同时承担了三类数据全部放在同一个 Redis 里。第一类是响应缓存也就是 prompt/response 缓存value 大写入频繁。第二类是限流计数LiteLLM 的 token 限流基于滑动窗口每次请求都要对 Redis 做 INCR 或窗口记录。第三类是预算计量记录每个 key 的消耗金额每笔调用都要写。当 Redis 达到 maxmemory并且当前的maxmemory-policy是noeviction时所有写操作都会被拒绝直接在客户端报 OOM error。响应缓存写失败时LiteLLM 有可能降级继续向上游转发请求但限流计数和预算计量写失败请求就没办法继续走完正常流程只能返回 429 或 500。这里有一个反直觉的点缓存不可用反而让情况更严重。因为每次请求的缓存读取都失败了LiteLLM 认为“没缓存”于是全部穿透到上游供应商。上游的 TPM/RPM 配额本来就在被正常消耗一下子多出几倍的突发令牌请求很快引起上游限流错误。于是客户端看到的不再只是网关错误还有上游的限流错误两边一起爆发形成了第一层放大。3.2 第二层Redis 慢了网关线程池就死LiteLLM 网关本身是 Python 异步框架所有 Redis 操作都是异步任务。当 Redis 由于 OOM 导致命令处理排队时每个 Redis 操作的响应时间从微秒级变成秒级最直接的影响就是事件循环被这些等待占满。事件循环被占满之后最怕的事情发生了健康检查接口也开始响应缓慢。Kubernetes 的 readinessProbe 连续三次失败就会把实例从 Service Endpoint 中摘掉。我们当时有 3 个网关副本其中 2 个被摘除所有流量集中到剩下的 1 个副本。问题在于剩下的那个副本依赖的还是同一个 Redis 实例。它没有变得更快反而因为承接全部流量积压得更严重。健康检查继续失败负载均衡器开始对探活产生超时新的请求开始被拒绝。这一层是整场事故里最危险的部分。很多人以为负载均衡会把流量分散到健康节点但实际上当所有节点都共享同一个故障存储时“健康”只是表象。3.3 第三层重试风暴与共享实例的连带故障客户端看到 499 和 504 后会自动重试。我们事后从日志统计平均每个失败请求被客户端重试了 2.3 次。每一次重试都会再次触发 Redis 写入或读取操作而 Redis 此时已经满内存、高 CPU、命令排队任何一个操作都在加重它的负担。更麻烦的是这台 Redis 上还有其他业务的分布式锁和会话缓存。那些业务并不关心 LiteLLM 出了什么问题它们只知道自己的 Redis 操作变慢了于是开始报错、重试、重建锁。Redis 的 CPU 占用进一步升高内存碎片进一步加剧。整个实例进入了一种“还活着但每次操作都在超时边缘”的半死状态。这种半死状态比完全宕机更难受因为监控图上看起来 CPU 还有余量、网络还在通让人一次次误判成网络抖动或偶发超时。3.4 为什么当时“重启 Redis”绝对不行现场一度有人提议重启 Redis我们反复拦住了。不是因为重启没用而是因为重启会造成更严重的次生伤害。第一重启后缓存清空所有瞬时流量都会穿透到上游供应商TPM/RPM 配额会在几分钟内被打满上游从正常变成限流错误率不会降反而会升。第二分布式锁会全部丢失依赖锁的定时任务可能同时多节点执行造成重复写入和脏数据。第三当时 Redis 的 maxmemory-policy 还是 noeviction即使重启成功内存照样会在短时间内再次写满。真正的止损不是重启而是先把异常流量源停掉——也就是那个批量任务。任务暂停后Redis 写入量骤降内存曲线开始缓慢往下走。到 15:30 左右内存降到 12GB 以下LiteLLM 的错误率开始回落。虽然业务还没有完全恢复但至少系统稳住了。这之后才进入真正的治理阶段。4. Redis 内存优化手术从止血到长期治理4.1 第一刀给缓存一个合理的时间边界首先要改的是 TTL。我们当时的 LiteLLM 配置里cache_params.ttl设置的是 3600 秒。在低频业务下这没问题但批量任务这种高并发场景下1 小时内的存留 key 会快速积累。我们把 TTL 从 3600 调整到 300 秒观察几天后发现缓存命中率并没有明显下降。原因是同类请求的重复集中在很短的时间窗口里通常是几分钟以内而不是几十分钟以后。TTL 调短后至少内存不会把“冷数据”留在原地。同时修改 Redis 的 maxmemory-policy从noeviction改为volatile-lru。这个策略只淘汰设置了 TTL 的 key不会动那些不带过期时间的分布式锁和业务持久化 key。这是相对安全的选择maxmemory 16gb maxmemory-policy volatile-lru maxmemory-samples 10maxmemory-samples提高到 10是为了让 LRU 近似算法更准确避免刚写进去的热点 key 被淘汰。这里有一个细节如果直接用allkeys-lru当内存压力来时Redis 会优先淘汰空闲的锁 key导致分布式锁逻辑彻底乱掉。所以我才强调用volatile-lru。4.2 第二刀把“公共水库”拆成“专用水池”共享 Redis 最大的问题不是容量不够而是劣化会互相传导。一个业务把内存写满连累所有依赖同一个 Redis 的其他应用。所以长期方案必须拆分。我们最终把 Redis 分成了两个实例原实例保留分布式锁、会话缓存等业务侧数据容量 8GB。新增实例专门给 LiteLLM 使用承担响应缓存、限流计数、预算计量容量 8GB。LiteLLM 侧只需要在配置里指定独立的 Redis 地址litellm_settings: cache: true cache_params: type: redis host: 10.0.10.12 port: 6379 password: xxxx ttl: 300限流和计量相关配置也指向同一个新实例。这样即使 LiteLLM 把新实例写爆也不会影响业务侧分布式锁。反过来业务侧出现异常流量也不会拖垮网关。拆分后的效果很明显原实例内存从 14.6GB 降到 4GB 左右LiteLLM 专用实例在高峰期也稳定在 6GB 以内。4.3 第三刀大 Key 治理与缓存瘦身拆分实例只是解决了耦合问题LiteLLM 专用实例如果还是同样的使用方式早晚也会被写满。所以接下来是针对大 key 的治理。先用redis-cli --bigkeys做第一轮扫描定位到最大的 key。再用脚本找出 top 100 大 key 的分布规律。我这里提供一个简单的 Python 脚本思路用 SCAN 配合 MEMORY USAGEimport redis r redis.Redis(host10.0.10.12, port6379, passwordxxxx) results [] for key in r.scan_iter(match*, count5000): size r.memory_usage(key) if size and size 1024 * 100: # 大于100KB results.append((key.decode(utf-8, ignore), size)) results.sort(keylambda x: x[1], reverseTrue) for k, v in results[:50]: print(v // 1024, KB, k[:120])扫完以后我们发现最大的 key 是某几个超长 prompt 的完整调用记录value 里保存了完整的请求和响应文本。针对这些大 key我们做了三件事第一对缓存 value 做 gzip 压缩。LiteLLM 的 value 大多是可压缩的 JSON 文本压缩后平均节省超过 70% 空间。压缩确实会带来一点 CPU 开销但在资源充足的情况下可以接受。第二裁剪缓存字段。只保留代码真正会用的内容比如 response text 和基础 usage去掉 annotations、system_fingerprint 这类元数据。这个收益很直接单条记录大小直接降了一个量级。第三超长 prompt 不参与缓存。超过 2KB 的 prompt 生成 cache key 很容易撑爆 Redis而且这类请求基本不会完全重复缓存命中率极低。我们在 LiteLLM 前的业务逻辑里加了判断超长请求直接跳过缓存层。4.4 LiteLLM 侧降级开关与客户端重试治理Redis 治理做得再好也需要回答一个问题如果 Redis 再次故障LiteLLM 网关应该怎么办LiteLLM 本身支持缓存降级也就是 cache 后端不可用时直接透传上游不让缓存成为请求链路上的强依赖。这个开关需要确认版本和配置项重点是测试缓存 Redis 彻底不可用时请求不会跟着抛异常而是正常转发到上游。另外客户端重试策略也必须管住。我们当时的重试风暴让故障多持续了至少 10 分钟。现在内部约定是503 可以重试最多 2 次每次退避至少 500ms。429 不做盲目重试优先检查配额和限流。499 不重试因为是客户端主动断开。网关这一侧我们还给每个业务方配置了独立的 QPS 上限。单一业务方的异常流量不再可能导致整体资源被打满这也就是很多人常说的“流量隔离”。当时复盘时我们还对比过 One API 和 LiteLLM 在并发场景下的表现差异但最终的结论是网关选型不是根因共享 Redis 缺乏容错和隔离才是根因。换网关解决不了存储层的脆弱性。5. 防复发监控、压测与故障演练5.1 给 Redis 内存建立“提前量”Redis 内存监控不能只看当前值要看增长斜率。内存从 8GB 涨到 14GB 只用了 45 分钟如果只在达到 90% 时告警留给处理的时间窗口非常短。我们现在的告警体系分四级级别触发条件处理方式P4used_memory 超过 maxmemory 的 60%观察趋势不介入P3超过 75% 且内存增速持续 10 分钟拉群确认流量来源P2超过 85%停止非核心任务准备摘流量P1超过 92%立即降级缓存限制写入PromQL 表达式大致是这样redis_memory_used_bytes / redis_memory_max_bytes * 100同时监控mem_fragmentation_ratio超过 1.5 就要主动执行memory purge或安排重启维护窗口避免内存碎片把 RSS 推到危险区间。5.2 网关和依赖链路要有自己的监控面Redis 内存告警只是最后一道防线前面几道防线在网关侧。LiteLLM 网关的几个关键指标必须单独画出来请求排队时间、连接池等待时间、健康检查成功率、上游 TPM 余量。其中最有价值的是请求排队时间它几乎在 Redis 变慢的同时就会立刻抬升比内存告警至少早 5 分钟。我们现在的做法是“三合一”告警Redis 内存、网关排队时间、错误率三个指标同时异常才触发 P1。单一指标波动不惊动这极大减少了误报。5.3 故障演练与团队共识最后是演练。光有监控还不够必须验证“Redis 挂了之后 LiteLLM 会不会跟着挂”。我们每个月挑一个低峰期做一次混沌演练方法很简单用CONFIG SET maxmemory 2gb临时把实例内存压到极限观察网关行为。第一次演练就发现了一个问题在noeviction策略下Redis 满内存后LiteLLM 的限流计数写失败会导致大量请求直接返回 500。后来调整了降级配置并重写了限流异常的兜底逻辑第二次演练时网关已经能在 Redis 异常时保持转发只是响应缓存短暂失效。演练的另一个作用是推动团队共识。现在新服务要接入共享 Redis必须先提交一份容量评估表写明预估 QPS、单 key 大小、TTL、增长斜率。不填这张表代码评审就不放行。这个习惯已经被写进了我们内部的接入规范。写在最后的一点体会这次事故最让我印象深刻的不是 Redis 会满而是“网关加共享 Redis”这种架构里故障的放大是分层的。先缓存穿透再线程池阻塞最后负载均衡补刀每一层都在让情况更糟。事后我们常开玩笑说Redis 内存其实没多少钱但故障之后的开会成本贵得多。我现在每接一个中间件第一件事就是查它的默认容错行为和默认 TTL而不是先跑通功能。网关这类基础组件更是如此。最值得问的问题永远是如果它依赖的 Redis 挂了你的应用是跟着挂还是降级继续跑希望这篇复盘能帮你把这个问题想清楚最好是在故障发生之前。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询