ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

大模型调用稳定性治理:重试、限流、降级与成本控制实战

大模型调用稳定性治理:重试、限流、降级与成本控制实战 去年我们团队上线了一个 AI 助手功能第一天就撞上两件事业务群里反复有人反馈“AI 没反应”另一边账单后台显示 token 消耗比预估高了三成。查到最后一个是重试策略写得太粗暴超时了就死命重发结果把上游限流给打爆了另一个是 prompt 里塞了太多历史记录每次调用都在为无用的上下文付钱。这两件事指向同一个核心大模型调用根本不能当成普通 HTTP 接口来治理它慢、贵、不稳定三个特性叠在一起让重试、限流、降级与成本控制从“可选项”直接变成了“必修课”。这篇文章我就围绕这四个关键词展开讲清楚它们各自该怎么做、为什么这样做以及我们踩过哪些坑。适合正在做 AI 应用、Agent 系统、大模型 API 接入的开发和架构同学参考也适合刚接触大模型工程化、想从“调通接口”走向“稳定上线”的团队读一读。1. 大模型调用为什么需要一套独立的稳定性治理方案这章先把问题摆到台面上大模型调用和普通接口调用到底差在哪为什么通用方案搬过来不好使。1.1 慢、贵、不稳定是三个完全不同的问题先说延迟。普通后端接口的 P95 一般要求 200ms 以内但大模型接口从发起请求到拿到完整响应动辄 3 秒、5 秒、甚至 10 秒以上。即使只看首字延迟TTFT主流模型也要 1 到 3 秒。延迟一上去用户侧就会疯狂刷新、重复点击前端就会自动重发最终看到的都是“AI 卡住了”。再说成本。大模型按 token 计费输入和输出分开算一次复杂推理花几美分是常态。如果调用量每天几十万次那账单就不是“云资源费”的概念而是需要单独做成本预算和封顶控制的专项支出。普通接口调用失败重发一次只浪费一点 CPU大模型调用重发一次是实打实在烧钱。最后说不稳定。模型服务的故障形态也和普通接口很不一样它不仅会返回 5xx、超时还会出现 429 限流、上下文超长报错、内容过滤拦截、甚至“模型本轮只输出了思考过程、没有产出正文”这种半成功状态。这些异常码在传统接口里根本没有对应物处理逻辑也就没法直接抄。1.2 整个调用链路的失败点分布得看清楚一次完整的大模型调用链路长得很客户端 → 业务服务 → 网关/代理层 → 模型服务商网关 → 模型实例。每一层都可能出问题而且问题会互相放大。客户端侧的失败用户网络断了、页面刷新了、请求取消了。业务服务侧线程池满了、内存不够、超时配置不合理。代理/网关侧上游配额耗尽每分钟请求数 RPM、每分钟 token 数 TPM被服务商限流。模型实例侧高负载排队、推理服务重启、长文本处理超时。传统接口做治理通常只关心“请求成功率”和“响应时间”但大模型调用得多看几个维度限流配额RPM/TPM、token 计费、上下文长度、模型版本差异。你不单独设计一套治理方案直接用普通的熔断组件去套很容易出现“上游限流了还在傻重试”“降级切到了更贵的模型导致账单爆炸”这类哭笑不得的事故。2. 四件事一起设计避免单点补丁重试、限流、降级、成本控制不是四个独立功能它们是一套互相咬合的体系。单独搞定任何一个其他环节都会拖后腿。2.1 四者之间的关系先理清楚我习惯用一句话概括四者的关系限流是前提重试是止损降级是兜底成本是边界。限流先行是为了不让你的调用洪峰打爆上游配额也防止服务端被瞬时流量冲垮。重试是在失败时做有限度的补救注意是“有限度”不是“无限次”。降级是在模型不可用时切换到备用方案让系统不至于整体宕掉。成本控制横跨所有环节它告诉你重试最多几次、降级能不能用贵模型、缓存能省多少钱。如果这四件事分开设计就会踩到这些典型问题重试和限流打架某个模型一直返回 429你不分析原因就按固定间隔重试 5 次结果限流更严重还占用业务线程。降级没考虑成本平时用便宜模型一旦超时降级到旗舰模型一次调用贵 10 倍流量一大账单直接失控。限流没考虑重试放行网关限住了但应用层不知道还在傻等重试窗口用户体验变成“无限转圈”。所以架构上必须把四件事放在同一层统一设计而不是散落在各个业务的代码里各写各的。2.2 落地方案网关层 vs 应用层 SDK我们团队最终采用的是“应用层 SDK 为主 网关层配额兜底”的结构。核心原因很现实团队业务代码里到处直接调用模型接口统一走自研轻量 SDK把重试、限流、降级、成本拦截逻辑全部收敛进去业务方只需要配置几行参数。网关层则独立部署一套流量代理负责总配额分配和全局出事时的快速止血。比如某个业务方异常调用把 RPM 打满网关直接拦掉一半流量避免整个团队的调用额度都被耗光。如果你团队规模小、没有专职做网关的人那么至少在应用层建一个统一的 Client 封装把公共逻辑沉淀进去。最怕的就是每个业务线各自接 SDK、各自写超时重试最后线上出了问题都不知道该改哪条链路的代码。3. 重试机制不是失败就重来那么简单重试是四件事里最容易上手、也最容易写坏的一环。很多同学半开玩笑说“重试嘛catch 住再来一次不就行了”但大模型场景真的不是这样。3.1 错误分类哪些值得重试哪些必须跳过我第一次做大模型接入时给所有异常统一加了重试结果直接跑出线上事故某个请求携带了过长的上下文返回了 context_length_exceeded 错误我们傻乎乎地每 3 秒重试一次重试了 6 次上游每响应一次就计费一次就这么几秒钟烧掉不少钱。后来我做了一张“可重试/不可重试”的清单团队每次接入新模型前都要对一遍错误类型是否可重试处理建议请求超时无响应体可重试用指数退避重试 2-3 次5xx 服务端错误可重试可能服务正在重启稍后重试429 限流可重试必须读取 Retry-After按服务端要求等待4xx 客户端参数错误不可重试修代码或参数重试只会重复报错context_length_exceeded不可重试触发上下文压缩、截断后再发起新请求内容过滤/content_filter不可重试需要走人工审核流程“半成功”输出思考过程无正文可重试但应提升输出预算参数而不是盲目重发这里最容易被忽视的就是“半成功”状态。现在一些推理模型会先输出思考过程你设置了低输出预算它把预算全花在思考上正文一个字没出系统显示成功但内容为空。这种状态不算程序异常常规重试逻辑根本捕获不到。当时我们线上就出现“模型本轮只输出了思考过程、没有产出正文。系统已自动重试 2 次逐级提升输出预算”的日志后来做了个专门判断如果返回内容为空且存在思考字段就自动调整参数重试一次而不是无脑重发。3.2 指数退避与抖动Jitter重试的体面姿势固定间隔重试是最坏的策略所有人同时失败、同时重试下游直接被“重试风暴”打崩。正确做法是指数退避加抖动。指数退避的公式很简单import random import time def get_retry_delay(attempt: int, base_seconds: float 1.0, cap_seconds: float 10.0): # 退避时间 min(基础时间 * 2^attempt, 上限) exponential min(base_seconds * (2 ** attempt), cap_seconds) # 加抖动取 0 到指数值之间的随机数避免所有请求同时重试 jittered random.uniform(0, exponential) return jittered比如第一次重试等待 0~1 秒第二次 0~2 秒第三次 0~4 秒封顶 10 秒。注意这个抖动是必须的没有抖动的退避是“整齐划一的退避”无法解决并发场景下所有客户端同步重试的问题。另外很多模型服务商在返回 429 时会附带 Retry-After 响应头表示要等多长时间再重试。我们的策略是服务商明确给了时间就以它为准没给时间才走自己的指数退避。注意重试次数一定要封顶。我们默认配置最多重试 3 次超过后直接返回失败进入降级流程。无上限的重试等于把故障放大还会拖着线程池和账单一起陪葬。3.3 重试的三个经典坑重复计费大模型接口天然非幂等你发一次请求失败超时上游可能已经生成结果了只是响应没回来。你重试一次就等于同一件事付了两次费。缓解方案有二一是请求时携带业务幂等 ID部分服务商支持去重二是把“结果按输入 hash 缓存”超时后先在短时间内查一次缓存命中就免去重试。重试放大压力本来 10% 的失败率重试 3 次后实际打到上游的请求量增加了 30%限流被诱发429 变多又触发更多重试形成循环。所以全局要监控“总调用量/有效调用量”的比值超过 1.3 说明重试配置太激进。同步重试占线程一个用户发给你的请求你同步重试 3 次每次等 5 秒业务线程被挂住 15 秒。在高并发下线程池直接耗尽。更好的方式是把失败任务投递到异步队列后台慢慢补用户侧先拿到“正在处理”的状态。4. 限流方案从令牌桶到多级配额管理限流不是为了限制自己人而是为了同时保护两样东西上游服务商的配额和你的账单。4.1 算法选型体验大模型场景下我建议做“双维度限流”一方面限制 QPS/RPM每分钟请求数另一方面限制 TPM每分钟 token 数。为什么不能只看 QPS因为大模型接口的负载和 token 数强相关。100 个短请求和 1 个超长请求消耗的资源天差地别。只看 QPS你可能放过去一个超长请求打爆上游。算法层面令牌桶依然是最顺手的方案。它允许一定程度的突发流量适合大模型调用这种“偶尔集中发一批”的模式。漏桶算法则更激进一些严格平滑请求速率适合你对接的上游特别脆弱、一点突发都不允许的场景。滑动窗口适合精确控制某一秒内的请求数但在 token 维度就不太方便了。落到代码上我们用的简单令牌桶实现如下伪代码形式import time class TokenBucket: def __init__(self, capacity: int, refill_per_second: float): self.capacity capacity self.tokens capacity self.refill_per_second refill_per_second self.last_refill time.monotonic() def take(self, tokens: int) - bool: now time.monotonic() # 先补充令牌 elapsed now - self.last_refill self.tokens min(self.capacity, self.tokens elapsed * self.refill_per_second) self.last_refill now if self.tokens tokens: return False self.tokens - tokens return True业务调用前调用take(1)如果按 token 限流就传入当前请求的 token 估算值。拿不到令牌就返回限流信号进入降级流程而不是硬等。4.2 多级限流与配额池划分链路上一共做三层限流第一层业务进程内本地限流用上面这种内存令牌桶防住单机突发。第二层分布式限流用 Redis 实现全局限流防止多实例加起来超过上游配额。这一步非常关键很多人只在单机限流部署了 20 个 Pod 以后调用量直接放大 20 倍。第三层网关层配额兜底按业务线分配 RPM/TPM 预算。比如 A 业务分配 40% 配额B 业务 30%C 业务 30%某个业务流量暴涨也不能抢别人的份额。我们当时踩过一个大坑所有业务共用同一个模型 API Key网关没做业务间隔离某个做数据清洗的任务一次性发了几千个并发请求直接把整个团队的 RPM 配额打满核心在线业务全部 429。后来才把配额池化核心业务单独分配配额批处理任务单独分配宁可让批处理等也不能影响线上用户体验。4.3 限流参数的动态调整与预留限流参数不是配一次就完事。模型服务商的配额限制可能随账号等级变化模型上线初期你的调用量也可能波动很大。建议把限流参数做成可热更新配置配合监控数据动态调。还有一个小技巧给核心链路预留 10%~20% 的 buffer 配额平时不放开遇到突发流量或者上游配额临时收紧时启用。这种“给关键业务留后手”的习惯在几次线上故障里都救了命。5. 降级策略让系统在模型故障中优雅运行降级的目标是模型全挂了你的系统还能给用户一个说得过去的反馈而不是统一报 502。5.1 模型分级降级链我们建了一条三级降级链旗舰模型 → 经济模型 → 本地小模型/缓存结果。核心业务默认使用经济模型复杂推理和高质量生成任务才升级到旗舰模型。旗舰模型超时或报错时自动降级到经济模型经济模型也失败时落到本地小模型或者缓存结果。为什么默认不用旗舰模型还是成本。旗舰模型的价格一般是经济模型的 8~10 倍很多常见任务用经济模型绰绰有余。降级配置大概是这样的model-router: primary: gpt-class-flagship fallback_1: gpt-class-mini fallback_2: local-small-model fallback_3: cached-result-or-default-reply fallback_conditions: error_rate_window: 30s error_threshold: 0.15 # 错误率超过 15% 触发降级 latency_threshold_ms: 10000 # P95 延迟超过 10s 触发降级 quota_exhausted: true # 配额耗尽触发降级降级条件要明确。我们采用“窗口统计 熔断开关”的组合统计最近 30 秒内的错误率和平均延迟超过阈值就拉开关把流量切换过去。这种做法比“单次失败就立刻降级”要稳得多不会因为一次偶发抖动就切换模型造成体验震荡。5.2 熔断、半开探测与缓存兜底熔断器是三态模型关闭正常放行、打开直接降级不给上游发请求、半开放少量探针流量测试是否恢复。大模型场景完全适用特别是上游模型服务出故障时熔断能在第一时间拦住成片的无效请求。熔断器打开以后所有请求直接走降级链路不给上游压力同时后台定一个探测间隔。间隔到了切到半开状态放 5% 流量过去测试如果成功率达到阈值熔断器关闭恢复正常调用。半开的流量比例要保守一点我见过一恢复就全量放出去又把上游打挂的。缓存是降级兜底里性价比最高的环节。我们做了两层缓存完全匹配缓存相同的请求内容在有效期内直接返回历史结果适合天气查询、政策解读这类输入模式固定的场景。语义缓存用 embedding 计算输入相似度相似度超过一定阈值就直接复用历史回答适合问答场景里用户反复用不同措辞问同一个问题的情况。语义缓存有一个阈值调参的坑阈值设太高命中率低基本没用设太低会出现答非所问把 A 问题的回答套给相关但不相同的 B 问题。我们最后从线上反馈里反复调才把阈值定在一个“宁可少命中、不能乱命中”的位置。6. 成本控制从账单倒推优化成本控制这件事我强烈建议“先看懂账单再谈优化”。否则你连钱花在哪都不知道谈什么省钱。6.1 先看懂 token 账单怎么组成主流模型的计费方式大同小异一般分三块输入 token、输出 token、缓存输入 token。以当前主流厂商的公开价格为例做一个简化对比计费项经济模型价格约旗舰模型价格约输入0.15~0.5 美分/千 token2.5 美分/千 token输出0.6~1.5 美分/千 token10 美分/千 token缓存命中输入通常为输入价的 50% 左右通常为输入价的 50% 左右这里的关键结论是输出 token 通常比输入 token 贵 2~4 倍旗舰模型比经济模型贵 10 倍以上。所以成本优化的优先级非常清晰先减少输出浪费再减少无用的输入最后才是换模型。6.2 上下文工程最容易忽略的成本黑洞前阵子看到“1M 上下文已经全量可用”的消息很多开发同学第一反应是“太好了可以把整个文档库都塞进去了”。作为一个被账单毒打过的人我劝你冷静。长上下文不仅能塞更多内容也能吞掉更多预算。上下文每涨 1000 token输入成本就涨一块多轮对话还会把历史消息层层累加一个长会话跑到最后每次调用光输入可能就上万 token。我们的优化手段有三个系统提示词瘦身删掉冗余描述、压缩指令、把示例从 5 条减到 1 条。零散省下的 token 在千万级调用量下非常可观。历史会话摘要多轮对话中把超过 N 轮的消息做一次摘要把摘要作为后续上下文而不是把完整历史全部带上。用 RAG 替代全量塞入需要知识库问答时先做检索只把命中的 Top-K 段落拼进上下文而不是把整个文档库塞进去。这里再强调一个坑请求上下文过长会触发 context_length_exceeded 错误。如果你不处理它不会自动截断只会反复报错。我们的做法是在发送前统计 token 估算值超过阈值就先做压缩再请求而不是等报错后再补救。6.3 模型分级路由与成本预算帽模型路由的核心思想很简单简单任务走便宜模型复杂任务才走贵模型。你可以用规则路由按业务类型、prompt 长度、是否需要 JSON 输出也可以做语义路由用小模型判断任务难度再分发到不同模型。成本预算帽也是我们踩了不少坑以后才建起来的每天设定硬性预算上限达到上限后系统自动把流量切到经济模型或者对非核心任务直接拒绝生成并提示用户稍后再试。没有预算帽的时候出现过线上 bug 导致循环调用模型一晚上烧掉大半月预算的事故。有了预算帽基本就能防住这类低级错误。再提供一个成本估算模板方便你做预算假设日均调用 10 万次平均每次输入 2000 token、输出 500 token。经济模型输入价格按 0.5 美分/千 token、输出按 1.5 美分/千 token则单次成本约等于 0.5×2000/1000 1.5×500/1000 1 0.75 1.75 美分日成本约 1750 美元月成本约 5.25 万美元。如果把 30% 的流量切到旗舰模型输入按 2.5 美分/千 token、输出按 10 美分/千 token单次成本变成 2.5×2000/1000 10×500/1000 5 5 10 美分日成本增加10 万×30%×(10-1.75) 美分 2475 美元。这个例子说明模型路由的收益巨大也说明在没做路由规划前盲目升级模型有多危险。7. 实践中的高频故障与排查心得前面讲的都是设计层面最后聊几个真实故障案例帮大家建立排查直觉。7.1 三个典型的线上事故复盘事故一重试风暴打爆 429。上线初期我们把重试次数从 2 提高到 5结果模型服务商短时故障时所有客户端同时重试把 RPM 配额直接打满故障恢复后 429 还在持续。排查后发现是缺少“重试等待时间上限”和“重试放行比例控制”。修复后她把重试次数改回 3增加了全局限速和熔断问题不再出现。事故二降级切到贵模型账单三倍增长。某个模型因为负载高出现部分超时自动降级策略把所有超时都切到了旗舰模型。错误率不算高但旗舰模型价格高几天下来成本飙升。后来我们在降级策略上加了条件只有错误率超过阈值时整体切换而不是单次失败就升级模型。事故三语义缓存答非所问。一名用户问“怎么退款”另一名用户问“怎么退货”embedding 相似度很高系统直接返回了退款流程的回答。用户觉得答非所问。后来把语义缓存阈值调高同时增加了“关键词不一致则强制走真实调用”的规则。7.2 监控指标与预警配置这一章最后给出我们认为最值得盯的指标指标说明预警阈值参考有效调用量/总调用量反映重试放大倍数超过 1.3 告警429 比例限流严重程度超过 5% 观察超过 10% 告警错误率5xx 加超时等错误占比超过 5% 告警10% 拉熔断P95 响应时间用户体验关键指标按业务容忍度设置TPM 配额剩余量上游配额消耗情况低于 20% 告警缓存命中率成本优化效果完全匹配缓存低于 30% 提示日成本速率预算消耗速度超过当日预算 80% 预警7.3 最后一点个人体会做了快两年大模型应用治理我的核心感受是这类系统的问题永远不会一次性消失。你修好重试限流露头你压好成本一个新模型上线就把调用量翻倍。与其追求“完美的首次设计”不如建立一套“可观测、可调整、有兜底”的机制。重试次数、限流阈值、降级条件、成本预算这些参数天然是动态的需要持续根据线上数据去修正。产品上线后我给自己定了一条规矩每次改完参数先小流量验证再调大放量比例同时盯着账单后台。说实话模型能力会越来越强但工程上的稳定性和成本意识才是团队能长期依赖的东西。
返回列表