新项目选DTM,已有Java微服务集群选Seata-Go;DTM适合中小团队快速落地,Seata-Go需确保server与客户端版本兼容(2026年主流用1.8+),两者均要求幂等接口、本地消息表同库事务及Saga补偿前状态校验。

DTM 和 Seata-Go 到底该选哪个?
新项目直接上 DTM,已有 Java 微服务集群才考虑 Seata-Go。DTM 的 dtmcli.SagaNew 两行代码就能发起事务编排,失败自动触发补偿链;Seata-Go 启动失败时常见报错是 no available service 或 failed to register branch,本质是注册中心不通或 service.vgroupMapping 分组名不匹配。
实操建议:
- DTM 更适合中小团队:自带 HTTP/gRPC 接口、控制台、跨语言支持,对
go-zero、Kratos等框架有开箱即用集成 - Seata-Go 必须确认
seata-server版本与客户端兼容(2026 年主流用 1.8+),且需同步维护 Java 和 Go 客户端的配置项(如registry.type、store.mode) - 两者都绕不开幂等接口——所有
Confirm或Try操作必须可重试,否则重复调用会写脏数据
本地消息表为什么不能用 goroutine 异步写?
因为 INSERT INTO message (biz_id, content, status) VALUES (?, ?, 'pending') 必须和业务 SQL 在同一个数据库事务里提交,否则无法保证原子性。用 goroutine 包裹就脱离了事务上下文,业务成功但消息没落库,或消息写了但业务回滚,都会导致“已发未处理”或“已处理无消息”。
关键约束:
- 消息表和业务表必须在同一个数据库实例,跨库就失去事务保障
- 投递任务必须加分布式锁(如
Redis SET lock:msg:dispatch NX EX 30),避免多个实例扫描同一批message.status = 'pending'记录 - 定时扫描逻辑要区分“发送中”状态,避免重复投递后又失败,造成消息堆积
Saga 补偿函数为什么不能只传 ID?
很多人写补偿时只传订单 ID,然后直接执行“加库存”,但真实场景中:用户可能已取消订单、库存已被其他订单占用、商品甚至已下架。直接加库存会导致超卖。
正确做法:
- 补偿函数签名必须带
context.Context和原始请求快照(如req *CreateOrderRequest) - 补偿前先查当前订单状态(比如用
orderRepo.FindByID),确认是否仍处于可恢复状态 - 状态校验失败时,日志记录并返回 nil(不重试),避免无效补偿反复触发
go-zero 里怎么快速接入本地消息表?
go-zero 自带 sqlx + mq 组合封装,核心是把消息写入和业务操作塞进同一个 tx。它不依赖外部协调器,但要求你手动实现消息投递器(MessageDispatcher)和消费确认逻辑。
典型陷阱:
- 忘记在消费端调用
msg.Ack(),导致消息不断重投 - 消费失败后没更新
message.status为'failed',定时任务会无限重试同一笔失败消息 - 消息内容序列化用
json.Marshal而非proto.Marshal,后续扩展字段时反序列化失败
复杂点不在框架本身,而在状态流转的设计边界——比如“扣库存成功但消息写入失败”这种中间态,必须靠人工干预或告警兜底,没有全自动解法。

















