让 AI 智能体去下单这事,听着很酷,真上线第一个把我吓出冷汗的就是——它给同一个用户连下了三单。

事故现场

场景是个会员续费的智能体:用户说"帮我续个会员",智能体确认信息后调下单接口。问题出在哪?

智能体调下单接口,网络抖了一下,接口其实已经下成功了,但响应超时没回来。智能体一看"没收到成功响应,那我重试一下吧"——又下了一单。更糟的是智能体的重试逻辑还带退避,它退避完又试了第三次。

用户一觉醒来发现被扣了三次会员费,截图发群里,那叫一个热闹。

根因就一句话:调用方(智能体)会重试,但下单接口不幂等。 这俩凑一起就是重复下单。

幂等的核心:同一个操作做多少次,结果都一样

防重复下单不是靠"祈祷别重试",重试在分布式里几乎不可避免。正确的姿势是让下单接口天然支持安全重试——同一笔单子提交 N 次,只生效一次。

实现起来就一个关键词:幂等键(idempotency key)

做法

1. 每笔业务请求带一个幂等键

这个键要能唯一标识"这一次业务意图"。不能用智能体随手生成的 UUID(它每次重试都生成新的就废了),得用业务维度能稳定推出来的键。

我用的是 用户ID + 商品ID + 时间窗口 哈希出一个键。比如同一个用户、同一个会员商品、5分钟内,算同一笔意图。这样智能体重试时带的是同一个键。

key = hash(user_123 + vip_monthly + window_5min)

2. 接口侧用这个键去重

下单接口拿到请求,先拿幂等键去 Redis SETNX(不存在才写入,带过期时间):

  • 写入成功 → 第一次,正常下单,把订单结果也缓存到这个键上

  • 写入失败(键已存在)→ 重复请求,直接返回上次缓存的订单结果,不再下单

这样智能体重试一百次,也只有第一次真正创建订单,后面全是把第一次的结果原样返回。它甚至感知不到自己重复了。

3. 数据库再兜一层唯一索引

Redis 可能丢键(重启、过期边界)。所以订单表上对幂等键再建一个唯一索引,真有漏网的重复插入,数据库这层用唯一约束硬挡,插入冲突就当重复处理。两层防护,别只信缓存。

在零代码平台上怎么落

我这个智能体是在一个拖拽配流程的平台上搭的,下单那步是调一个 HTTP 工具节点。平台本身不帮你做幂等——它就负责把请求发出去。所以幂等逻辑必须落在你自己的下单接口那一侧,平台节点只管把幂等键当参数传过去。

具体就是在智能体的编排里,下单节点之前加一个"生成幂等键"的步骤(用前面说的业务维度哈希),把键塞进调接口的 header 里。平台这边能做的就到这儿,真正的去重得后端接口接住。

这里有个我当时没想到的坑:智能体的"确认信息"环节如果让用户改了商品(从月卡改年卡),幂等键得跟着变,不然用户想改单子结果被你当重复给挡了。所以幂等键的业务维度要把"可能变的关键字段"都算进去。

一个没做到极致的地方

老实说我的时间窗口设成5分钟是拍的。窗口太短防不住慢重试,太长又会误伤"用户真的想5分钟内买两次"的合理场景(虽然续会员这种很少见)。我目前是宁可误挡也不漏放——会员场景下用户极少短时间真的要买两次,挡错了客服退一下就行,但重复扣费的投诉成本高得多。换成别的高频交易场景,这个窗口策略可能就得重新设计了。

小结

智能体会重试是常态,别指望它不重试。防重复下单靠幂等键:业务维度生成稳定键 → Redis SETNX 去重 → 数据库唯一索引兜底,三件套。幂等逻辑放后端接口,零代码平台只负责传键。

(这智能体背后的模型走的讯飞星辰 MaaS,现成 API,我精力全花在幂等这种业务逻辑上,没去操心算力。)

Logo

欢迎加入 MCP 技术社区!与志同道合者携手前行,一同解锁 MCP 技术的无限可能!

更多推荐