分布式事务超时:失败返回不代表没有提交
一、超时是最容易误判的状态
分布式事务里,客户端收到超时,不代表事务一定失败。请求可能已经到达协调者,也可能部分参与者已经提交,只是响应丢了。把超时简单当失败重试,可能造成重复写、状态反转或外部副作用。
分布式系统里,超时通常表示未知,而不是失败。
二、区分结果状态
stateDiagram-v2 [*] --> pending pending --> committed pending --> aborted pending --> unknown unknown --> committed unknown --> aborted业务接口要能表达 unknown 状态,并提供查询最终结果的方式。只返回 success 或 failed,会逼调用方做错误假设。
transaction_result: success: committed failed: aborted timeout: unknown require_query_api: true超时后的第一步应该是查状态,而不是盲目重试。
三、幂等键必须前置
如果业务允许重试,就要在第一次请求时携带幂等键。协调者和参与者都要能识别同一个业务请求,避免重复执行。
CREATE UNIQUE INDEX uk_request_id ON payment_order(request_id);幂等不是只在接口层做判断,数据库约束也要兜底。否则并发重试下仍可能穿透。
四、补偿要基于事实
失败补偿不能凭客户端感受执行。比如扣款请求超时,如果直接发起退款,可能在原扣款尚未确认时引入更复杂状态。应先查询事务日志、参与者状态和外部系统回执。
timeout_handling: query_transaction_log: true query_participants: true retry_with_idempotency: true compensate_after_confirmed: true还要设置超时层级。客户端超时、网关超时、事务协调者超时、参与者超时应有明确关系。外层超时太短,会制造大量 unknown。
最后,监控要统计 unknown 比例。unknown 上升说明系统正在失去确定性,哪怕最终大多提交成功,也会让业务逻辑变复杂。
日志设计也要服务状态确认。每个阶段都要记录事务 ID、幂等键、参与者、状态版本和最后更新时间。超时后排查时,如果只能看到一条网关 504,就无法判断事务到底走到哪里。
transaction_log: transaction_id: required idempotency_key: required participant_state: required state_version: required updated_at: required对于外部副作用,例如发券、发消息、调用支付渠道,更要用 outbox 或可靠消息模式承接。数据库事务提交和外部调用之间没有天然原子性,超时会把这个缺口放大。
最后,文档要明确调用方策略。哪些错误可重试,哪些需要查询,哪些必须人工处理。分布式事务的稳定性,不只在服务端,也在调用契约里。
测试也要模拟响应丢失。只测服务端返回成功或失败,覆盖不到最危险的未知状态。可以在提交后丢弃响应、延迟回包、重复发送请求,确认幂等和查询接口真的有效。
timeout_test_cases: drop_response_after_commit: true retry_same_idempotency_key: true query_final_state: true五、总结
分布式事务超时要按 unknown 处理,通过幂等键、状态查询、事务日志和事实补偿降低风险。
失败返回不代表没有提交。先确认事实,再决定重试或补偿。