资讯动态

如何保证有副作用工具调用幂等?

发布时间:2026/9/30 13:06:27 来源:尧图企业网站定制
第175题如何保证有副作用工具调用幂等1. 核心回答对于付款、发邮件、建工单、修改配置、提交代码这类有副作用的 Tool Call我会把幂等设计成一套完整协议稳定 Idempotency Key↓请求参数指纹 Request Hash↓幂等状态表↓原子抢占执行权↓执行外部副作用↓保存 External Object ID / Result↓COMMITTED如果调用结果不确定UNKNOWN↓Reconcile External State↓COMMITTED / SAFE_RETRY / NEEDS_REVIEW核心目标是Effect(Op,Op,…,Op)Effect(Op) Effect(Op,Op,\ldots,Op)Effect(Op)Effect(Op,Op,…,Op)Effect(Op)也就是同一个业务操作即使因为RetryTimeoutWorker 重启消息重复投递Client 重复请求被执行路径触发多次也不能产生额外业务副作用。工程上不能仅依靠数据库 Unique Index。完整方案至少需要Idempotency KeyRequest HashIdempotency State Table本地事务响应丢失后的 ReconciliationTransactional Outbox / Idempotent Consumer跨系统 Saga / CompensationCheckpoint 与恢复并发、崩溃和重复投递故障注入。2. 先定义“幂等”如果操作函数是fff幂等要求f(f(x))f(x) f(f(x))f(x)f(f(x))f(x)对于有副作用的业务调用更实际的含义是对同一个逻辑业务操作进行重复请求最终业务状态与成功执行一次相同。例如create_ticket(order-123)因为超时连续调用三次最终应该只有1 个工单而不会产生三个工单。3. 幂等不等于“请求绝不会重复”分布式系统中可能发生Client → Server → Server 完成操作 → Response 丢失客户端只看到timeout它无法根据 Timeout 判断服务端没有执行还是服务端已经成功执行只是响应丢失因此系统必须允许重复请求同时控制重复副作用。更可靠的模型是AtLeastOnceAttemptIdempotentExecution→EffectivelyOnceEffect AtLeastOnceAttempt IdempotentExecution \rightarrow EffectivelyOnceEffectAtLeastOnceAttemptIdempotentExecution→EffectivelyOnceEffect不要轻易声称拥有无条件的端到端 Exactly-Once。4. 第一层稳定的 Idempotency Key每个逻辑业务操作生成KIdempotencyKey KIdempotencyKeyKIdempotencyKey例如tenant-A:create-ticket:order-123或者使用随机 UUID550e8400-e29b-41d4-a716-446655440000最关键的规则是同一次业务操作的所有 Retry 必须继续使用同一个 Key。错误设计attempt 1 → UUID-A attempt 2 → UUID-B attempt 3 → UUID-C服务端看来这是三个完全不同的请求因此无法去重。5. Key 应绑定业务作用域仅有idempotency_key有时还不够。推荐作用域包括Scope(Tenant,Tool,Operation,IdempotencyKey) Scope ( Tenant, Tool, Operation, IdempotencyKey )Scope(Tenant,Tool,Operation,IdempotencyKey)例如tenant_123 payment.create charge K123这样可以防止不同租户Tool操作类型错误共享一个 Key。6. 同一个 Key 必须绑定同一组参数需要计算规范化请求指纹HHash(CanonicalRequest) H Hash( CanonicalRequest )HHash(CanonicalRequest)例如idempotency_key K123 request_hash SHA256( tool tenant resource normalized_args )幂等表第一次看到K123 HashA以后再次收到K123 HashA可以视为同一次业务重试。如果收到K123 HashB应该拒绝IDEMPOTENCY_PARAMETER_MISMATCH否则同一个 Key 可能先表示支付100元后面又变成支付1000元语义已经不再相同。AWS EC2 的 Client Token 和 Stripe 的 Idempotency Key 都采用类似的参数一致性约束。7. 第二层建立持久 Idempotency State Table例如idempotency_key request_hash operation_type tenant_id state external_object_id result error created_at updated_at expires_at状态不能只有exists / not exists更合理的是PLANNED STARTED COMMITTED FAILED_RETRYABLE FAILED_FINAL UNKNOWN COMPENSATING COMPENSATED NEEDS_REVIEW8. 各状态代表什么8.1 PLANNED业务意图已经持久化但副作用尚未执行。8.2 STARTED已经取得执行权外部操作可能正在进行。8.3 COMMITTED外部副作用已经确认成功。保存external_object_id result后续相同请求直接返回历史结果。8.4 UNKNOWN关键状态。表示当前无法确定外部副作用有没有发生。典型原因Timeout网络断开Worker CrashAPI 响应丢失。此时禁止盲目重新执行。8.5 FAILED_RETRYABLE确认操作没有成功且失败属于可安全重试类别。8.6 FAILED_FINAL参数、权限、业务规则等确定性错误。一般无需重复请求。9. 第三层并发请求必须原子抢执行权假设两个 Worker 同时收到K123流程不能是Worker A: query → no record Worker B: query → no record Worker A: execute Worker B: execute否则经典 Check-Then-Act Race 会产生双执行。需要使用原子 InsertUnique ConstraintCompare-And-SetRow Lock数据库事务使同一 Key 在某一时刻只有一个执行者取得执行资格。10. Unique Index 有价值但作用有限例如UNIQUE(tenant_id,operation_type,idempotency_key)可以很好地阻止两个 Worker 同时创建两条 Idempotency Record但它无法单独解决外部 API 已执行 ↓ 本地 COMMITTED 尚未写入 ↓ 进程崩溃因为真正副作用已经位于本地数据库事务之外。所以源文件特别指出易错点是只靠唯一索引。11. 本地副作用可以与幂等记录放在同一事务如果业务状态和 Idempotency Table 位于同一个数据库可以BEGIN INSERT idempotency_record UPDATE business_state UPDATE idempotency_record SET state COMMITTED COMMIT如果中间失败ROLLBACK业务状态和幂等状态一起恢复。这属于最容易处理的场景。12. 外部 Tool Call 无法加入本地数据库事务更困难的是Local DB External API例如本地 Agent State 第三方 Payment API通常无法进行BEGIN distributed transaction ... COMMIT所以必须显式处理失败窗口。13. 最危险窗口外部成功本地尚未提交例如1. state STARTED 2. 调 payment API 3. Payment 成功 4. 服务进程崩溃 5. 尚未来得及写 COMMITTED恢复后数据库只看到STARTED如果直接再次支付double charge因此这类状态应该进入UNKNOWN或通过恢复逻辑把长期STARTED视为待对账状态。14. UNKNOWN 状态必须先 Reconciliation恢复流程UNKNOWN ↓ Query External System例如使用Idempotency KeyExternal Operation IDBusiness KeyOrder IDTransaction ID。查询这个业务操作到底有没有成功如果确认已经存在UNKNOWN → COMMITTED如果确认从未执行UNKNOWN → SAFE_RETRY如果外部系统无法查询UNKNOWN → NEEDS_REVIEW15. 优先使用外部 API 原生幂等机制如果 Tool Provider 支持 Idempotency Key应把当前业务 Key继续传给它。例如Agent operation K123 ↓ External API Idempotency-Key: K123这样客户端重试K123 K123 K123外部服务本身也能够识别为同一业务操作。AWS EC2 的 Client Token 和 Stripe 的 Idempotency Key 都属于这种设计。16. 外部 API 不支持幂等怎么办可以退化为Business Unique Key Query Before Retry External Object ID Reconciliation例如创建工单时传external_reference order-123再查询ticket where external_reference order-123但需要注意query ↓ not found ↓ create本身存在 Race Condition。因此目标系统如果能够提供Unique Business KeyConditional CreateCreate-if-AbsentNative Idempotency Key可靠性通常更高。17. Key 的生命周期必须覆盖 Retry Horizon如果幂等记录只保存5分钟但消息系统可能24小时后重投那么旧 Key 已被删除重复消息就可能再次产生副作用。因此需要满足IdempotencyRetention≥MaximumReplayWindow IdempotencyRetention \ge MaximumReplayWindowIdempotencyRetention≥MaximumReplayWindow并结合Queue RetentionRetry PolicyCheckpoint Recovery Window业务合规要求设计 TTL。18. Transactional Outbox 解决什么经典问题BEGIN UPDATE business_table COMMIT publish event如果DB commit 成功但publish 失败下游系统永远不知道状态已经改变。Transactional Outbox 改成BEGIN UPDATE business_table INSERT outbox_event COMMIT两条本地写入在同一个事务。随后独立 RelayOutbox → Message Broker这样消除了业务状态已提交但事件完全丢失这一双写窗口。19. Outbox 仍然可能产生重复消息Relay 可能发生publish 成功 ↓ 尚未标记已发送 ↓ 进程崩溃 ↓ 重新 publish所以TransactionalOutbox TransactionalOutboxTransactionalOutbox并没有自动消灭重复消息。消费者仍需要Idempotent Consumer / Inbox。AWS 的 Transactional Outbox 指南也明确要求消费者能够处理重复事件。20. 消费端也需要幂等消费端可以保存consumer_id message_id processed_at result收到message-123先进行原子 Claim。如果message-123 already processed直接跳过。形成AtLeastOnceDeliveryIdempotentConsumer AtLeastOnceDelivery IdempotentConsumerAtLeastOnceDeliveryIdempotentConsumer从业务效果上接近一次处理。21. Agent Checkpoint 也必须保存幂等信息源文件要求 Checkpoint 保存version completed_steps idempotency_keys artifact_refs external_refs例如step_1 committed step_2 unknown step_3 pendingAgent 重启以后不能从 Step 1 全部重做而应该Reconcile Step 2 ↓ Replay only uncommitted steps22. Worker Lease 可以减少双执行长任务中可能出现Worker A 卡顿 ↓ 系统认为 A 死亡 ↓ Worker B 接管 ↓ A 又恢复此时 A、B 可能同时执行。可以通过LeaseHeartbeatTask Version控制执行权。但 Lease 本身也不能替代 Idempotency因为网络分区和延迟仍可能形成短暂并发。最终外部副作用仍需幂等保护。23. 错误重试需要分类不能把所有异常都自动 Retry。23.1 适合有限重试例如429Timeout部分瞬时 5xx临时网络异常。使用Exponential Backoff Jitter Retry Budget23.2 通常不应自动重试例如参数错误权限拒绝业务规则拒绝Schema Validation Failure。如果输入本身不会变化重复请求只会重复失败。24. Timeout 也不能直接等于 Retryable Failure需要区分confirmed failure和unknown outcomeTimeout 更接近UNKNOWN如果调用具有副作用恢复时优先Reconcile而非立即Retry这是保证幂等的关键状态语义。25. 跨系统事务使用 Saga如果一个 Agent 操作涉及创建工单 → 修改配置 → 发送通知 → 更新资产状态这些操作跨多个独立系统通常无法使用单个数据库 ACID Transaction。可以建模为 SagaT1→T2→T3→T4 T_1 \rightarrow T_2 \rightarrow T_3 \rightarrow T_4T1​→T2​→T3​→T4​每一步是独立 Local Transaction。如果后续失败则执行对应Ci C_iCi​补偿动作。26. Compensation 不等同于数据库 Rollback例如T1 订机票 C1 取消机票取消以后可能有手续费其他并发状态可能已经变化原操作可能已经产生外部通知。所以Compensation(T) Compensation(T)Compensation(T)通常无法完全恢复历史世界状态。它只是业务定义的反向动作。因此补偿过程本身也可能失败需要持久状态需要 Retry应尽量幂等可能需要人工处理。27. 不可逆操作尤其要靠执行顺序控制如果某一步无法补偿例如已发送真实邮件已对外披露秘密已执行不可逆物理操作它不适合过早出现在 Saga 中。可以把高不可逆步骤放到更靠后的位置并在之前完成ValidationApprovalReservationDry Run。降低补偿困难。28. 一个推荐的端到端状态机REQUESTED ↓ PLANNED ↓ STARTED ↓ ┌───────────────┐ │ │ ↓ ↓ COMMITTED UNKNOWN ↓ RECONCILING ┌────┼─────────┐ ↓ ↓ ↓ COMMITTED RETRY NEEDS_REVIEW ↓ STARTED跨系统失败时还可以进入COMPENSATING ↓ COMPENSATED29. 推荐的 Idempotency RecordIdempotencyRecord { tenant_id idempotency_key operation_type request_hash state task_id step_id external_object_id external_request_id result_ref error_class started_at committed_at updated_at expires_at }其中tenant_id operation_type idempotency_key可以形成唯一约束。30. 同一请求再次到达时如何处理伪逻辑可以写成load record by key if not exists: atomically create PLANNED if request_hash mismatch: reject if state COMMITTED: return cached result if state STARTED or state UNKNOWN: reconcile external state if state FAILED_FINAL: return original failure if state FAILED_RETRYABLE: retry under policy if state NEEDS_REVIEW: stop automatic execution核心是Retry 由持久状态机决定不能由 LLM 临时猜测。31. 哪些测试才能证明幂等真的成立不能仅测试连续点击两次按钮至少覆盖31.1 同步重复请求同时发送N NN个相同 Key。最终只允许一个真实副作用。31.2 Response Lost外部提交成功后故意丢弃响应。恢复后不能重复执行。31.3 Crash Before Commit外部成功、本地 COMMITTED 前 Crash。31.4 Crash After Commit本地状态完成后客户端没有收到响应。31.5 Duplicate Message Delivery同一个 Event 重复投递。31.6 Worker Double Execution模拟 Lease 过期、旧 Worker 恢复。31.7 Different Payload Same Key必须明确拒绝。31.8 Same Payload Different Retry应复用首次操作结果。31.9 Compensation Failure补偿中途失败后仍能继续恢复。32. 应报告哪些指标32.1 Duplicate Side-Effect RateDSRDuplicateBusinessEffectsLogicalOperations DSR \frac{ DuplicateBusinessEffects }{ LogicalOperations }DSRLogicalOperationsDuplicateBusinessEffects​关键目标应接近0 0032.2 Reconciliation Success RateRSRSuccessfullyResolvedUnknownUnknownOperations RSR \frac{ SuccessfullyResolvedUnknown }{ UnknownOperations }RSRUnknownOperationsSuccessfullyResolvedUnknown​32.3 Idempotency Conflict Rate统计 Same-Key/Different-Payload 的冲突。32.4 Manual Recovery Rate多少不确定状态最终需要人工介入。32.5 Recovery Time从 UNKNOWN 到最终一致状态的P50P95P99。32.6 Compensation Success Rate跨系统失败后补偿是否真正完成。33. 一个需要避免的强结论如果系统仅实现UNIQUE(idempotency_key)我不会声称系统已经保证有副作用工具 Exactly-Once。更可靠的表述是系统通过稳定幂等键、参数绑定、持久状态、外部对账和补偿使重复调用在定义的业务作用域和保留窗口内尽量产生单一业务效果。具体保证强度还取决于下游系统是否提供幂等接口唯一业务键可查询执行状态补偿接口。34. 什么结果会推翻“幂等已经保证”的主张34.1 并发同 Key 能产生两个外部对象说明执行权控制失效。34.2 同 Key 不同参数能够继续执行说明业务语义绑定不足。34.3 Response Lost 后发生重复扣款或重复建单说明 UNKNOWN / Reconciliation 路径失效。34.4 Outbox 重复消息导致下游重复副作用说明 Consumer 缺少幂等。34.5 Agent 恢复后重复执行已完成步骤说明 Checkpoint / Replay 设计错误。34.6 Compensation 失败后任务被错误标记成功说明 Saga 状态管理不完整。出现任何一种都不能继续声称端到端幂等已经成立。35. 当前资料能够支持到什么程度根据原始题目表格第175题明确支持有副作用操作必须使用 Idempotency Key需要持久状态表状态至少区分planned / started / committedCheckpoint 保存已完成步骤和 Idempotency KeyWorker 使用 Lease / Heartbeat 防止重复执行恢复前先与外部系统 Reconciliation只重放尚未提交步骤Response Lost 后先查询结果再决定跨系统使用 Outbox / Saga需要补偿机制无法自动恢复时进入人工接管验收必须覆盖重复执行、依赖故障、恢复与回滚易错点是只依赖 Unique Index。源文件没有提供实际 Idempotency Key 格式状态表 SchemaKey Retention实际外部 Tool 能力Duplicate Side-Effect RateResponse-Lost 故障注入结果Saga 补偿成功率实际恢复时间。因此这些结果必须通过真实实现和故障实验补证。36. 面试时可以压缩成下面这段有副作用 Tool Call 的幂等我不会只靠 Unique Index。核心是给一次逻辑业务操作分配稳定的 Idempotency Key所有 Retry 复用同一个 Key同时保存 Request Hash保证同 Key 不能绑定不同参数。服务端维护持久状态机例如PLANNED → STARTED → COMMITTED。同一 Key 再次到达时如果已经 COMMITTED就直接返回之前保存的结果如果处于 STARTED 或 UNKNOWN就先根据 External Object ID、业务唯一键或者原 Idempotency Key 去外部系统对账确认真实状态以后再决定是否可以重试。Timeout 本身不能直接判定成失败因为最危险的情况是外部操作已经成功、响应却丢失。本地数据库更新和事件发布之间的双写问题我会用 Transactional Outbox把业务状态和 Outbox Event 放在一个本地事务中消息可能重复投递因此消费者仍要按 Message ID 做幂等消费。跨多个独立系统时通常没有一个全局 ACID 事务所以用 Saga 和 Compensating Transaction。补偿本身也是业务动作可能失败因此也要持久状态、幂等设计和人工接管。最后我会做并发同 Key、Response Lost、Crash Before Commit、Duplicate Delivery、Worker Double Execution 和 Compensation Failure 等故障注入。只有这些情况下 Duplicate Side-Effect Rate 仍接近零我才认为幂等协议真正通过验收。37. 来源原始题目表格第175题明确要求 Idempotency Key、状态表、事务/Outbox、补偿、Checkpoint、外部状态对账和恢复重放并指出“只靠唯一索引”属于易错点。Amazon EC2 Documentation,Ensuring Idempotency in Amazon EC2 API RequestsClient Token 用于保证相同参数的重复请求不产生新的操作并检测同 Token 的参数不一致。Stripe API Documentation,Idempotent Requests通过 Idempotency Key 支持安全重试并绑定同一请求参数和首次执行结果。AWS Prescriptive Guidance,Transactional Outbox Pattern用同一数据库事务记录业务变更和 Outbox Event解决数据库更新与消息发布的 Dual-Write 问题同时强调消费端仍需处理重复消息。Microsoft Azure Architecture Center,Compensating Transaction Pattern跨系统最终一致操作需要记录补偿动作补偿可能失败并且自身也应设计为幂等操作。Microsoft Azure Architecture Center,Saga Distributed Transactions Pattern将跨服务业务事务拆成多个 Local Transaction并通过补偿处理后续步骤失败。

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

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

免费获取报价 →
↑