dtm saga执行器避免卡在waiting状态的关键是确保steps[i].action和compensateurl为绝对可访问url、服务网络可达、接口返回标准json且含"result":"success"字段,并将req.timeout设为子服务p99耗时的3倍(不低于5000ms)。

Go 微服务里做分布式事务,Saga 是现实可行的路,但补偿逻辑和执行器不是配个库就能跑通——它直接决定事务是否真能回滚、状态是否可恢复、失败后能不能自动兜底。
dtm 的 Saga 执行器怎么避免卡在 Waiting 状态
dtm 启动 Saga 后长期停留在 Waiting 状态,基本等于整个流程被冻结。这不是 dtm 挂了,而是它根本没收到子事务的响应。
-
steps[i].Action和steps[i].Compensate必须是绝对 URL(如http://inventory-svc:8080/v1/stock/deduct),不能写成/v1/stock/deduct或localhost:8080/...;dtm client 不做任何 host 补全或协议推断 - 确保目标服务监听的是 dtm 可达的网络地址:K8s 环境下别用
127.0.0.1,Docker Compose 里注意 service name 解析,HTTP 服务需暴露端口且无反向代理拦截Content-Type: application/json - 每个子事务接口必须返回 JSON,且含
"result": "success"字段;dtm 只认这个字段判断成功,返回{"code":0}或空 body 会被当作失败并重试,直到超时进入Failed -
req.Timeout单位是毫秒,设太小(比如 1000)会导致 dtm 在子服务还没处理完就判定超时,反复重试;建议设为该服务 P99 响应时间的 3 倍,最低不低于 5000
补偿接口为什么必须支持“空回滚”和幂等
补偿不是“undo 上一步”,而是“让系统回到正向操作未发生的状态”。网络重试、dtm 重启、手动重放都会导致补偿被多次调用,不幂等 = 数据错乱。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
- 空回滚指:正向操作根本没执行(比如
deductStock因网络失败没到达库存服务),但 dtm 仍会调compensateStock;此时补偿必须能识别“该订单无扣减记录”,然后静默返回 success - 典型错误是写
UPDATE stock SET qty = qty + 1 WHERE order_id = ?—— 如果正向没执行,这条语句会把库存加多;正确做法是先查当前订单状态:SELECT status FROM stock_log WHERE order_id = ? AND action = 'deduct',只对已成功扣减的记录恢复 - 幂等 key 必须包含业务唯一标识 + 操作类型,例如
order_12345_deduct;dtm 用它去重,但你的补偿逻辑也得用同一 key 做 DB 唯一索引或 Redis SETNX 校验 - 别依赖数据库自增 ID 或时间戳做幂等依据——它们无法跨服务对齐;用业务主键(如订单号)最稳
手动实现 Saga 执行器时,状态持久化为什么不能省
用 RunSaga 这类内存函数跑本地测试没问题,但一上生产,进程重启或节点故障就会丢状态,补偿链直接断裂。
- 必须把
SagaInstance写入持久存储:推荐用 PostgreSQL 的saga_state表,字段至少含gid(全局事务 ID)、status、current_step、executed_steps(JSON 数组) - 每次正向执行前先
INSERT ... ON CONFLICT DO UPDATE,确保状态可重入;补偿触发前也得先查状态,防止重复补偿 - 别用内存 map 或 local file 存状态——K8s Pod 重启后数据全丢,且多实例部署时状态不同步
- 如果用 Redis,注意
SET saga:GID status:running EX 3600要配合 Lua 脚本保证原子性;单纯GET + SET在并发下会覆盖彼此
Lanerra/saga 与 dtm 在补偿触发时机上的关键差异
两者都走编排式,但失败后谁来决定“什么时候补偿”、以及“补到哪一步”,逻辑完全不同。
- dtm 是服务端驱动:子事务返回非
result: success时,dtm server 立即按逆序发起补偿请求,不等客户端确认;补偿失败会进死信队列,需人工干预 - Lanerra/saga 是客户端驱动:执行器在 Go 进程内同步捕获
Do()error,立刻调用已注册的Undo();补偿失败不会自动重试,得靠外层加 retry loop 或结合消息队列异步兜底 - dtm 的补偿路径由 URL 定义,Lanerra/saga 的
Undo()是纯函数,无法跨进程;这意味着 Lanerra/saga 更适合单体或强信任网络环境,dtm 更适合松耦合微服务 - dtm 要求每个子事务独立部署、URL 可寻址;Lanerra/saga 允许把多个
Do/Undo封在一个 struct 里,但违反服务边界时,补偿将无法隔离——比如库存和订单逻辑混写,补偿失败会连带影响订单状态修复
Saga 的复杂性不在代码行数,而在每一步失败路径是否被真实压测过;补偿接口上线前,必须用 chaos mesh 注入网络延迟、随机 500、DB 连接中断,验证空回滚和重试是否真正生效。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










