4.2 百万级调用平台:多级缓存网关与成本治理 当产品日调用从二十万冲到百万级,且背后是好几条业务线、几十个上游服务共用一套模型能力时,前面那种"在调用入口插个薄代理层"的做法就开始失灵了。原因很朴素:流量太大、调用方太多、诉求太杂,单点缓存既撑不住吞吐,也照顾不了各业务不同的语义和时效要求。 这一节我们看一个日调用 120 万次、聚合了问答、摘要、代码生成三条业务线的平台,是怎么把"缓存加路由"从一套技巧升级成一套平台级成本治理架构的。 起点:三套独立缓存,各自为战 这个平台最早和多数团队一样,每条业务线自己维护一套缓存加路由。问题随着规模放大被急剧放大: 重复建设:问答线和摘要线各自有一份近乎相同的 FAQ 语义缓存,向量库存了两份,embedding 算了两遍;
当产品日调用从二十万冲到百万级,且背后是好几条业务线、几十个上游服务共用一套模型能力时,前面那种"在调用入口插个薄代理层"的做法就开始失灵了。原因很朴素:流量太大、调用方太多、诉求太杂,单点缓存既撑不住吞吐,也照顾不了各业务不同的语义和时效要求。
这一节我们看一个日调用 120 万次、聚合了问答、摘要、代码生成三条业务线的平台,是怎么把"缓存加路由"从一套技巧升级成一套平台级成本治理架构的。
这个平台最早和多数团队一样,每条业务线自己维护一套缓存加路由。问题随着规模放大被急剧放大:
一句话:当缓存和路由散落在各个业务服务内部时,规模越大越失控。 这是百万级平台必须做架构升级的根本原因。
他们的解法不是再加一层缓存,而是把缓存和路由从业务服务里抽出来,收敛到一个统一网关之下,做成多级结构。
网关收到请求,先查自己进程内存里的本地缓存。这是最快、最便宜的一层,命中几乎零开销。适合存什么?极高频且极稳定的答案,比如平台级的"服务状态说明""接口调用限制"这类所有业务线都会问、几天不变的内容。
L1 的特点是小、快、易失。容量设得很克制(只放 Top 几千条),TTL 也短,避免占内存也避免陈腐。
L1 未命中,查分布式缓存(跨进程、跨网关实例共享)。这一层按业务域分桶——问答、摘要、代码生成各占一个命名空间,互不干扰,从根本上解决了前面"意图串味"的问题。
关键在于 L2 的键设计做了"语义归一化":把同一业务域下不同业务线的等价提问映射到同一个键空间。比如问答线和客服机器人本质上都在回答产品 FAQ,就归到一个桶里共享命中。这一步让跨业务线的重复请求也能被复用,把"重复建设"那块浪费也吃掉了。
L2 还没命中,才进语义层:embedding 算相似度,回忆历史近似问题。L3 同样按业务域隔离,且每个域可以配置独立阈值和 TTL——代码生成域阈值高(宁可少命中也要保证准确),摘要域阈值低(容错空间大)。这种"一域一策"是单点缓存做不到的。
只有 L1、L2、L3 全部 miss,请求才真正落到模型上。网关在这里再做最后一次路由决策(见下文),然后回写各级缓存。多级结构的价值在于用最便宜的层尽量兜住流量:大部分请求在 L1/L2 就截住了,只有真正长尾、个性化的才花模型钱。
缓存收敛到网关后,路由也顺势集中。他们建了一个路由策略中心,所有业务线的选路规则都注册在这里,而不是写在各自服务里:
这套集中路由带来的额外好处是成本归因变得轻而易举——所有请求都过网关,天然带上了业务线标签,月底直接按标签聚合出每条线的花费。前面那个"财务问哪条线花多少"的问题,从此一个查询就能答。
架构画起来漂亮,落地时有两个真实的硬骨头值得单独讲。
多级缓存最怕"脏数据"。比如某条产品规则更新了,旧答案还躺在 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)
上线这套多级网关加集中路由后,统计一个完整月度:
把 4.1 和 4.2 放进一张表里,读者可以对号入座:
我想替读者下一个明确结论:降本的架构复杂度,应该跟着你的调用规模和团队数量走,而不是跟着"业界最佳实践"走。 一个小团队硬上多级网关,是过度工程;一个百万级平台还在各服务里各写各的缓存,是治理缺失。找准自己在这张光谱上的位置,比盲目抄方案重要得多。
有人会问,案例二里那套多级网关听起来工程不小,值不值?我们用改造前后的边际成本算给你看:
假设日调用 120 万,改造前月账单 9 万美元,改造后 4.3 万美元,每月净省 4.7 万美元。即便这套网关从设计到上线投入一个 3 人小组两个月(按行业人力粗估约 6 万美元),也在第二个月就回本,此后每月持续净省。更别提它还顺手解决了成本归因——这笔隐性收益在财务对账、业务线结算时价值极高,难以用单次省下的 Token 钱衡量。
反过来,如果把这个方案套到案例一那种日调用 3 万的小团队,光是搭建分布式缓存和路由策略中心的人力,可能半年都回不了本。这再次印证了上面的结论:架构要匹配规模,不匹配本身就是成本。
多级网关不是上线就完事,它把"缓存一致性""回源风暴"这类风险从业务侧集中到了网关侧,运维责任反而更重。上线前务必确认这三件事有人盯:
到这一章为止,"基础—核心—实战"的主线已经闭合:你知道了 Token 怎么计费、缓存和路由怎么设计、命中率由什么决定,也看了三个体量不同的真实落地。后面第五章,我们会把这些散点收拢成一套可以复用的成本治理框架,并展望缓存路由技术的下一步。
会,而且降得明显,只是机理不同。没上网关前,每一次请求不管难易都直冲大模型,排队和生成都要时间;上了多级缓存后,约 41% 的请求在 L1/L2/L3 就被截住直接返回,这部分延迟从「几百毫秒到数秒」变成「几毫秒」。剩下约 59% 回源的请求,又因为集中路由能实时避开变慢或限流的模型,实际等待也比以前更稳。
所以这套架构的隐性收益是:命中的请求变快、没命中的请求更稳。对终端用户而言,体感是「问答一下子变灵了」,而这其实是成本优化顺手带来的体验红利——又一次印证,降本和提质在很多场景下是同一件事的两面,而不是取舍关系。