dtm是目前最省心的saga协调器选择,支持多语言、开箱即用,docker一条命令即可启动;需严格校验trans_type、steps数组及每个step的action和compensate url,补偿接口必须实现幂等以应对重试。

用 DTM 快速启动 Saga 协调器
直接上生产可用的协调模块,DTM 是目前最省心的选择。它不依赖语言栈、不强制改数据库、HTTP/gRPC 接口开箱即用,docker run 一条命令就能拉起协调服务。
常见错误是试图自己写协调器——Saga 的状态机、幂等、悬挂、空补偿、重试退避、事务日志持久化,这些细节加起来远超业务逻辑本身。DTM 已把它们封装成 /api/saga 接口,你只需定义分支和回调地址。
- 下载并运行 DTM Server:
docker run -d --name dtm -p 36789:36789 -p 36790:36790 yedf/dtm:1.22.0 - 确认服务就绪:
curl http://localhost:36789/health返回{"status":"success"} - 无需配置数据库(默认用内存存储),如需高可用,挂载
-v /path/to/dtm.yaml:/app/dtm.yaml并启用store.type = mysql
定义 Saga 分支时必须传哪些参数
调用 /api/saga 提交全局事务时,DTM 不校验业务逻辑,但会严格校验结构。漏掉或错配字段会导致 400 Bad Request 或静默失败。
关键字段只有三个:全局唯一 trans_type(固定填 saga)、steps 数组、每个 step 的 action 和 compensate URL。其余如 gid(建议用 UUID v4)、timeout(单位秒,默认 60)可选但强烈建议显式设置。
-
action和compensate必须是完整可访问的 HTTP 地址,且服务端要能处理POST请求 - 每个 step 的
action返回200才进入下一步;返回非2xx或超时,DTM 立即触发对应compensate - DTM 默认对所有请求加
X-Dtm-Gidheader,你的服务需透传该值用于幂等判断
补偿接口为什么总被重复调用
这不是 DTM 的 bug,而是 Saga 模式下最常踩的坑:补偿操作没做幂等。DTM 在网络抖动、超时、协调器重启时会重发 compensate 请求,这是设计使然,不是异常。
你的 compensate 接口必须能识别重复请求并安全退出。最简单可靠的做法是:查库确认原 action 是否已执行,若未执行则跳过;若已执行,则执行反向操作并记录 compensated = true 标志位。
- 避免只靠“查当前状态”判断——比如库存服务补偿时查“当前库存是否已回滚”,这在并发下不可靠
- 推荐方案:在业务表加
compensated_at字段,补偿接口先UPDATE ... SET compensated_at = NOW() WHERE gid = ? AND compensated_at IS NULL,再根据影响行数决定是否真正执行回滚逻辑 - DTM 会传递
X-Dtm-Trans-Type和X-Dtm-Gid,务必在补偿日志里打出来,方便排查重试链路
协调器宕机后事务状态怎么恢复
DTM 默认用内存存事务状态,重启后未完成的 Saga 就丢了——这在开发环境没问题,但生产必须切到持久化存储。
切换方式很简单:改 dtm.yaml 中的 store.type 为 mysql,填好 host/port/user/password/database,然后执行 DTM 提供的建表 SQL(sql/dtm_schema_mysql.sql)。之后所有事务状态都会写入 trans_global 和 trans_branch 表。
- MySQL 版本要求 ≥ 5.7,且必须开启
binlog(DTM 用它做事务状态同步) - 不要手动删
trans_global表里的记录——DTM 有自动清理策略,误删会导致状态不一致 - 如果协调器集群部署,多个实例共享同一 MySQL 库即可,DTM 内部用
SELECT ... FOR UPDATE保证并发安全
Saga 协调模块最难的不是启动,而是让每个 compensate 接口真正具备“可重入性”。很多团队卡在测试阶段反复看到补偿被调两次,根源不在协调器,而在业务服务没把幂等当第一需求来实现。











