Kratos 集成 DTM 分布式事务核心是注册发现对齐与分支调用带 gid 且幂等:DTM server 注册地址须与 Kratos 客户端 Target 一致,分支必须返回标准 HTTP 状态码并实现幂等,Msg 模式需设 WaitResult=true 捕获错误,子事务屏障须手动调用 barrier.Call 且绑定正确事务 db。

Kratos 服务集成 DTM 做分布式事务,核心就两件事:注册发现对得上、分支调用带 gid 且幂等。其他都是围绕这两点踩坑。
dtm-server 注册地址必须和 Kratos 客户端配置一致
DTM 服务启动后,需要注册到注册中心(如 Nacos、Etcd),Kratos 客户端才能通过 discovery:// 地址找到它。常见错误是客户端写错 driver 或 endpoint:
-
Driver: "dtm-driver-kratos"必须显式导入(_ "github.com/dtm-labs/driver-kratos"),否则discovery解析失败,dtmgrpc.MustGenGid会 panic -
Target和EndPoint要区分清楚:Target是 DTM 向注册中心注册的路径(如discovery://127.0.0.1:2379/dtmservice),EndPoint是客户端直连 DTM 的 gRPC 地址(如grpc://localhost:36790);Kratos 客户端只用Target,不用EndPoint - 如果用 Etcd,确保 Kratos 服务也注册到同一 Etcd 实例,且
busiServer的discovery://地址能被正确解析(比如discovery://127.0.0.1:2379/busi)
分支服务必须支持幂等 + 正确响应 HTTP 状态码
DTM 在 Saga 模式下靠 HTTP 状态码判断分支是否成功,不是靠返回体里的 status 字段。Kratos 默认返回 200 OK,但 DTM 要求:成功返回 200,失败必须返回 4xx/5xx(如 409 Conflict 表示业务拒绝,500 Internal Server Error 表示执行异常)。
- 分支接口里不能只靠
c.JSON(200, ...)就完事,要主动校验前置状态(例如扣库存前查余额是否充足),不满足就c.AbortWithStatus(409) - 所有分支操作必须幂等:重复请求相同
gid+ 相同参数,结果必须一致(比如用INSERT IGNORE或先SELECT FOR UPDATE再更新) - 别在分支里手动开启本地事务——DTM 不感知,会导致补偿时状态错乱;让 Kratos 的
sql.Tx或 GORMTransaction在 handler 内完成即可
Msg 模式提交后 waitResult=true 才能捕获真实错误
Kratos 调用 dtmgrpc.NewMsgGrpc(...).Submit() 默认是异步提交,返回 nil 并不代表事务成功。如果你没设 m.WaitResult = true,错误会被吞掉,日志里只看到 “submit ok”,实际补偿可能已经触发。
- 开发阶段务必开启
m.WaitResult = true,这样Submit()会阻塞直到 DTM 返回全局结果(成功或失败) - 生产环境可关掉,但必须监听 DTM 控制台或日志中的
trans log,或用dtmgrpc.GetTransStatus(gid)主动轮询 - 注意
WaitResult=true会增加接口延迟(最长约 30s,默认超时),不适合高 QPS 场景
子事务屏障(Branch Barrier)不是自动生效的
DTM 的子事务屏障用来防悬挂、空补偿、幂等,但它不会自动包裹你的 handler。Kratos 里必须手动在分支入口调用 barrier.Call,否则屏障逻辑不触发。
- 不能只依赖 DTM server 配置,要在每个分支 handler 开头加:
barrier := dtmgrpc.BarrierFromGin(c),再barrier.Call(...) -
barrier.Call的db必须是当前事务的*sql.Tx,不能传全局*sql.DB,否则 barrier 表写不进同一事务 - Barrier 表(如
dtm_barrier)需提前建好,且和业务表在同一库;跨库场景 barrier 失效
最常被忽略的是 barrier 的 db 绑定和状态码语义——前者导致屏障形同虚设,后者让 DTM 误判分支成功而跳过补偿。这两个点不处理,Saga 事务基本等于裸奔。











