Agent 跑了一半卡住,我才知道“超时”不是加个定时任务
有一次线上任务卡在“处理中”,用户等了很久,页面一直转圈。我第一反应是加个定时任务:超过一定时间,把状态改成失败,不就行了?
后来发现,这个想法只对了三分之一。
卡住的不只是模型
任务链路比我想的长:接收请求、创建任务、调用模型、调工具、写结果、更新状态。任何一段都可能慢:模型排队、工具超时、网络抖动、下游服务限流。
最开始我只给模型调用加了超时,以为够了。结果工具调用卡住,任务还是挂着;工具超时了,但任务状态没变;任务状态变了,额度却没退。
“超时”不是一个点,是一条链。
我后来把状态机画了出来
任务不是“成功/失败”两个状态,而是一串:
- 已提交
- 排队中
- 执行中
- 等待人工确认
- 成功
- 失败
- 已取消
- 超时失败
每个状态都要回答三个问题:谁能进来、什么条件能出去、出去之后要不要补偿。
比如“执行中”超时,不能直接改“失败”就完事。要先确认这个任务是不是真的没人跑了;如果另一个 worker 还在跑,你把它标失败,它后面又把结果写回来,状态就乱了。
定时任务只是最后一道保险
真正有用的,是三层:
- 调用层超时:模型、工具、HTTP 请求都要有超时,不能让一个调用无限等;
- 任务层心跳:执行中的任务定期续约,长时间没心跳才认为是死任务;
- 兜底扫描:定时扫描超时任务,用条件更新抢占状态,抢到了才做补偿。
第三层不能少,但它不是第一层。只靠定时任务,等于等尸体凉了再报警。
补偿最怕“重复”
任务失败要退额度、退次数、释放资源。第一版没做幂等,结果定时任务跑两次,退了两次。
后来改成:
- 状态更新用条件更新,只有从“执行中”改成“失败”成功的那一次,才触发补偿;
- 补偿按业务单号查原记录,不重新发一份;
- 补偿失败进重试队列,不吞掉;
- 所有操作带
requestId,重复请求直接返回。
这套东西听起来像“分布式事务”,其实就是。Agent 任务一旦跨服务、跨进程,它就是一个分布式系统,跑不掉。
用户看到的是什么
技术上的状态再多,用户只看到三件事:还在跑、失败了、需要我做什么。
所以我在任务详情里加了:
- 当前阶段;
- 最后更新时间;
- 失败原因(能说人话就说人话);
- 能不能重试;
- 需不需要人工确认。
这比在日志里写一堆堆栈有用。
最后一句
Agent 的可靠性,最后都会落到几个很“后端”的问题上:状态、超时、重试、幂等、补偿。
模型决定它能不能做,这些决定它能不能稳定地做完。
本文是原创文章,采用 CC BY-NC-ND 4.0 协议,完整转载请注明来自 王德明
评论
匿名评论
隐私政策
你无需删除空行,直接评论以获取最佳展示效果