资讯动态

大模型应用心跳机制成本黑洞:如何避免token被无效消耗

发布时间:2026/10/20 18:48:24 来源:尧图企业网站定制
1. 从一个让人肉疼的账单说起上个月帮一个做智能客服的朋友看后台账单他指着屏幕上一串数字问我“为什么我的调用量比实际用户消息多了快三倍”我扫了一眼他的架构图就笑了——典型的“心跳陷阱”。他的系统里每个会话连接都挂着一个定时器每隔几秒就往模型接口发一次探测请求用来判断“连接还活着吗”。用户一天真正说话可能就几百次但心跳请求跑了上万次每一次都在烧token。这个现象我管它叫“小龙虾的heartbeat”——小龙虾在水里不停地扇动鳃看起来是在呼吸实际上大部分动作都是无效的维持性动作。很多AI应用的架构里也有这种“鳃”它不产生任何用户价值却在后台一刻不停地消耗你的额度。这篇文章就把这个隐蔽的成本黑洞拆开讲清楚心跳机制为什么会吃掉token、哪些设计模式最容易踩坑、怎么用最小的代价把它摁住。不管你是刚接上大模型API的独立开发者还是维护着几十个微服务的中台负责人这里面的账都值得算一算。2. 心跳机制到底在“跳”什么2.1 心跳的本质一个善意的谎言心跳机制最早来自分布式系统用来解决一个朴素的问题我怎么知道对面那个服务还活着网络是不可靠的TCP连接可能因为中间设备超时被悄悄掐断而两端都以为对方还在。于是就有了心跳——每隔固定时间发一个极小的数据包收到回复就认为链路正常收不到就重连。这个设计在传统后端里几乎零成本一个空包几十字节发一万次也就几百KB。但到了大模型应用里事情变了。很多开发者把“心跳”直接实现成了“向模型发一条消息”因为模型接口本身就是他们唯一的交互通道。于是原本几十字节的探测包变成了几百甚至上千token的请求。心跳从“维持连接”变成了“维持对话”成本结构完全变了。2.2 为什么大模型场景下心跳会失控我观察下来失控通常来自三个叠加因素。第一是计费粒度。传统心跳按流量计费大模型按token计费而且输入输出都算钱。一个心跳请求哪怕只发“ping”两个字加上系统提示词、历史上下文实际消耗可能是几百token。第二是上下文累积。很多框架会把心跳消息也塞进对话历史下一次请求时整个历史又被重新计费形成滚雪球。第三是并发放大。一个用户会话一个心跳一千个在线用户就是一千个定时器如果间隔设成5秒一天就是一千七百多万次请求。这三个因素单独看都不致命叠在一起就是灾难。我见过最夸张的案例一个内部工具类应用日活不到两百人但因为心跳设计不当一个月烧掉了六位数的token费用。负责人一直以为是用户在疯狂提问查了日志才发现90%以上的请求都是心跳。2.3 心跳和“保活”的边界在哪里这里要区分两个概念连接保活和会话保活。连接保活是网络层的事用TCP keepalive或者WebSocket的ping/pong帧就能解决根本不经过模型。会话保活是应用层的事比如你想让模型记住上下文或者想检测用户是否还在线。问题出在很多人把会话保活也做成了“定期问模型一句话”。正确的做法是连接层用协议自带的保活机制会话层用本地状态管理。模型只应该在真正需要生成内容的时候被调用。任何“为了确认还活着”而调用模型的行为都是在给小龙虾喂token。3. 那些悄悄吃掉token的心跳设计模式3.1 定时轮询式心跳最贵的一种“在吗”这是最常见也最烧钱的一种。实现方式通常是setInterval或者setTimeout循环每隔N秒向模型接口发一条消息内容可能是“在吗”、“继续”、“ping”或者干脆是空字符串。开发者觉得反正消息很短花不了几个钱。但实际账单会告诉你短消息不等于短token。我拿一个真实场景算过账。假设系统提示词是500token每次心跳请求都带上完整系统提示词心跳间隔10秒一个用户在线8小时。那么单个用户一天的心跳请求是2880次消耗token是2880乘以500等于144万token。一百个这样的用户就是1.44亿token。按主流模型的价格这已经是一笔不小的开支。而这144万token里没有一个字是用户真正想说的。更隐蔽的是很多框架会把心跳请求的响应也计入输出token。模型收到“在吗”出于礼貌回一句“我在有什么可以帮您”又是几十token。输入加输出双倍消耗。3.2 上下文携带式心跳滚雪球效应比定时轮询更可怕的是“带上下文的心跳”。有些开发者为了让心跳“看起来自然”会把之前的对话历史一起发过去。第一次心跳带1条历史第二次带2条第三次带3条……token消耗呈线性增长总消耗呈平方级增长。我拆过一个开源项目的代码它的心跳逻辑是这样的每次心跳时把当前会话的所有消息拼成一个数组发给模型然后取最后一条回复作为“心跳响应”。用户聊了50轮之后一次心跳的输入token就超过了一万。而心跳还在每5秒发一次。这个项目的作者在issue里抱怨“模型太贵了”其实贵的是他自己的心跳设计。这种模式还有一个变种把心跳消息也存进数据库下次加载会话时又带出来。等于心跳不仅当时花钱以后每次对话还要为它再花一次钱。我称之为“token的复利”只不过这个复利是负的。3.3 多路复用失效一个用户多个心跳微服务架构下一个用户请求可能经过网关、会话服务、模型服务、日志服务等多个环节。如果每个环节都独立维护一个心跳就会出现“一个用户多个心跳”的情况。我见过一个系统前端一个心跳、网关一个心跳、会话服务一个心跳、模型服务一个心跳四层心跳叠加实际消耗是设计预期的四倍。这种问题的根源是职责不清。每个团队都觉得“我这一层需要保活”但没有人从全局看总共有多少心跳在跑。等到账单出来每个团队都觉得自己那部分“没多少钱”合起来却是个大数目。3.4 心跳与重试的混淆失败一次烧三次还有一种情况是把心跳和重试逻辑混在一起。心跳请求超时了系统自动重试重试又超时再重试。本来一次心跳只应该花一次钱结果因为网络抖动一次心跳变成了三次请求。如果重试时还带着上下文消耗就更夸张。我调过一个案例某系统的模型接口平均响应时间是2秒但心跳超时设成了1秒。结果每次心跳都超时每次都重试三次实际请求量是设计量的三倍。开发者一直以为是模型服务不稳定其实是自己的超时参数设错了。4. 把心跳成本摁下去的实操方案4.1 第一步给心跳做一次“尸检”在优化之前先搞清楚你的系统里到底有多少心跳在跑。我的做法是加一层日志中间件把所有发往模型接口的请求打上标签typeuser_message、typeheartbeat、typeretry、typesystem。跑一天之后统计各类请求的占比和token消耗。这个动作看起来简单但很多团队从来没做过。我帮一个团队做这个统计时发现他们70%的模型调用来自一个“健康检查”定时任务而这个任务是一个已经离职的同事两年前加的没人知道它还在跑。关掉之后当月账单直接降了六成。统计的时候要注意区分请求次数和token消耗。有些心跳请求次数多但token少有些次数少但每次带大量上下文。两个维度都要看才能找到真正的成本大头。4.2 第二步把心跳从模型层赶到协议层确认了心跳的存在之后第一件事就是把它从模型调用里剥离出来。具体怎么做取决于你的通信方式。如果是WebSocket直接用协议自带的ping/pong帧。客户端每隔30秒发一个ping帧服务端回一个pong帧这个过程完全不经过业务逻辑更不经过模型。token消耗为零。如果是HTTP短连接用TCP keepalive或者HTTP/2的ping帧。这些都在传输层完成应用层根本感知不到。如果确实需要应用层的心跳比如要检测用户是否还在当前页面那就用一个独立的轻量接口比如/api/health这个接口只返回一个状态码不调用模型。核心原则就一句话心跳不应该知道模型的存在。任何让心跳和模型发生关系的设计都是在烧钱。4.3 第三步会话状态本地化别让模型当“记忆管家”很多心跳之所以要调模型是因为开发者把会话状态存在了模型侧。比如靠对话历史来维持上下文靠模型回复来判断会话是否有效。这种做法在原型阶段很方便但到了生产环境就是成本灾难。正确的做法是把会话状态存在本地。用Redis或者内存缓存存对话历史用本地时间戳判断会话是否过期。模型只负责生成内容不负责记忆。每次真正需要生成时从本地取出相关历史拼成prompt发给模型。心跳完全不参与这个过程。我自己的项目里会话状态全部放在Redis设置30分钟过期。心跳只检查Redis里的key是否存在存在就续期不存在就清理。整个过程零模型调用。上线半年token消耗比之前降了八成。4.4 第四步给心跳设一个“预算上限”即使做了上面三步有些场景下还是需要定期调用模型比如长连接的心跳需要确认模型服务本身是否可用。这时候就要给心跳设预算。我的做法是给心跳单独设一个token配额比如每天不超过总配额的5%。一旦超过自动降级为协议层心跳不再调模型。同时加告警提醒团队检查是否有异常。另外心跳的间隔不要设得太短。很多系统设5秒、10秒其实完全没必要。用户感知不到30秒和5秒的区别但成本差6倍。我一般建议心跳间隔不低于30秒长连接场景可以放到60秒。如果业务对实时性要求极高那也应该用推送而不是轮询。4.5 第五步重试逻辑和心跳解耦心跳失败就失败了不要重试。心跳的意义是“探测”不是“保证送达”。如果心跳都需要重试来保证说明你的网络本身就有问题应该去修网络而不是让心跳反复烧token。重试逻辑只应该用在真正的用户请求上而且要有退避策略。第一次失败等1秒第二次等2秒第三次等4秒最多重试三次。重试时不要带完整上下文只带必要信息。我见过一个系统心跳重试了十次每次带完整对话历史一次网络抖动烧掉了正常情况下一周的token。这种设计必须改。5. 常见问题与排查技巧实录5.1 账单突然暴涨怎么快速定位是不是心跳先看请求量曲线和用户活跃曲线是否吻合。如果请求量在用户不活跃的时段依然很高大概率是心跳。然后看请求内容的分布统计一下有多少请求的prompt长度在某个固定值附近心跳请求的prompt长度通常很稳定因为内容模板固定。还可以看请求的时间间隔。心跳请求的时间间隔非常规律比如精确的每10秒一次。用户请求的间隔是随机的。用这个特征可以快速区分。5.2 心跳关了之后连接老断怎么办这说明你的连接保活没做好。检查TCP keepalive参数Linux默认是7200秒太长了可以调到300秒。如果是WebSocket检查ping/pong帧是否正常收发。如果是负载均衡器检查它的空闲超时设置通常需要比心跳间隔大。还有一个常见原因是NAT超时。很多网络设备会清理长时间没有流量的连接这时候需要靠协议层的心跳来维持。但协议层心跳不花钱放心用。5.3 怎么说服团队改掉心跳设计用数据说话。把心跳的token消耗单独拉出来换算成钱放在团队面前。我试过最有效的方式是做一次A/B对比一组保持原样一组关掉心跳跑一周看账单差异。数字摆出来没有人会跟钱过不去。如果团队担心关掉心跳会影响稳定性可以先灰度。先关掉10%的实例观察一周。通常会发现稳定性没有任何变化因为心跳本来就不解决稳定性问题它只是探测问题。5.4 有没有工具能自动检测心跳浪费目前没有现成的工具能直接告诉你“这是心跳浪费”。但可以用APM工具加自定义标签来实现。比如在请求头里加一个X-Request-Type然后在APM里按这个维度统计。或者用日志分析工具对prompt内容做聚类心跳请求的prompt通常高度相似。我自己写了一个简单的脚本定期扫描日志找出“高频、短内容、固定间隔”的请求基本就是心跳。这个脚本不到一百行但帮我省了不少钱。5.5 心跳间隔设多少合适这个问题没有标准答案取决于你的业务场景。我的经验值是如果用户主动操作频繁比如聊天应用心跳间隔可以放到60秒因为用户操作本身就会刷新连接。如果用户可能长时间挂机比如监控面板心跳间隔30秒比较稳妥。如果对实时性要求极高比如协同编辑那应该用WebSocket加协议层心跳而不是轮询模型。关键是要算一笔账心跳间隔从10秒改成30秒成本降三分之二用户体验有变化吗大多数情况下没有。那就改。6. 一个真实的优化案例拆解6.1 优化前的架构和账单某内部知识问答系统日活约300人平均每人每天提问5次。按道理每天模型调用应该是1500次左右。但实际后台显示每天调用量是4万次以上token消耗是预期的20多倍。排查发现系统用了WebSocket长连接每个连接上挂了一个心跳定时器每5秒向模型发一次“ping”。300个在线用户每人每天在线4小时心跳请求就是300乘以4乘以3600除以5等于86万次。但实际调用量是4万次说明同时在线人数没那么多但即使按4万次算也是正常用户请求的26倍。更糟糕的是心跳请求带了完整的系统提示词每次约800token。4万次乘以800每天光心跳就消耗3200万token。6.2 优化动作和效果优化分三步。第一步把心跳从模型调用改成WebSocket协议层的ping/pong帧token消耗直接归零。第二步把会话状态从模型侧迁到Redis用户请求时从Redis取历史不再依赖心跳维持。第三步给模型调用加标签持续监控是否有新的心跳类请求出现。改完之后模型调用量从每天4万次降到1800次左右基本等于真实用户请求量。token消耗降到原来的5%以下。整个优化过程花了不到两天其中大部分时间是在测试协议层心跳的稳定性。6.3 踩过的坑和教训第一个坑是协议层心跳的间隔设得太短导致客户端耗电增加。后来调到45秒平衡了稳定性和功耗。第二个坑是Redis会话过期时间设得太短用户聊到一半历史丢了。后来改成滑动过期每次用户请求就续期30分钟。第三个坑是忘了关掉旧的定时器改完代码后旧的心跳还在跑多花了一周的钱才发现。教训是改完一定要看监控确认旧逻辑真的停了。7. 把心跳成本纳入日常监控7.1 建立token消耗的分层监控不要只看总消耗要分层看。我的做法是分四层用户请求、心跳、重试、系统任务。每层单独统计请求量和token量设置不同的告警阈值。用户请求的阈值可以宽松一些心跳的阈值要严格一旦超过总消耗的5%就告警。监控面板上要能一眼看出各层的占比。我习惯用一个堆叠面积图用户请求是绿色心跳是红色重试是黄色系统任务是灰色。红色区域一旦变大立刻排查。7.2 定期做“心跳审计”每个月做一次心跳审计检查所有定时任务、健康检查、保活逻辑。重点看三件事有没有新的心跳被加进来、旧的心跳有没有被关掉、心跳的token消耗占比有没有变化。这个审计不需要很复杂一个检查清单就够了。我自己的清单包括所有setInterval和setTimeout的用途、所有/health和/ping接口的实现、所有WebSocket连接的保活方式、所有重试逻辑的触发条件。逐项确认逐项记录。7.3 把心跳成本写进代码规范最根本的解决办法是让团队形成共识心跳不调模型。把这条写进代码规范在代码审查时检查。新来的同事可能不知道这个坑但规范里写了审查时就会拦住。我还会在项目文档里放一个“成本陷阱”章节把心跳、上下文累积、重试放大这几个常见问题写进去。新项目启动时先读一遍能避免很多重复踩坑。8. 一些更隐蔽的“类心跳”消耗8.1 预热请求另一种形式的浪费有些系统为了“保持模型热乎”会定期发一些预热请求。理由是“避免冷启动”。但大模型接口通常没有冷启动的概念预热请求纯粹是浪费。我测过多个主流接口首次请求和后续请求的延迟差异在统计上不显著。预热带来的收益远小于成本。如果确实担心延迟应该用连接池和长连接来优化而不是发预热请求。连接池保持TCP连接活跃但不发数据成本为零。8.2 日志上报别把日志也发给模型有些系统会把日志内容发给模型做“智能分析”如果日志里包含心跳记录就形成了二次消耗。更隐蔽的是有些APM工具会自动采集请求内容如果采集频率很高也会产生额外调用。检查一下你的日志管道和监控代理确认它们不会触发模型调用。日志分析应该用规则引擎或者本地小模型不要用大模型接口。8.3 会话恢复用户回来时的一次性大消耗用户离开一段时间后回来有些系统会把离开期间的所有消息包括心跳打包发给模型做“上下文恢复”。这个操作本身可能合理但如果打包的内容里混入了大量心跳消息就会造成一次性大消耗。解决办法是在存储会话历史时就把心跳消息过滤掉只存真正的用户消息和模型回复。这样恢复时就不会带上垃圾。9. 我个人的几条实操心得第一条心跳的默认值应该是“不发”。任何需要发心跳的场景都要有明确的理由并且经过成本评估。不要因为“别人都发”就跟着发。第二条把token当成钱来管。每次设计一个新功能先算一下它会产生多少token消耗换算成钱。如果这个功能带来的价值小于它的token成本就不做。这个习惯能帮你避开很多隐形成本。第三条监控要能看到“谁在花钱”。不要只看总数要能下钻到具体的请求类型、具体的调用方。我见过太多团队账单涨了但不知道是谁花的只能干着急。第四条定期清理“僵尸心跳”。人员流动、系统重构、功能下线都会留下没人管的心跳。每个季度扫一遍该关的关该改的改。第五条别怕改架构。如果发现心跳设计有问题该重构就重构。我见过一个团队因为怕影响稳定性拖了半年没改多花的钱够招一个人了。实际上改完之后稳定性反而更好因为少了一层不必要的依赖。最后分享一个小技巧在模型调用的入口加一个简单的频率限制同一个会话每秒最多调用一次。这个限制能挡住大部分失控的心跳和重试。实现起来很简单一个Redis的计数器就行但效果立竿见影。我在多个项目里用过每次都能抓到几个意料之外的调用源。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