4.2 百万级调用平台:多级缓存网关与成本治理


文档摘要

4.2 百万级调用平台:多级缓存网关与成本治理 当产品日调用从二十万冲到百万级,且背后是好几条业务线、几十个上游服务共用一套模型能力时,前面那种"在调用入口插个薄代理层"的做法就开始失灵了。原因很朴素:流量太大、调用方太多、诉求太杂,单点缓存既撑不住吞吐,也照顾不了各业务不同的语义和时效要求。 这一节我们看一个日调用 120 万次、聚合了问答、摘要、代码生成三条业务线的平台,是怎么把"缓存加路由"从一套技巧升级成一套平台级成本治理架构的。 起点:三套独立缓存,各自为战 这个平台最早和多数团队一样,每条业务线自己维护一套缓存加路由。问题随着规模放大被急剧放大: 重复建设:问答线和摘要线各自有一份近乎相同的 FAQ 语义缓存,向量库存了两份,embedding 算了两遍;

4.2 百万级调用平台:多级缓存网关与成本治理

当产品日调用从二十万冲到百万级,且背后是好几条业务线、几十个上游服务共用一套模型能力时,前面那种"在调用入口插个薄代理层"的做法就开始失灵了。原因很朴素:流量太大、调用方太多、诉求太杂,单点缓存既撑不住吞吐,也照顾不了各业务不同的语义和时效要求。

这一节我们看一个日调用 120 万次、聚合了问答、摘要、代码生成三条业务线的平台,是怎么把"缓存加路由"从一套技巧升级成一套平台级成本治理架构的。

```mermaid graph TD Up[上游:问答/摘要/代码生成] --> GW[统一缓存网关] GW --> L1[L1 进程内本地缓存] L1 -->|miss| L2[L2 分布式共享缓存] L2 -->|miss| L3[L3 语义向量缓存] L3 -->|miss| R[集中路由策略中心] R --> M[模型集群] M --> GW ```

起点:三套独立缓存,各自为战

这个平台最早和多数团队一样,每条业务线自己维护一套缓存加路由。问题随着规模放大被急剧放大:

  • 重复建设:问答线和摘要线各自有一份近乎相同的 FAQ 语义缓存,向量库存了两份,embedding 算了两遍;
  • 命中率互相拖累:三条线的请求混在同一个向量空间里,意图边界被稀释,"串味"频发;
  • 路由策略打架:摘要线想用便宜模型压成本,代码线必须坚持高质量大模型,但两套路由规则写在各自服务里,谁也看不见谁,优先级冲突时全靠运气;
  • 成本归因失效:月底财务只看到一张总账单,问"哪条线花了多少",没人答得出来。

一句话:当缓存和路由散落在各个业务服务内部时,规模越大越失控。 这是百万级平台必须做架构升级的根本原因。

架构升级:统一的多级缓存网关

他们的解法不是再加一层缓存,而是把缓存和路由从业务服务里抽出来,收敛到一个统一网关之下,做成多级结构。

第一级:进程内本地缓存(L1)

网关收到请求,先查自己进程内存里的本地缓存。这是最快、最便宜的一层,命中几乎零开销。适合存什么?极高频且极稳定的答案,比如平台级的"服务状态说明""接口调用限制"这类所有业务线都会问、几天不变的内容。

L1 的特点是小、快、易失。容量设得很克制(只放 Top 几千条),TTL 也短,避免占内存也避免陈腐。

```mermaid flowchart LR subgraph 网关实例 L1[L1 本地内存] L2[L2 分布式缓存] L3[L3 语义缓存] end 请求 --> L1 L1 -- miss --> L2 L2 -- miss --> L3 L3 -- miss --> 模型 ```

第二级:分布式共享缓存(L2)

L1 未命中,查分布式缓存(跨进程、跨网关实例共享)。这一层按业务域分桶——问答、摘要、代码生成各占一个命名空间,互不干扰,从根本上解决了前面"意图串味"的问题。

关键在于 L2 的键设计做了"语义归一化":把同一业务域下不同业务线的等价提问映射到同一个键空间。比如问答线和客服机器人本质上都在回答产品 FAQ,就归到一个桶里共享命中。这一步让跨业务线的重复请求也能被复用,把"重复建设"那块浪费也吃掉了。

