先记住这个答案
设计幂等键时,核心是区分"重试同一逻辑任务"与"用户再次发起同一诉求"。键应由任务类型、业务主键(如订单号)、输入参数规范哈希构成逻辑身份;每次实际执行附加唯一执行ID,重试复用同一执行ID,人工重跑生成新执行ID但保留关联的原始逻辑键,从而允许存储层按逻辑键去重或覆盖。
- 幂等键必须区分逻辑任务与物理执行
- 重试复用执行ID,人工重跑新执行ID
- 状态存储是幂等判断的关键条件
双层键:逻辑唯一性与执行唯一性
Agent任务往往是多步、多工具调用,失败可能发生在任意一步。重试时若不区分执行实例,可能重复扣款或重复发消息。键结构应为:逻辑键 = task_type + business_key + input_hash,执行键 = logical_key + execution_id。逻辑键用于幂等存储的查找,执行键用于标识一次具体尝试。
自动重试必须复用同一执行ID,因为它是同一次逻辑尝试的延续,状态机已记录已完成的步骤。人工重跑则生成新的执行ID,但保留相同的逻辑键,允许系统识别"同诉求的新尝试",可选择覆盖旧状态或提示冲突。参数哈希需要规范化顺序,JSON字段排序后哈希,避免键不稳定。
支付场景:自动重试与客服手动重放
输入:一次"转账"任务,业务键为user:123,order:456,参数为金额和账号。第一次执行中,Agent调用扣款工具成功了,但响应超时,系统判定失败并自动重试。若重试用新执行ID,会再次扣款;正确做法是复用同一执行ID,状态存储中已有"扣款已完成"的记录,直接从该步骤后继续。
如果用户联系客服要求重新执行,因为之前任务整体失败(比如后续步骤无法恢复),客服触发重跑。此时应生成新执行ID,并关联原逻辑键。存储层可根据逻辑键查询历史执行,人工确认后选择新执行覆盖旧状态,确保业务对象最终正确。
幂等键设计失效的边界
一是键在生成后就不可变,如果步骤中参数因外部变化而调整,逻辑键会变,导致无法关联旧执行。解决:将可变参数放在独立字段,只用稳定业务标识做逻辑键,变参记录在执行上下文中。二是人工重跑时若不区分"想强制重来"和"仅检查",可能误覆盖有效结果,需要增加人工确认标记。
三是分布式环境下,状态存储的写入必须原子,否则两个执行并发时可能都通过检查。代价是需要为每个Agent维护一个分布式锁或数据库事务。四是长时间任务中执行ID可能被重用,需保证ID全局唯一,通常由UUID生成。
容易答错的地方
- 幂等键只用业务主键
- 如果同用户多次手动发起相同诉求,就会被误判为重试,导致后面的任务被拒绝。业务主键只能唯一标识业务对象,不能唯一标识用户意图。需加入请求来源或会话ID。
- 重试生成新键
- 把重试当成全新任务,新执行ID导致已完成的副作用无法被识别,重复扣款或重复写数据。重试必须复用执行ID,只有人工明确重跑才新生成。
面试官还会怎么问?
如何防止状态存储与工具调用之间的并发写导致重复副作用?
在写状态前获取分布式锁,且工具调用的执行必须与状态更新在同一事务中,或采用两阶段提交。否则两个执行并发时都会通过检查而重复调用。
人工重跑时,旧执行留下的部分副作用如何处理?
优先通过业务键定位并补偿(如退款),但有些副作用不可逆(如发邮件)。此时应在逻辑键下记录全部已完成动作,供人工决定。
如果输入参数中含时间戳,每次都变,如何生成稳定的逻辑键?
从参数中提取业务核心字段(如订单号、用户ID),时间戳作为执行上下文而非键的一部分。否则逻辑键每次不同,无法识别同诉求。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。