2.3 缓存 + 路由协同的工程实现:中间件 / 网关模式 前两节我们分别把「缓存」和「路由」两道机制讲透了。但它们单独存在时,只是两个零件;真正在生产环境扛成本的是把它们拼成一道统一闸门——所有请求先过缓存,未命中再走路由,最后才真正调模型。这一节就给你一套可落地的工程骨架:中间件 / 网关模式,把两层串起来,并给出可复用的代码结构与配置模板。 读完这一节,你应能一句话概括:缓存与路由要共享同一个入口(网关),按「先缓存、后路由、再回填」的顺序串成一条流水线,才能同时吃掉「重复」和「差异化」两笔成本。 为什么必须「先缓存、后路由」 顺序很重要。正确的顺序是: 先查缓存:命中则直接返回,零额外 Token,连路由都不用走。 未命中再路由:由路由器决定调哪个模型档位。
前两节我们分别把「缓存」和「路由」两道机制讲透了。但它们单独存在时,只是两个零件;真正在生产环境扛成本的是把它们拼成一道统一闸门——所有请求先过缓存,未命中再走路由,最后才真正调模型。这一节就给你一套可落地的工程骨架:中间件 / 网关模式,把两层串起来,并给出可复用的代码结构与配置模板。
读完这一节,你应能一句话概括:缓存与路由要共享同一个入口(网关),按「先缓存、后路由、再回填」的顺序串成一条流水线,才能同时吃掉「重复」和「差异化」两笔成本。
顺序很重要。正确的顺序是:
如果反过来「先路由再缓存」,意味着每条请求都先消耗一次路由判断(甚至一次 embedding),即便它本可精确命中。缓存是第一道最便宜的闸门,必须排在最前。
最干净的实现,是在你的业务服务和模型 API 之间插一层网关(或中间件)。所有对模型的调用都必须经过它,业务代码完全无感知——它就像一道「智能代理」。
这层网关负责:
这样业务侧只要「调用模型」,降本逻辑全在网关里,互不污染,也方便统一升级。
下面给一个与语言无关的骨架,你按自己的栈(Python/Go/Node)替换即可。重点是流水线顺序和数据流:
def handle_request(user_input, context): # 1. 规范化输入,剥离噪声字段 normalized = normalize(user_input, context) # 2. 精确缓存查询(零成本、零误差) key = exact_hash(normalized) hit = cache.get(key) if hit: metrics.record("exact_hit") return hit # 3. 语义缓存查询(付费、有风险) if semantic_enabled(normalized): vec = embed(normalized) similar = vector_search(vec, threshold=0.92) if similar: metrics.record("semantic_hit") return similar.result # 高风险类目应跳过此步 # 4. 智能路由:先规则,后级联 tier = route_by_rules(normalized) or route_by_cascade(normalized) # 5. 调用选定模型 result = call_model(tier, normalized) # 6. 质量判官:不达标则升级 if not quality_pass(result, normalized): result = call_model(upgrade(tier), normalized) # 7. 回填缓存(懒写入:本次如需,下次沉淀) cache.put(key, result, ttl=pick_ttl(normalized)) return result
注意几个工程细节:
normalize 必须剥离私有状态(如用户 ID、时间戳),否则缓存键会膨胀且可能泄隐私。semantic_enabled 应按业务域开关:高风险域返回 False,强制走精确或直接模型。pick_ttl 按内容时效定:稳定知识长 TTL,易变事实短 TTL 甚至 0。quality_pass 是路由红线的执行点,前文讲过的判官在这里落地。把「规则、档位、阈值、红线」都做成配置,而非写死在代码里,方便运营调参:
cache: exact: enabled: true normalize: [trim, collapse_ws, drop_fields: [request_id, ts]] semantic: enabled: true threshold: 0.92 disabled_domains: [payment, medical, legal] ttl: stable_knowledge: 86400 volatile_fact: 300 private_state: 0 # 不缓存 routing: tiers: [cheap, mid, flagship] rules: - intent: [translate, classify, extract] -> cheap - intent: summarize & len<500 -> mid - intent: [reasoning, codegen] -> flagship cascade_order: [cheap, mid, flagship] quality_gate: rule_then_validator red_lines: - domains: [delete, payment, medical, legal] -> force flagship monitor: metrics: [hit_rate, tier_cost, quality_pass_rate] sample_review_ratio: 0.05
这套配置把「哪些意图走哪档、哪些域强制大模型、TTL 多长」全部外置,运营不改代码就能调,也方便 A/B 对比不同策略的成本。
网关上线不是终点。你必须持续看三个数:
每周回看一次,调规范化规则、调语义阈值、调档位映射。降本是「持续调参」的过程,不是一次配置完事。
在把骨架搬到生产前,有两个决策会直接决定这套方案是否稳,我必须单独拎出来说。
缓存可以放在应用进程内(本地 LRU/字典),也可以放在独立存储(Redis/向量库)。前者的好处是零网络开销、延迟最低,坏处是多实例部署时各实例缓存不共享,命中率被摊薄,且重启即丢。后者(Redis + 向量库)是所有实例共享同一份缓存,命中率最高,但需要额外的基础设施和网络往返。
我的建议:单实例或小规模先用进程内精确缓存(极低成本),一旦多实例或命中率上不去,立刻上 Redis 做精确层、上向量库做语义层。 语义缓存因为要 embedding 和向量检索,几乎必然走独立存储。
前文反复强调「含用户私有状态的请求要谨慎缓存」。在工程上,正确的做法是:在 normalize 阶段把私有字段(用户 ID、会话、余额快照)剥离,只对「脱敏后的非私有部分」做缓存键。如果整条结果都依赖私有状态(如「根据我的余额推荐」),那就直接把 TTL 设为 0(不缓存),或只在用户维度内做极短缓存。绝不能把 A 用户的结果返回给 B 用户——这是隐私事故,不是成本问题。
网关是统一入口,意味着它一旦挂了,所有模型调用都挂。所以网关本身必须设计降级:
一句话原则:任何降本组件故障,都要「优雅降级到『不省钱但能用』」,绝不能「降级到『不能用』」。
一个典型的中等规模部署长这样:业务服务集群 → 降本网关(无状态、可水平扩展)→ 缓存集群(Redis + 向量库)→ 模型供应商(多档位)。网关无状态很关键,这样你能随流量扩缩容,而不会把缓存状态绑死在单机。埋点数据异步上报到监控,运营在仪表盘上看到命中率、各档成本和质量通过率,据此调配置。
把闸门推上线前,我给你一份清单,缺一项都别轻易全量:
disabled_domains 关闭。cheap→mid→flagship 已配,质量判官已挂。不要一上来全量切。建议先放 5% 流量走新网关,对比这 5% 和剩下 95% 的:成本变化、质量抽查通过率、用户投诉率。如果成本降了、质量没塌、投诉没涨,再逐步放量到 20%、50%、100%。灰度期间特别盯「质量通过率」,一旦跌就回滚到旧链路——省钱永远排在「不出事」之后。
最后提醒几个容易建歪的地方:
把这四点避开,你的闸门就是一道真正的成本闸门,而不是又一个没人维护的中间件。很多团队省了钱却埋下技术债,根子就在这些架构细节上——宁可多花半天设计,也别日后返工。
把 2.1 的缓存(假设总命中率 60%)和 2.2 的路由(未命中部分再省 36%)叠起来:先靠缓存砍掉 60% 的调用,剩下 40% 里路由再省约 36%,综合下来整体模型调用成本可以降到原来的四成左右——也就是砍掉一半以上。而且用户侧几乎无感,因为缓存命中和路由选档都在毫秒级完成。
这一节的骨架是「配置驱动」的,适合大多数团队快速落地。当你的流量和标注数据积累够了,还可以往前走一步——让路由自己学:用历史请求和「实际质量/成本」作为训练信号,训练一个轻量的路由策略模型,自动决定每条请求的档位,甚至动态调语义阈值。但这属于进阶玩法,依赖足够多的埋点数据,我们留在后续章节的原则部分再展开,这里先不铺开,避免你一上来就陷入「要不要上 ML」的纠结。先把配置驱动的闸门跑稳,成本账已经很好看了。
回到开头那句话——缓存和路由要共享一个入口、按「先缓存后路由再回填」串成流水线。你今天至少带走一个可复用的骨架:网关在中间,缓存在前,路由在后,回填收尾,全程埋点。 到这一节,第二章的核心模块已经完整——你有了能直接挡在模型前的「缓存+路由」闸门。但闸门背后的数学(命中率到底能省多少、路由决策树怎么算最优)我们留到第三章从原理讲透。