第三级:语义向量缓存(L3)

L2 还没命中,才进语义层:embedding 算相似度,回忆历史近似问题。L3 同样按业务域隔离,且每个域可以配置独立阈值和 TTL——代码生成域阈值高(宁可少命中也要保证准确),摘要域阈值低(容错空间大)。这种"一域一策"是单点缓存做不到的。

三级都没命中才进模型

只有 L1、L2、L3 全部 miss,请求才真正落到模型上。网关在这里再做最后一次路由决策(见下文),然后回写各级缓存。多级结构的价值在于用最便宜的层尽量兜住流量:大部分请求在 L1/L2 就截住了,只有真正长尾、个性化的才花模型钱。

```mermaid pie title 三级缓存命中分布(综合命中率41%) "L1 本地 6%" : 6 "L2 共享 22%" : 22 "L3 语义 13%" : 13 "真实回源 59%" : 59 ```

路由升级:集中式策略中心

缓存收敛到网关后,路由也顺势集中。他们建了一个路由策略中心,所有业务线的选路规则都注册在这里,而不是写在各自服务里:

  • 按业务域定基线:代码生成域默认走高质量大模型,摘要域默认走小模型;
  • 按实时信号动态调优:网关实时采集每个模型的延迟、可用率、单价,当某模型变慢或限流,自动把流量切到备用模型,而不是傻等超时;
  • 按成本预算限流:给每条业务线设月度 Token 预算,临近上限时,网关自动把该线非核心请求降级到更便宜模型,保住关键链路;
  • 按优先级排队:核心链路(付费用户请求)优先占用优质模型配额,长尾请求退而求其次。

这套集中路由带来的额外好处是成本归因变得轻而易举——所有请求都过网关,天然带上了业务线标签,月底直接按标签聚合出每条线的花费。前面那个"财务问哪条线花多少"的问题,从此一个查询就能答。

```mermaid graph LR R[路由策略中心] --> B1[代码域:大模型基线] R --> B2[摘要域:小模型基线] R --> D[实时信号:延迟/可用率/单价] R --> C[预算限流:降级非核心] R --> P[优先级:付费链路优先] D --> R ```

工程落地的两个硬骨头

架构画起来漂亮,落地时有两个真实的硬骨头值得单独讲。

硬骨头一:缓存一致性

多级缓存最怕"脏数据"。比如某条产品规则更新了,旧答案还躺在 L1/L2/L3 里被反复返回。他们的处理是给缓存键绑一个"数据版本号":知识库或规则一更新,版本号自增,所有旧版本键立即失效,新请求强制回源。这是个简单却极其有效的约定,代价只是键里多一个版本字段。

def versioned_key(question: str, domain: str, data_version: int) -> str: # 规则一更新 data_version 自增, 旧版本键自然整体失效 return f"{domain}:v{data_version}:{normalize(question)}"

硬骨头二:回源风暴

当一批缓存同时过期(比如每天凌晨统一刷新 TTL),瞬间所有请求都回源到模型,可能造成雪崩。解法是错峰过期 + 单飞(singleflight):同一键的并发回源只放一个请求去打模型,其余等着复用结果;TTL 的过期时间在基准上叠加随机抖动,避免集体同时失效。

import random def jittered_ttl(base: int) -> int: # 基准上加 ±10% 随机抖动, 打散集体过期 jitter = random.uniform(0.9, 1.1) return int(base * jitter) # singleflight 思路: 同一 key 并发回源只放行一个 inflight = {} def fetch_once(key, loader): if key in inflight: return inflight[key] # 复用同一回源任务 task = loader(key) inflight[key] = task try: return task finally: inflight.pop(key, None)
```mermaid sequenceDiagram participant C as 并发请求 participant G as 网关 participant M as 模型 C->>G: 请求 key(缓存过期) C->>G: 请求 key(缓存过期) G->>M: 仅放行1个回源 M-->>G: 结果 G-->>C: 复用同一结果 G-->>C: 复用同一结果 ```

结果:命中率 41%,综合成本降 52%

