尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

副作用已发生但响应丢失呢?

副作用已发生但响应丢失呢? 第174题副作用已发生但响应丢失呢1. 核心回答这是典型的“结果不确定”问题。如果调用了有副作用的工具Agent → create_rule() → 服务端已经成功创建规则 → Response 在网络中丢失 → Agent 收到 timeout此时Timeout≠OperationFailed Timeout \neq OperationFailedTimeoutOperationFailed系统不能直接把状态写成FAILED也不能换一个新请求直接重试。我会进入UNKNOWN → RECONCILING然后按以下顺序恢复用原来的operation_id / idempotency_key查询下游如果确认副作用已发生补写本地COMMITTED如果确认没有发生再使用相同幂等键重试如果无法确认则停止自动重试进入人工接管或业务补偿。核心原则是先对账再决定是否重试。2. 为什么直接 Retry 很危险假设第一次create_firewall_rule(R1)已经成功。只是Response lost客户端看到TIMEOUT然后再次调用create_firewall_rule(R1)如果下游没有幂等机制可能产生Rule R1 Rule R1同样的问题会出现在创建工单发送邮件提交 PR禁用账号扣费隔离主机部署策略。因此Retry(SideEffect) Retry(SideEffect)Retry(SideEffect)只有在幂等和状态恢复设计明确以后才安全。3. 请求状态不能只有 SUCCESS / FAILED我会为副作用操作建立持久状态表。至少包含PLANNED STARTED COMMITTED FAILED UNKNOWN RECONCILING COMPENSATING COMPLETED例如PLANNED ↓ STARTED ↓ 外部系统已经commit ↓ Response丢失 ↓ UNKNOWN ↓ RECONCILING ↓ COMMITTED这里UNKNOWN很重要。它表示当前系统不知道操作有没有发生。而不是操作没有发生。4. 执行前就要生成 Idempotency Key不要等到第一次失败后才生成幂等键。在真正调用工具之前就生成task_id step_id logical_operation_id idempotency_key例如task_id T100 step_id S7 operation isolate_host:host123 idempotency_key T100:S7:isolate_host:host123并先持久化PLANNED然后才能执行外部调用。5. Retry 必须复用同一个 Key同一个逻辑操作attempt 1 attempt 2 attempt 3必须使用同一个idempotency_key也就是$$Key(attempt_1)Key(attempt_2)Key(attempt_3)$$如果每次 Retry 都重新生成 UUIDK1 K2 K3对于服务端来说就是三个不同业务请求。幂等机制实际上失效。6. 服务端应该怎样处理相同 Idempotency Key收到请求K123时检查状态表。如果不存在创建 operation 执行副作用 保存 result如果已经COMMITTED则返回之前的结果而不再次执行。理想语义是$$Apply(K)Apply(Apply(K))$$也就是相同逻辑请求执行多次业务结果与执行一次相同。7. 一个典型状态表可以记录字段含义operation_id业务操作IDidempotency_key幂等键task_id所属Agent任务tool工具arguments_hash参数摘要state当前状态external_ref下游对象IDattempt尝试次数created_at创建时间committed_at提交时间last_error最近错误尤其需要保存external_ref例如firewall_rule_id ticket_id payment_id pull_request_id便于后续对账。8. 响应丢失以后第一件事是 Reconciliation假设POST /firewall-rules超时。不要立即再次POST /firewall-rules优先查询GET /operations/{operation_id}或者GET /firewall-rules?request_idK123然后判断FOUND NOT_FOUND UNKNOWN9. 情况一确认副作用已经发生例如返回operation_id K123 status completed rule_id R888那么External: COMMITTED Local: UNKNOWN需要执行的是补写本地状态而不是再次创建规则。最终Local: COMMITTED External: COMMITTED达到一致。10. 情况二确认副作用没有发生如果外部系统能够明确证明K123不存在而且操作本身仍然满足业务前置条件则可以Retry但仍使用K123而不是生成新的 Key。11. 情况三外部系统也无法确认这是最困难的情况。例如邮件服务timeout 没有message status query API此时系统不知道邮件发了还是邮件没发如果重复发送的业务代价很高就不能自动重试。应该UNKNOWN → Human Review或者根据业务规则执行 Forward Recovery。12. 幂等能力应该成为 Tool Contract 的一部分每个 Tool 最好声明side_effect: true idempotent: true/false queryable: true/false compensatable: true/false reversible: true/false例如Tool幂等可查询可补偿read_file是是不需要create_ticket可实现是通常可关闭send_email较困难不一定很难真正撤销create_firewall_rule可实现是可删除charge_payment通常支持幂等键是可退款Agent Orchestrator 根据这些属性选择恢复方式。13. 幂等与补偿不是同一个概念Idempotency 解决同一个操作重复调用时怎么避免重复副作用Compensation 解决一个已经成功的副作用现在业务上需要撤销怎么办例如reserve inventory → charge → create shipment如果 shipment 失败可以refund release inventory这是 Saga Compensation。它与 Retry 是两个不同机制。14. Compensation 也不能理解成数据库 Rollback外部世界很多操作无法真正回滚。例如send_email()成功以后不能保证把邮件从收件人的邮箱删除。能做的可能只是send_correction_email()这属于补偿动作。所以应区分Reversible Compensatable Irreversible三类副作用。对不可逆、高风险操作应提高审批等级。15. 跨服务业务流程可以使用 Saga例如创建安全工单 → 隔离主机 → 禁用账号每一步都是独立服务中的本地事务。无法简单依赖一个数据库事务BEGIN ... COMMIT覆盖全部服务。可以建模成T1 create_ticket T2 isolate_host T3 disable_account并分别定义C1 close_ticket C2 release_host C3 restore_account如果中途失败根据风险采用Forward Recovery或Compensation恢复业务一致性。16. 本地写状态 发消息要防 Dual Write例如工具成功以后系统需要1. 更新数据库为COMMITTED 2. 发布ToolCompleted事件如果DB成功 Message失败会出现数据库认为完成 下游却永远不知道反过来也可能Message成功 DB失败这就是 Dual Write Problem。17. Transactional Outbox 解决什么一种常见方案是把业务状态更新 待发布事件写进同一个本地数据库事务。例如BEGIN UPDATE operation SET stateCOMMITTED INSERT INTO outbox (eventToolCompleted, operation_idK123) COMMIT之后独立 Publisher 再把 Outbox 中的消息发送出去。这样可以避免业务数据已经commit 但事件永久丢失的问题。18. Outbox 的 Consumer 仍然需要幂等Outbox 保证消息最终可以继续发送。但消息系统可能产生duplicate delivery所以 Consumer 仍需要根据event_id operation_id idempotency_key进行去重。因此完整关系是OutboxIdempotentConsumer Outbox IdempotentConsumerOutboxIdempotentConsumer而不只是 Outbox。19. Retry 仍然要有上限即使操作已经幂等也不能无限重试。暂时故障可以Retry Exponential Backoff Jitter同时设置max_attempts deadline retry_budget因为大量客户端同时重试仍然可能把下游服务压垮。20. 所有 5xx 也不能无脑 Retry对有副作用操作来说500有时仍然是副作用已经部分或全部发生 服务端最后返回500所以遇到timeout connection reset ambiguous 5xx都应该首先考虑OutcomeUnknown OutcomeUnknownOutcomeUnknown而不是直接OutcomeFailed OutcomeFailedOutcomeFailedStripe 的错误处理文档也明确把部分服务器错误后的结果视为 indeterminate并强调保留原 Idempotency Key。21. Exactly-once 要谨慎表述跨网络和跨系统条件下简单宣称Exactly Once通常过强。更可实现的目标是At-least-once delivery Idempotent processing Durable state Reconciliation使MultipleAttempts→OneLogicalEffect MultipleAttempts \rightarrow OneLogicalEffectMultipleAttempts→OneLogicalEffect也就是从业务观察上避免重复副作用。22. Agent 状态恢复应该怎样做Checkpoint 中应保存task_version completed_steps pending_steps operation_id idempotency_key external_ref side_effect_statusWorker 崩溃恢复后读取Checkpoint ↓ 发现stepS7, stateUNKNOWN ↓ 查询外部系统 ↓ 确认R888已经存在 ↓ 本地补写COMMITTED ↓ 继续S8不能简单从S7重新运行23. 什么情况下必须人工接管我会在以下情况停止自动恢复下游无幂等支持下游无法查询真实执行状态副作用不可逆补偿动作本身风险很高多个系统状态互相矛盾自动重试超过预算关键业务状态无法确定。此时输出MANUAL_RECONCILIATION_REQUIRED并提供operation_id attempt history last known state external references recommended checks24. 怎样验证机制真的有效需要主动注入服务端commit后丢Response 客户端timeout 重复callback Worker crash 网络断连 消息重复 Outbox发布失败 补偿失败最关键的故障点是External Side Effect COMMITTED ↓ Response Lost / Process Crash ↓ Local State Not COMMITTED然后检查恢复以后LogicalEffectCount1 LogicalEffectCount1LogicalEffectCount125. 关键指标至少记录Duplicate Side-effect Rate Unknown Outcome Rate Reconciliation Success Rate Manual Reconciliation Rate Retry Count Retry Budget Exhaustion Compensation Success Rate Recovery Latency其中最关键的验收条件之一是DuplicateSideEffectRate≈0 DuplicateSideEffectRate \approx0DuplicateSideEffectRate≈0具体目标值必须依据真实业务 SLO 定义。26. 什么结果说明方案失败例如create_rule第一次已成功 → Response丢失 → Agent重新创建 → 最终存在两条规则说明 Idempotency 失败。又例如timeout → 系统直接标FAILED但稍后发现外部系统已经执行成功。说明状态模型缺少UNKNOWN / RECONCILING再例如外部操作成功 → 本地commit失败 → Worker恢复后重新执行说明 Checkpoint 和对账机制失效。27. 当前题目的最准确结论如果副作用已经发生但响应丢失我不会直接 Retry。首先将调用状态标记为UNKNOWN然后使用原来的operation_id idempotency_key向下游查询和对账。如果确认已经成功就补写本地COMMITTED如果确认没有执行才使用同一个 Idempotency Key 重试如果无法确认就停止自动重试进入补偿、前向修复或人工接管。跨系统流程再通过Transactional Outbox Saga Compensation Checkpoint Event Log维护可恢复一致性。因此核心不是失败了怎么重试而是在不知道第一次执行结果时怎样防止第二次执行产生重复副作用。28. 面试时可以压缩成下面这段副作用工具发生超时后我首先不会把它当成失败因为服务端可能已经 commit只是 Response 丢了。这种情况应该进入UNKNOWN状态。所以有副作用调用在执行前就要生成并持久化 operation ID 和 idempotency key。发生 timeout 后优先拿这个 ID 去下游查询。如果确认已经成功就补写本地 committed如果确认没执行再使用同一个 idempotency key 重试如果无法确认就停止自动重试并进入人工对账或补偿。同一个逻辑操作的所有 Retry 必须复用同一个 key。否则每次生成新 UUID服务端会认为是不同操作幂等机制没有意义。对于本地数据库更新和事件发送这种 dual write我会用 transactional outbox跨多个服务的长业务事务则用 saga 做 forward recovery 或 compensation。但补偿不等于物理 rollback比如邮件已经发出通常无法真正撤回。验收时我会专门注入“服务端成功后丢响应”“外部成功后 Worker 崩溃”“重复回调”和“消息重复”等故障最终要求同一个 logical operation 只产生一次业务副作用。29. 来源原始 相关 题库第174题要求先按可重试性与副作用分类副作用操作携带幂等键并记录 planned/started/committed响应丢失先查询结果跨系统使用 outbox/saga 与补偿无法自动恢复则人工接管。AWS Well-Architected Framework,REL04-BP04 Make mutating operations idempotent通过 Idempotency Token 识别重复写请求使客户端在未收到响应时能够安全重试并要求持久化操作状态。AWS Well-Architected Framework,Implement idempotent task execution patternsAgent/工作流中的 Retry 若没有幂等机制会产生重复副作用幂等键应在多步骤工作流中稳定传播。Stripe API,Idempotent Requests / Advanced Error Handling连接错误后可使用相同 Idempotency Key 重试对于执行结果不确定的服务器错误应避免换新 Key 导致重复副作用。AWS Prescriptive Guidance,Transactional Outbox Pattern用于解决数据库更新与消息发布之间的 Dual Write 不一致。AWS Prescriptive Guidance,Saga Patterns跨多个服务的业务流程通过本地事务、前向恢复和补偿事务维护一致性。
返回列表