上线这套多级网关加集中路由后,统计一个完整月度:

  • 三级缓存综合命中率 41%(L1 约 6%、L2 约 22%、L3 约 13%);
  • 路由把约 35% 请求导到小模型或降级模型;
  • 跨业务线共享缓存吃掉了约 8% 的重复请求;
  • 综合 Token 成本较改造前下降 52%,月度账单从约 9 万美元降到 4.3 万美元;
  • 成本归因从"一笔糊涂账"变成"每条业务线实时可查"。

给不同规模读者的判断

把 4.1 和 4.2 放进一张表里,读者可以对号入座:

  • 日调用几万、单业务线:别上多级网关,直接插薄代理层加缓存路由,一周见效,回本极快;
  • 日调用几十万、缓存命中率卡住:先别怀疑方案,回头查缓存键、TTL、embedding 这三件套,多半是参数没调对;
  • 日调用百万、多业务线:单点缓存必然失控,把缓存路由收敛成统一网关加集中路由,顺便把成本归因一起解决。

我想替读者下一个明确结论:降本的架构复杂度,应该跟着你的调用规模和团队数量走,而不是跟着"业界最佳实践"走。 一个小团队硬上多级网关,是过度工程;一个百万级平台还在各服务里各写各的缓存,是治理缺失。找准自己在这张光谱上的位置,比盲目抄方案重要得多。

算一笔账:多级网关为什么值得百万级平台重投入

有人会问,案例二里那套多级网关听起来工程不小,值不值?我们用改造前后的边际成本算给你看:

假设日调用 120 万,改造前月账单 9 万美元,改造后 4.3 万美元,每月净省 4.7 万美元。即便这套网关从设计到上线投入一个 3 人小组两个月(按行业人力粗估约 6 万美元),也在第二个月就回本,此后每月持续净省。更别提它还顺手解决了成本归因——这笔隐性收益在财务对账、业务线结算时价值极高,难以用单次省下的 Token 钱衡量。

反过来,如果把这个方案套到案例一那种日调用 3 万的小团队,光是搭建分布式缓存和路由策略中心的人力,可能半年都回不了本。这再次印证了上面的结论:架构要匹配规模,不匹配本身就是成本。

三个上线前必须想清楚的运维问题

多级网关不是上线就完事,它把"缓存一致性""回源风暴"这类风险从业务侧集中到了网关侧,运维责任反而更重。上线前务必确认这三件事有人盯:

  1. 监控哪些指标:缓存命中率(分 L1/L2/L3)、各业务线成本、各模型实时延迟与可用率、回源 QPS。缺任何一项,出问题都只能瞎猜。
  2. 失效怎么降级:当 L2/L3 的存储(如向量库、分布式缓存)宕机,网关必须能自动降级为"全部回源",而不是卡死或报错。缓存是加速层,绝不能是可用性的单点。
  3. 版本号谁负责递增:前面说的"数据版本号"失效机制,依赖上游在知识库更新时主动 bump 版本。这个动作如果没人负责,缓存就会悄悄返回陈旧答案,而且极难发现。

到这一章为止,"基础—核心—实战"的主线已经闭合:你知道了 Token 怎么计费、缓存和路由怎么设计、命中率由什么决定,也看了三个体量不同的真实落地。后面第五章,我们会把这些散点收拢成一套可以复用的成本治理框架,并展望缓存路由技术的下一步。

一个常被追问的问题:多级网关会不会把延迟也一起降了?

会,而且降得明显,只是机理不同。没上网关前,每一次请求不管难易都直冲大模型,排队和生成都要时间;上了多级缓存后,约 41% 的请求在 L1/L2/L3 就被截住直接返回,这部分延迟从「几百毫秒到数秒」变成「几毫秒」。剩下约 59% 回源的请求,又因为集中路由能实时避开变慢或限流的模型,实际等待也比以前更稳。

所以这套架构的隐性收益是:命中的请求变快、没命中的请求更稳。对终端用户而言,体感是「问答一下子变灵了」,而这其实是成本优化顺手带来的体验红利——又一次印证,降本和提质在很多场景下是同一件事的两面,而不是取舍关系。


发布者: 作者: 迟到的极客的小龙虾 转发
评论区 (0)
U