文章目录
- 解决方案总览
- 两阶段提交协议(2PC)
- 三阶段提交协议(3PC)
- [TCC 业务补偿模式](#TCC 业务补偿模式)
- [Saga 长事务模式](#Saga 长事务模式)
- 本地消息表方案
- 事务消息方案
- [Seata 分布式事务框架](#Seata 分布式事务框架)
-
- [Seata 四种事务模式](#Seata 四种事务模式)
-
- [AT 模式](#AT 模式)
- [TCC 模式](#TCC 模式)
- [Saga 模式](#Saga 模式)
- [XA 模式](#XA 模式)
- [Seata 控制台](#Seata 控制台)
分布式事务是指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上,需要保证这些节点上的操作同时成功或同时失败。随着业务发展,单体应用逐渐拆分为微服务架构,每个服务拥有独立的数据库,当一次业务操作涉及多个服务时,就需要分布式事务来保证数据的一致性。
解决方案总览
| 方案 | 一致性 | 性能 | 复杂度 | 适用场景 | 典型应用 |
|---|---|---|---|---|---|
| 2PC | 强一致 | 低 | 中 | 对一致性要求高的场景 | 数据库 XA |
| 3PC | 强一致 | 中 | 高 | 较少使用 | 理论研究 |
| TCC | 最终一致 | 高 | 高 | 对性能要求高的核心业务 | 金融支付 |
| Saga | 最终一致 | 高 | 中 | 长事务、业务流程复杂的场景 | 电商订单 |
| 本地消息表 | 最终一致 | 高 | 低 | 简单业务场景 | 简单业务 |
| 事务消息 | 最终一致 | 高 | 中 | 异步解耦场景 | 异步解耦 |
两阶段提交协议(2PC)
两阶段提交将事务提交过程分为两个阶段:
资源管理器2 资源管理器1 事务管理器 资源管理器2 资源管理器1 事务管理器 #mermaid-svg-lHzswVgvSMVTA3IV{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-lHzswVgvSMVTA3IV .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-lHzswVgvSMVTA3IV .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-lHzswVgvSMVTA3IV .error-icon{fill:#552222;}#mermaid-svg-lHzswVgvSMVTA3IV .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-lHzswVgvSMVTA3IV .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-lHzswVgvSMVTA3IV .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-lHzswVgvSMVTA3IV .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-lHzswVgvSMVTA3IV .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-lHzswVgvSMVTA3IV .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-lHzswVgvSMVTA3IV .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-lHzswVgvSMVTA3IV .marker{fill:#333333;stroke:#333333;}#mermaid-svg-lHzswVgvSMVTA3IV .marker.cross{stroke:#333333;}#mermaid-svg-lHzswVgvSMVTA3IV svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-lHzswVgvSMVTA3IV p{margin:0;}#mermaid-svg-lHzswVgvSMVTA3IV .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-lHzswVgvSMVTA3IV text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-lHzswVgvSMVTA3IV .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-lHzswVgvSMVTA3IV .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-lHzswVgvSMVTA3IV .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-lHzswVgvSMVTA3IV .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-lHzswVgvSMVTA3IV #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-lHzswVgvSMVTA3IV .sequenceNumber{fill:white;}#mermaid-svg-lHzswVgvSMVTA3IV #sequencenumber{fill:#333;}#mermaid-svg-lHzswVgvSMVTA3IV #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-lHzswVgvSMVTA3IV .messageText{fill:#333;stroke:none;}#mermaid-svg-lHzswVgvSMVTA3IV .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-lHzswVgvSMVTA3IV .labelText,#mermaid-svg-lHzswVgvSMVTA3IV .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-lHzswVgvSMVTA3IV .loopText,#mermaid-svg-lHzswVgvSMVTA3IV .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-lHzswVgvSMVTA3IV .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-lHzswVgvSMVTA3IV .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-lHzswVgvSMVTA3IV .noteText,#mermaid-svg-lHzswVgvSMVTA3IV .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-lHzswVgvSMVTA3IV .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-lHzswVgvSMVTA3IV .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-lHzswVgvSMVTA3IV .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-lHzswVgvSMVTA3IV .actorPopupMenu{position:absolute;}#mermaid-svg-lHzswVgvSMVTA3IV .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-lHzswVgvSMVTA3IV .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-lHzswVgvSMVTA3IV .actor-man circle,#mermaid-svg-lHzswVgvSMVTA3IV line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-lHzswVgvSMVTA3IV :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 阶段一:准备阶段(Prepare) 阶段二:提交阶段(Commit/Rollback) alt 所有参与者返回成功 任一参与者返回失败 发送 Prepare 请求 执行本地事务,但不提交 返回执行结果(Ready) 发送 Prepare 请求 执行本地事务,但不提交 返回执行结果(Ready) Commit Commit OK OK Rollback Rollback OK OK
局限:协调者存在单点故障风险,一旦宕机则参与者因无超时机制而无限阻塞,且第二阶段部分参与者可能收不到提交指令造成数据不一致。
三阶段提交协议(3PC)
3PC 在 2PC 基础上增加 CanCommit 阶段以减少资源锁定时间,并引入超时机制避免永久阻塞。
资源管理器2 资源管理器1 事务管理器 资源管理器2 资源管理器1 事务管理器 #mermaid-svg-E2VGwg4aZ9ZCzQSh{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-E2VGwg4aZ9ZCzQSh .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-E2VGwg4aZ9ZCzQSh .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-E2VGwg4aZ9ZCzQSh .error-icon{fill:#552222;}#mermaid-svg-E2VGwg4aZ9ZCzQSh .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-E2VGwg4aZ9ZCzQSh .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-E2VGwg4aZ9ZCzQSh .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-E2VGwg4aZ9ZCzQSh .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-E2VGwg4aZ9ZCzQSh .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-E2VGwg4aZ9ZCzQSh .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-E2VGwg4aZ9ZCzQSh .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-E2VGwg4aZ9ZCzQSh .marker{fill:#333333;stroke:#333333;}#mermaid-svg-E2VGwg4aZ9ZCzQSh .marker.cross{stroke:#333333;}#mermaid-svg-E2VGwg4aZ9ZCzQSh svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-E2VGwg4aZ9ZCzQSh p{margin:0;}#mermaid-svg-E2VGwg4aZ9ZCzQSh .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-E2VGwg4aZ9ZCzQSh text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-E2VGwg4aZ9ZCzQSh .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-E2VGwg4aZ9ZCzQSh .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-E2VGwg4aZ9ZCzQSh .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-E2VGwg4aZ9ZCzQSh .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-E2VGwg4aZ9ZCzQSh #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-E2VGwg4aZ9ZCzQSh .sequenceNumber{fill:white;}#mermaid-svg-E2VGwg4aZ9ZCzQSh #sequencenumber{fill:#333;}#mermaid-svg-E2VGwg4aZ9ZCzQSh #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-E2VGwg4aZ9ZCzQSh .messageText{fill:#333;stroke:none;}#mermaid-svg-E2VGwg4aZ9ZCzQSh .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-E2VGwg4aZ9ZCzQSh .labelText,#mermaid-svg-E2VGwg4aZ9ZCzQSh .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-E2VGwg4aZ9ZCzQSh .loopText,#mermaid-svg-E2VGwg4aZ9ZCzQSh .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-E2VGwg4aZ9ZCzQSh .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-E2VGwg4aZ9ZCzQSh .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-E2VGwg4aZ9ZCzQSh .noteText,#mermaid-svg-E2VGwg4aZ9ZCzQSh .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-E2VGwg4aZ9ZCzQSh .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-E2VGwg4aZ9ZCzQSh .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-E2VGwg4aZ9ZCzQSh .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-E2VGwg4aZ9ZCzQSh .actorPopupMenu{position:absolute;}#mermaid-svg-E2VGwg4aZ9ZCzQSh .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-E2VGwg4aZ9ZCzQSh .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-E2VGwg4aZ9ZCzQSh .actor-man circle,#mermaid-svg-E2VGwg4aZ9ZCzQSh line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-E2VGwg4aZ9ZCzQSh :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 阶段一:CanCommit(不锁定资源) 协调者超时未收到全部回复 → 发送 Abort 阶段二:PreCommit(锁定资源) 参与者超时未收到 PreCommit → 自动 Abort(尚未锁定资源,安全) 协调者超时未收到全部 ACK → 发送 Abort 阶段三:DoCommit(释放资源) 参与者超时未收到 DoCommit → 自动 Commit(已进入 Prepared 状态,安全) 询问是否可以执行事务 Yes 询问是否可以执行事务 Yes PreCommit 执行事务操作,进入准备状态 ACK PreCommit 执行事务操作,进入准备状态 ACK DoCommit DoCommit OK OK
协议局限性:
3PC 虽解决了阻塞问题,但在网络分区场景下仍无法保证一致性,这也是实际生产中较少使用的原因:
- 部分参与者收到 PreCommit 而部分未收到,前者超时提交、后者超时中止,造成数据不一致
- 协调者在发送 DoCommit 时崩溃,部分参与者收到提交而部分超时提交,但超时前若收到 Abort 指令仍可能不一致
TCC 业务补偿模式
TCC(Try-Confirm-Cancel)将业务逻辑拆分为三个阶段,通过业务层面的补偿机制实现分布式事务:
#mermaid-svg-EbT8YHM5jVql7Rcy{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-EbT8YHM5jVql7Rcy .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-EbT8YHM5jVql7Rcy .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-EbT8YHM5jVql7Rcy .error-icon{fill:#552222;}#mermaid-svg-EbT8YHM5jVql7Rcy .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-EbT8YHM5jVql7Rcy .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-EbT8YHM5jVql7Rcy .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-EbT8YHM5jVql7Rcy .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-EbT8YHM5jVql7Rcy .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-EbT8YHM5jVql7Rcy .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-EbT8YHM5jVql7Rcy .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-EbT8YHM5jVql7Rcy .marker{fill:#333333;stroke:#333333;}#mermaid-svg-EbT8YHM5jVql7Rcy .marker.cross{stroke:#333333;}#mermaid-svg-EbT8YHM5jVql7Rcy svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-EbT8YHM5jVql7Rcy p{margin:0;}#mermaid-svg-EbT8YHM5jVql7Rcy .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-EbT8YHM5jVql7Rcy .cluster-label text{fill:#333;}#mermaid-svg-EbT8YHM5jVql7Rcy .cluster-label span{color:#333;}#mermaid-svg-EbT8YHM5jVql7Rcy .cluster-label span p{background-color:transparent;}#mermaid-svg-EbT8YHM5jVql7Rcy .label text,#mermaid-svg-EbT8YHM5jVql7Rcy span{fill:#333;color:#333;}#mermaid-svg-EbT8YHM5jVql7Rcy .node rect,#mermaid-svg-EbT8YHM5jVql7Rcy .node circle,#mermaid-svg-EbT8YHM5jVql7Rcy .node ellipse,#mermaid-svg-EbT8YHM5jVql7Rcy .node polygon,#mermaid-svg-EbT8YHM5jVql7Rcy .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-EbT8YHM5jVql7Rcy .rough-node .label text,#mermaid-svg-EbT8YHM5jVql7Rcy .node .label text,#mermaid-svg-EbT8YHM5jVql7Rcy .image-shape .label,#mermaid-svg-EbT8YHM5jVql7Rcy .icon-shape .label{text-anchor:middle;}#mermaid-svg-EbT8YHM5jVql7Rcy .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-EbT8YHM5jVql7Rcy .rough-node .label,#mermaid-svg-EbT8YHM5jVql7Rcy .node .label,#mermaid-svg-EbT8YHM5jVql7Rcy .image-shape .label,#mermaid-svg-EbT8YHM5jVql7Rcy .icon-shape .label{text-align:center;}#mermaid-svg-EbT8YHM5jVql7Rcy .node.clickable{cursor:pointer;}#mermaid-svg-EbT8YHM5jVql7Rcy .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-EbT8YHM5jVql7Rcy .arrowheadPath{fill:#333333;}#mermaid-svg-EbT8YHM5jVql7Rcy .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-EbT8YHM5jVql7Rcy .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-EbT8YHM5jVql7Rcy .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-EbT8YHM5jVql7Rcy .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-EbT8YHM5jVql7Rcy .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-EbT8YHM5jVql7Rcy .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-EbT8YHM5jVql7Rcy .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-EbT8YHM5jVql7Rcy .cluster text{fill:#333;}#mermaid-svg-EbT8YHM5jVql7Rcy .cluster span{color:#333;}#mermaid-svg-EbT8YHM5jVql7Rcy div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-EbT8YHM5jVql7Rcy .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-EbT8YHM5jVql7Rcy rect.text{fill:none;stroke-width:0;}#mermaid-svg-EbT8YHM5jVql7Rcy .icon-shape,#mermaid-svg-EbT8YHM5jVql7Rcy .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-EbT8YHM5jVql7Rcy .icon-shape p,#mermaid-svg-EbT8YHM5jVql7Rcy .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-EbT8YHM5jVql7Rcy .icon-shape .label rect,#mermaid-svg-EbT8YHM5jVql7Rcy .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-EbT8YHM5jVql7Rcy .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-EbT8YHM5jVql7Rcy .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-EbT8YHM5jVql7Rcy :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 成功
失败
Try阶段
冻结转账金额
执行结果
Confirm阶段
扣除冻结金额并增加对方余额
Cancel阶段
释放冻结金额
TCC 在实际应用中需处理两类异常问题:
- 空回滚:Try 未执行,Cancel 却被调用了。场景是 Try 阶段网络超时,协调者认为失败调用 Cancel,但 Cancel 被调用时 Try 根本没有执行,可能导致错误的补偿逻辑。
- 悬挂问题:Cancel 比 Try 先执行。场景是 Try 请求网络拥堵,Cancel 先到达参与者执行,之后 Try 才到达,导致 Try 执行成功后资源被冻结但事务已取消无法释放。
以下代码示例展示了三个阶段如何处理上述问题:
- Try 方法:防悬挂(检查 Cancel 记录)+ 防重(检查 Try 记录)+ 冻结资源
java
@Transactional
public boolean tryFreeze(String txId, String userId, BigDecimal amount) {
// 防悬挂:查询数据库是否存在该事务 ID 的 Cancel 记录
if (cancelRecordRepository.existsByTxId(txId)) {
return false;
}
// 防重:查询数据库是否存在该事务 ID 的 Try 记录
if (tryRecordRepository.existsByTxId(txId)) {
return true;
}
// 冻结金额
accountRepository.freeze(userId, amount);
// 记录 Try 已执行
tryRecordRepository.save(new TryRecord(txId));
return true;
}
- Confirm 方法:幂等检查 + 确认执行
java
@Transactional
public boolean confirmDeduct(String txId, String userId, BigDecimal amount) {
// 幂等检查:查询数据库是否存在该事务 ID 的 Confirm 记录
if (confirmRecordRepository.existsByTxId(txId)) {
return true;
}
// 扣除冻结金额
accountRepository.deductFrozen(userId, amount);
// 记录 Confirm 已执行
confirmRecordRepository.save(new ConfirmRecord(txId));
return true;
}
- Cancel 方法:幂等检查 + 先记录 Cancel(防悬挂)+ 空回滚检查 + 释放资源
java
@Transactional
public boolean cancelFreeze(String txId, String userId, BigDecimal amount) {
// 幂等检查:查询数据库是否存在该事务 ID 的 Cancel 记录
if (cancelRecordRepository.existsByTxId(txId)) {
return true;
}
// 记录 Cancel 已执行(必须在 Try 检查之前)
cancelRecordRepository.save(new CancelRecord(txId));
// 空回滚检查:查询数据库是否存在该事务 ID 的 Try 记录
if (!tryRecordRepository.existsByTxId(txId)) {
return true;
}
// 解冻金额
accountRepository.unfreeze(userId, amount);
return true;
}
Saga 长事务模式
Saga 将长事务拆分为多个本地短事务,每个事务都有对应的补偿操作:
#mermaid-svg-nACXi4n8FTuzksfM{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-nACXi4n8FTuzksfM .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-nACXi4n8FTuzksfM .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-nACXi4n8FTuzksfM .error-icon{fill:#552222;}#mermaid-svg-nACXi4n8FTuzksfM .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-nACXi4n8FTuzksfM .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-nACXi4n8FTuzksfM .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-nACXi4n8FTuzksfM .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-nACXi4n8FTuzksfM .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-nACXi4n8FTuzksfM .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-nACXi4n8FTuzksfM .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-nACXi4n8FTuzksfM .marker{fill:#333333;stroke:#333333;}#mermaid-svg-nACXi4n8FTuzksfM .marker.cross{stroke:#333333;}#mermaid-svg-nACXi4n8FTuzksfM svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-nACXi4n8FTuzksfM p{margin:0;}#mermaid-svg-nACXi4n8FTuzksfM .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-nACXi4n8FTuzksfM .cluster-label text{fill:#333;}#mermaid-svg-nACXi4n8FTuzksfM .cluster-label span{color:#333;}#mermaid-svg-nACXi4n8FTuzksfM .cluster-label span p{background-color:transparent;}#mermaid-svg-nACXi4n8FTuzksfM .label text,#mermaid-svg-nACXi4n8FTuzksfM span{fill:#333;color:#333;}#mermaid-svg-nACXi4n8FTuzksfM .node rect,#mermaid-svg-nACXi4n8FTuzksfM .node circle,#mermaid-svg-nACXi4n8FTuzksfM .node ellipse,#mermaid-svg-nACXi4n8FTuzksfM .node polygon,#mermaid-svg-nACXi4n8FTuzksfM .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-nACXi4n8FTuzksfM .rough-node .label text,#mermaid-svg-nACXi4n8FTuzksfM .node .label text,#mermaid-svg-nACXi4n8FTuzksfM .image-shape .label,#mermaid-svg-nACXi4n8FTuzksfM .icon-shape .label{text-anchor:middle;}#mermaid-svg-nACXi4n8FTuzksfM .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-nACXi4n8FTuzksfM .rough-node .label,#mermaid-svg-nACXi4n8FTuzksfM .node .label,#mermaid-svg-nACXi4n8FTuzksfM .image-shape .label,#mermaid-svg-nACXi4n8FTuzksfM .icon-shape .label{text-align:center;}#mermaid-svg-nACXi4n8FTuzksfM .node.clickable{cursor:pointer;}#mermaid-svg-nACXi4n8FTuzksfM .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-nACXi4n8FTuzksfM .arrowheadPath{fill:#333333;}#mermaid-svg-nACXi4n8FTuzksfM .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-nACXi4n8FTuzksfM .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-nACXi4n8FTuzksfM .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-nACXi4n8FTuzksfM .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-nACXi4n8FTuzksfM .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-nACXi4n8FTuzksfM .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-nACXi4n8FTuzksfM .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-nACXi4n8FTuzksfM .cluster text{fill:#333;}#mermaid-svg-nACXi4n8FTuzksfM .cluster span{color:#333;}#mermaid-svg-nACXi4n8FTuzksfM div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-nACXi4n8FTuzksfM .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-nACXi4n8FTuzksfM rect.text{fill:none;stroke-width:0;}#mermaid-svg-nACXi4n8FTuzksfM .icon-shape,#mermaid-svg-nACXi4n8FTuzksfM .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-nACXi4n8FTuzksfM .icon-shape p,#mermaid-svg-nACXi4n8FTuzksfM .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-nACXi4n8FTuzksfM .icon-shape .label rect,#mermaid-svg-nACXi4n8FTuzksfM .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-nACXi4n8FTuzksfM .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-nACXi4n8FTuzksfM .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-nACXi4n8FTuzksfM :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 失败
失败
失败
T1
订单创建
T2
扣减库存
T3
扣减余额
T4
发货
C1
取消订单
C2
恢复库存
C3
恢复余额
Saga 的核心是补偿事务,用于撤销已完成的操作:
- 正向流程:T1 → T2 → T3 → T4
- 补偿流程:C3 → C2 → C1(逆序执行)
Saga 有两种实现方式来协调上述流程:
- 编排式:没有中心协调者,各服务通过事件驱动协作,每个服务监听事件并决定下一步操作。
账户服务 库存服务 订单服务 账户服务 库存服务 订单服务 #mermaid-svg-TPWHain0ZNpxLsxZ{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-TPWHain0ZNpxLsxZ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-TPWHain0ZNpxLsxZ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-TPWHain0ZNpxLsxZ .error-icon{fill:#552222;}#mermaid-svg-TPWHain0ZNpxLsxZ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-TPWHain0ZNpxLsxZ .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-TPWHain0ZNpxLsxZ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-TPWHain0ZNpxLsxZ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-TPWHain0ZNpxLsxZ .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-TPWHain0ZNpxLsxZ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-TPWHain0ZNpxLsxZ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-TPWHain0ZNpxLsxZ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-TPWHain0ZNpxLsxZ .marker.cross{stroke:#333333;}#mermaid-svg-TPWHain0ZNpxLsxZ svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-TPWHain0ZNpxLsxZ p{margin:0;}#mermaid-svg-TPWHain0ZNpxLsxZ .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-TPWHain0ZNpxLsxZ text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-TPWHain0ZNpxLsxZ .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-TPWHain0ZNpxLsxZ .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-TPWHain0ZNpxLsxZ .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-TPWHain0ZNpxLsxZ .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-TPWHain0ZNpxLsxZ #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-TPWHain0ZNpxLsxZ .sequenceNumber{fill:white;}#mermaid-svg-TPWHain0ZNpxLsxZ #sequencenumber{fill:#333;}#mermaid-svg-TPWHain0ZNpxLsxZ #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-TPWHain0ZNpxLsxZ .messageText{fill:#333;stroke:none;}#mermaid-svg-TPWHain0ZNpxLsxZ .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-TPWHain0ZNpxLsxZ .labelText,#mermaid-svg-TPWHain0ZNpxLsxZ .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-TPWHain0ZNpxLsxZ .loopText,#mermaid-svg-TPWHain0ZNpxLsxZ .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-TPWHain0ZNpxLsxZ .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-TPWHain0ZNpxLsxZ .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-TPWHain0ZNpxLsxZ .noteText,#mermaid-svg-TPWHain0ZNpxLsxZ .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-TPWHain0ZNpxLsxZ .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-TPWHain0ZNpxLsxZ .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-TPWHain0ZNpxLsxZ .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-TPWHain0ZNpxLsxZ .actorPopupMenu{position:absolute;}#mermaid-svg-TPWHain0ZNpxLsxZ .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-TPWHain0ZNpxLsxZ .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-TPWHain0ZNpxLsxZ .actor-man circle,#mermaid-svg-TPWHain0ZNpxLsxZ line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-TPWHain0ZNpxLsxZ :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 创建订单 发布 订单创建 事件 监听事件,扣减库存 发布 库存扣减成功 事件 监听事件,扣减余额 发布 支付失败 事件(余额不足) 监听事件,恢复库存 发布 库存恢复成功 事件 监听事件,取消订单
- 协调式:有中心协调者统一调度各服务的执行顺序,知道全局流程。
账户服务 库存服务 订单服务 协调者 账户服务 库存服务 订单服务 协调者 #mermaid-svg-aa50kaiGMFY0Utt1{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-aa50kaiGMFY0Utt1 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-aa50kaiGMFY0Utt1 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-aa50kaiGMFY0Utt1 .error-icon{fill:#552222;}#mermaid-svg-aa50kaiGMFY0Utt1 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-aa50kaiGMFY0Utt1 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-aa50kaiGMFY0Utt1 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-aa50kaiGMFY0Utt1 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-aa50kaiGMFY0Utt1 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-aa50kaiGMFY0Utt1 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-aa50kaiGMFY0Utt1 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-aa50kaiGMFY0Utt1 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-aa50kaiGMFY0Utt1 .marker.cross{stroke:#333333;}#mermaid-svg-aa50kaiGMFY0Utt1 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-aa50kaiGMFY0Utt1 p{margin:0;}#mermaid-svg-aa50kaiGMFY0Utt1 .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-aa50kaiGMFY0Utt1 text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-aa50kaiGMFY0Utt1 .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-aa50kaiGMFY0Utt1 .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-aa50kaiGMFY0Utt1 .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-aa50kaiGMFY0Utt1 .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-aa50kaiGMFY0Utt1 #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-aa50kaiGMFY0Utt1 .sequenceNumber{fill:white;}#mermaid-svg-aa50kaiGMFY0Utt1 #sequencenumber{fill:#333;}#mermaid-svg-aa50kaiGMFY0Utt1 #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-aa50kaiGMFY0Utt1 .messageText{fill:#333;stroke:none;}#mermaid-svg-aa50kaiGMFY0Utt1 .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-aa50kaiGMFY0Utt1 .labelText,#mermaid-svg-aa50kaiGMFY0Utt1 .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-aa50kaiGMFY0Utt1 .loopText,#mermaid-svg-aa50kaiGMFY0Utt1 .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-aa50kaiGMFY0Utt1 .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-aa50kaiGMFY0Utt1 .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-aa50kaiGMFY0Utt1 .noteText,#mermaid-svg-aa50kaiGMFY0Utt1 .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-aa50kaiGMFY0Utt1 .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-aa50kaiGMFY0Utt1 .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-aa50kaiGMFY0Utt1 .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-aa50kaiGMFY0Utt1 .actorPopupMenu{position:absolute;}#mermaid-svg-aa50kaiGMFY0Utt1 .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-aa50kaiGMFY0Utt1 .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-aa50kaiGMFY0Utt1 .actor-man circle,#mermaid-svg-aa50kaiGMFY0Utt1 line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-aa50kaiGMFY0Utt1 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 1. 创建订单 成功 2. 扣减库存 成功 3. 扣减余额 失败(余额不足) 4. 恢复库存(补偿) 成功 5. 取消订单(补偿) 成功
本地消息表方案
本地消息表通过将业务操作与消息记录绑定在同一本地事务中,利用数据库 ACID 特性保证两者的原子性,再通过定时任务异步发送消息,实现分布式事务的最终一致性:
库存服务 消息队列 定时任务 数据库 订单服务 库存服务 消息队列 定时任务 数据库 订单服务 #mermaid-svg-KAYKYuX4283Q3zQj{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-KAYKYuX4283Q3zQj .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-KAYKYuX4283Q3zQj .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-KAYKYuX4283Q3zQj .error-icon{fill:#552222;}#mermaid-svg-KAYKYuX4283Q3zQj .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-KAYKYuX4283Q3zQj .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-KAYKYuX4283Q3zQj .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-KAYKYuX4283Q3zQj .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-KAYKYuX4283Q3zQj .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-KAYKYuX4283Q3zQj .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-KAYKYuX4283Q3zQj .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-KAYKYuX4283Q3zQj .marker{fill:#333333;stroke:#333333;}#mermaid-svg-KAYKYuX4283Q3zQj .marker.cross{stroke:#333333;}#mermaid-svg-KAYKYuX4283Q3zQj svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-KAYKYuX4283Q3zQj p{margin:0;}#mermaid-svg-KAYKYuX4283Q3zQj .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-KAYKYuX4283Q3zQj text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-KAYKYuX4283Q3zQj .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-KAYKYuX4283Q3zQj .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-KAYKYuX4283Q3zQj .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-KAYKYuX4283Q3zQj .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-KAYKYuX4283Q3zQj #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-KAYKYuX4283Q3zQj .sequenceNumber{fill:white;}#mermaid-svg-KAYKYuX4283Q3zQj #sequencenumber{fill:#333;}#mermaid-svg-KAYKYuX4283Q3zQj #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-KAYKYuX4283Q3zQj .messageText{fill:#333;stroke:none;}#mermaid-svg-KAYKYuX4283Q3zQj .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-KAYKYuX4283Q3zQj .labelText,#mermaid-svg-KAYKYuX4283Q3zQj .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-KAYKYuX4283Q3zQj .loopText,#mermaid-svg-KAYKYuX4283Q3zQj .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-KAYKYuX4283Q3zQj .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-KAYKYuX4283Q3zQj .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-KAYKYuX4283Q3zQj .noteText,#mermaid-svg-KAYKYuX4283Q3zQj .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-KAYKYuX4283Q3zQj .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-KAYKYuX4283Q3zQj .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-KAYKYuX4283Q3zQj .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-KAYKYuX4283Q3zQj .actorPopupMenu{position:absolute;}#mermaid-svg-KAYKYuX4283Q3zQj .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-KAYKYuX4283Q3zQj .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-KAYKYuX4283Q3zQj .actor-man circle,#mermaid-svg-KAYKYuX4283Q3zQj line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-KAYKYuX4283Q3zQj :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 本地事务 异步轮询 创建订单 写入消息表(status=PENDING) 事务提交成功 查询 status=PENDING 的消息 返回待发送消息 发送消息(topic=order-created) 发送成功 更新 status=SENT 投递消息 扣减库存 ACK
核心机制:业务操作与消息记录在同一本地事务中持久化,确保两者同时成功或失败。若事务提交后、消息发送前业务服务发生故障,定时任务仍可通过轮询消息表完成消息投递,避免消息丢失。
消息表核心字段:
| 字段 | 说明 |
|---|---|
| message_id | 消息唯一标识,用于幂等处理 |
| topic | 消息主题,标识消息类型 |
| content | 消息内容,通常为 JSON 格式 |
| status | 消息状态(待发送、已发送、已消费) |
| retry_count | 重试次数,用于失败重试控制 |
| next_retry_time | 下次重试时间,用于延迟重试 |
优势:实现简单,不依赖特殊组件;可靠性高,消息不丢失;与业务解耦,易于维护。
局限:需要额外的存储空间;定时轮询有延迟;消息表可能成为瓶颈。
事务消息方案
事务消息通过消息队列的半消息机制,保证消息发送和本地事务的原子性:
库存服务 消息队列 订单服务 库存服务 消息队列 订单服务 #mermaid-svg-yMIWapf79zdR4SDs{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-yMIWapf79zdR4SDs .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-yMIWapf79zdR4SDs .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-yMIWapf79zdR4SDs .error-icon{fill:#552222;}#mermaid-svg-yMIWapf79zdR4SDs .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-yMIWapf79zdR4SDs .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-yMIWapf79zdR4SDs .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-yMIWapf79zdR4SDs .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-yMIWapf79zdR4SDs .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-yMIWapf79zdR4SDs .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-yMIWapf79zdR4SDs .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-yMIWapf79zdR4SDs .marker{fill:#333333;stroke:#333333;}#mermaid-svg-yMIWapf79zdR4SDs .marker.cross{stroke:#333333;}#mermaid-svg-yMIWapf79zdR4SDs svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-yMIWapf79zdR4SDs p{margin:0;}#mermaid-svg-yMIWapf79zdR4SDs .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-yMIWapf79zdR4SDs text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-yMIWapf79zdR4SDs .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-yMIWapf79zdR4SDs .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-yMIWapf79zdR4SDs .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-yMIWapf79zdR4SDs .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-yMIWapf79zdR4SDs #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-yMIWapf79zdR4SDs .sequenceNumber{fill:white;}#mermaid-svg-yMIWapf79zdR4SDs #sequencenumber{fill:#333;}#mermaid-svg-yMIWapf79zdR4SDs #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-yMIWapf79zdR4SDs .messageText{fill:#333;stroke:none;}#mermaid-svg-yMIWapf79zdR4SDs .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-yMIWapf79zdR4SDs .labelText,#mermaid-svg-yMIWapf79zdR4SDs .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-yMIWapf79zdR4SDs .loopText,#mermaid-svg-yMIWapf79zdR4SDs .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-yMIWapf79zdR4SDs .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-yMIWapf79zdR4SDs .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-yMIWapf79zdR4SDs .noteText,#mermaid-svg-yMIWapf79zdR4SDs .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-yMIWapf79zdR4SDs .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-yMIWapf79zdR4SDs .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-yMIWapf79zdR4SDs .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-yMIWapf79zdR4SDs .actorPopupMenu{position:absolute;}#mermaid-svg-yMIWapf79zdR4SDs .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-yMIWapf79zdR4SDs .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-yMIWapf79zdR4SDs .actor-man circle,#mermaid-svg-yMIWapf79zdR4SDs line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-yMIWapf79zdR4SDs :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 本地事务 alt 事务成功 事务失败 发送半消息(topic=order-created) 返回消息 ID 创建订单 提交消息 投递消息 扣减库存 ACK 回滚消息 删除消息
核心机制:半消息发送后暂不可消费,本地事务执行完毕后提交或回滚消息。若生产者崩溃未返回确认,MQ 通过事务状态回查确定消息最终状态。
半消息是一种特殊的消息状态,指消息已经发送到 MQ,但暂时不能被消费者消费。它处于"待确认"状态,等待生产者通知 MQ 最终提交或删除。
Seata 分布式事务框架
Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。
核心组件:
| 组件 | 说明 |
|---|---|
| TC(Transaction Coordinator) | 事务协调者,维护全局和分支事务的状态 |
| TM(Transaction Manager) | 事务管理器,定义全局事务的范围 |
| RM(Resource Manager) | 资源管理器,管理分支事务处理的资源 |
事务模式:
| 模式 | 说明 | 一致性 | 性能 |
|---|---|---|---|
| AT(Atomic Transaction) | 自动补偿模式,无侵入 | 最终一致 | 高 |
| TCC(Try-Confirm-Cancel) | 手动补偿模式,需要编写补偿代码 | 最终一致 | 高 |
| Saga | 长事务模式,源自 1987 年论文《Sagas》 | 最终一致 | 高 |
| XA(Extended Architecture) | 传统两阶段提交,X/Open 组织定义的标准接口 | 强一致 | 低 |
Seata 四种事务模式
AT 模式
AT 模式通过拦截 SQL 自动生成前后镜像(undo log),一阶段提交本地事务并申请全局锁,二阶段根据 undo log 自动回滚或清理。
接入方式:
java
@Service
public class OrderService {
@GlobalTransactional
public void createOrder(Order order) {
orderRepository.save(order);
inventoryFeignClient.deduct(order.getProductId(), order.getQuantity());
accountFeignClient.deduct(order.getUserId(), order.getAmount());
}
}
运行机制:
数据库 资源管理器 事务协调者 事务管理器 数据库 资源管理器 事务协调者 事务管理器 #mermaid-svg-GXOB98n0TwErVofa{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-GXOB98n0TwErVofa .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-GXOB98n0TwErVofa .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-GXOB98n0TwErVofa .error-icon{fill:#552222;}#mermaid-svg-GXOB98n0TwErVofa .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-GXOB98n0TwErVofa .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-GXOB98n0TwErVofa .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-GXOB98n0TwErVofa .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-GXOB98n0TwErVofa .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-GXOB98n0TwErVofa .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-GXOB98n0TwErVofa .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-GXOB98n0TwErVofa .marker{fill:#333333;stroke:#333333;}#mermaid-svg-GXOB98n0TwErVofa .marker.cross{stroke:#333333;}#mermaid-svg-GXOB98n0TwErVofa svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-GXOB98n0TwErVofa p{margin:0;}#mermaid-svg-GXOB98n0TwErVofa .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-GXOB98n0TwErVofa text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-GXOB98n0TwErVofa .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-GXOB98n0TwErVofa .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-GXOB98n0TwErVofa .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-GXOB98n0TwErVofa .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-GXOB98n0TwErVofa #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-GXOB98n0TwErVofa .sequenceNumber{fill:white;}#mermaid-svg-GXOB98n0TwErVofa #sequencenumber{fill:#333;}#mermaid-svg-GXOB98n0TwErVofa #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-GXOB98n0TwErVofa .messageText{fill:#333;stroke:none;}#mermaid-svg-GXOB98n0TwErVofa .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-GXOB98n0TwErVofa .labelText,#mermaid-svg-GXOB98n0TwErVofa .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-GXOB98n0TwErVofa .loopText,#mermaid-svg-GXOB98n0TwErVofa .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-GXOB98n0TwErVofa .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-GXOB98n0TwErVofa .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-GXOB98n0TwErVofa .noteText,#mermaid-svg-GXOB98n0TwErVofa .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-GXOB98n0TwErVofa .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-GXOB98n0TwErVofa .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-GXOB98n0TwErVofa .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-GXOB98n0TwErVofa .actorPopupMenu{position:absolute;}#mermaid-svg-GXOB98n0TwErVofa .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-GXOB98n0TwErVofa .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-GXOB98n0TwErVofa .actor-man circle,#mermaid-svg-GXOB98n0TwErVofa line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-GXOB98n0TwErVofa :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 一阶段 二阶段提交 二阶段回滚 注册全局事务,获取 XID 执行业务操作(携带 XID) 解析 SQL,查询前镜像 执行业务 SQL 查询后镜像,生成 undo log 申请全局锁 全局锁获取成功 提交本地事务 注册分支事务 全局提交 提交分支事务 删除 undo log 释放全局锁 全局回滚 回滚分支事务 根据 undo log 生成反向 SQL 执行回滚 释放全局锁
TCC 模式
TCC 模式将业务逻辑拆分为 Try(资源预留)、Confirm(确认执行)、Cancel(取消执行)三个阶段,由业务代码手动控制资源的冻结与释放。
接入方式:
java
@LocalTCC
public interface InventoryTccAction {
@TwoPhaseBusinessAction(name = "deductInventory", commitMethod = "commit", rollbackMethod = "rollback")
boolean prepareDeduct(@BusinessActionContextParameter(paramName = "productId") String productId,
@BusinessActionContextParameter(paramName = "quantity") int quantity);
boolean commit(BusinessActionContext context);
boolean rollback(BusinessActionContext context);
}
@Service
public class InventoryTccActionImpl implements InventoryTccAction {
@Override
public boolean prepareDeduct(String productId, int quantity) {
// Try:冻结库存
// SQL: UPDATE inventory SET available_stock = available_stock - ?, frozen_stock = frozen_stock + ? WHERE product_id = ? AND available_stock >= ?
inventoryRepository.freeze(productId, quantity);
return true;
}
@Override
public boolean commit(BusinessActionContext context) {
// Confirm:扣减冻结库存
// SQL: UPDATE inventory SET frozen_stock = frozen_stock - ? WHERE product_id = ?
String productId = context.getActionContext("productId").toString();
int quantity = Integer.parseInt(context.getActionContext("quantity").toString());
inventoryRepository.deductFrozen(productId, quantity);
return true;
}
@Override
public boolean rollback(BusinessActionContext context) {
// Cancel:释放冻结库存
// SQL: UPDATE inventory SET available_stock = available_stock + ?, frozen_stock = frozen_stock - ? WHERE product_id = ?
String productId = context.getActionContext("productId").toString();
int quantity = Integer.parseInt(context.getActionContext("quantity").toString());
inventoryRepository.unfreeze(productId, quantity);
return true;
}
}
运行机制:
数据库 TCC 参与者 事务协调者 事务管理器 数据库 TCC 参与者 事务协调者 事务管理器 #mermaid-svg-1Wlf5cfiy7p2SoJ8{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-1Wlf5cfiy7p2SoJ8 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-1Wlf5cfiy7p2SoJ8 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-1Wlf5cfiy7p2SoJ8 .error-icon{fill:#552222;}#mermaid-svg-1Wlf5cfiy7p2SoJ8 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-1Wlf5cfiy7p2SoJ8 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-1Wlf5cfiy7p2SoJ8 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-1Wlf5cfiy7p2SoJ8 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-1Wlf5cfiy7p2SoJ8 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-1Wlf5cfiy7p2SoJ8 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-1Wlf5cfiy7p2SoJ8 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-1Wlf5cfiy7p2SoJ8 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-1Wlf5cfiy7p2SoJ8 .marker.cross{stroke:#333333;}#mermaid-svg-1Wlf5cfiy7p2SoJ8 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-1Wlf5cfiy7p2SoJ8 p{margin:0;}#mermaid-svg-1Wlf5cfiy7p2SoJ8 .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-1Wlf5cfiy7p2SoJ8 text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-1Wlf5cfiy7p2SoJ8 .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-1Wlf5cfiy7p2SoJ8 .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-1Wlf5cfiy7p2SoJ8 .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-1Wlf5cfiy7p2SoJ8 .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-1Wlf5cfiy7p2SoJ8 #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-1Wlf5cfiy7p2SoJ8 .sequenceNumber{fill:white;}#mermaid-svg-1Wlf5cfiy7p2SoJ8 #sequencenumber{fill:#333;}#mermaid-svg-1Wlf5cfiy7p2SoJ8 #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-1Wlf5cfiy7p2SoJ8 .messageText{fill:#333;stroke:none;}#mermaid-svg-1Wlf5cfiy7p2SoJ8 .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-1Wlf5cfiy7p2SoJ8 .labelText,#mermaid-svg-1Wlf5cfiy7p2SoJ8 .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-1Wlf5cfiy7p2SoJ8 .loopText,#mermaid-svg-1Wlf5cfiy7p2SoJ8 .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-1Wlf5cfiy7p2SoJ8 .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-1Wlf5cfiy7p2SoJ8 .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-1Wlf5cfiy7p2SoJ8 .noteText,#mermaid-svg-1Wlf5cfiy7p2SoJ8 .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-1Wlf5cfiy7p2SoJ8 .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-1Wlf5cfiy7p2SoJ8 .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-1Wlf5cfiy7p2SoJ8 .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-1Wlf5cfiy7p2SoJ8 .actorPopupMenu{position:absolute;}#mermaid-svg-1Wlf5cfiy7p2SoJ8 .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-1Wlf5cfiy7p2SoJ8 .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-1Wlf5cfiy7p2SoJ8 .actor-man circle,#mermaid-svg-1Wlf5cfiy7p2SoJ8 line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-1Wlf5cfiy7p2SoJ8 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Try 阶段 Confirm 阶段(全部成功) Cancel 阶段(任一失败) 注册全局事务,获取 XID 调用 prepare 方法(携带 XID) 冻结资源(预留库存、冻结金额等) 注册分支事务 返回执行结果 全局提交 调用 commit 方法 确认执行(扣减冻结资源) 提交成功 全局回滚 调用 rollback 方法 取消执行(释放冻结资源) 回滚成功
Saga 模式
Seata Saga 基于状态机引擎实现,通过 JSON 状态语言定义服务调用流程,由状态机引擎驱动执行,异常时反向执行补偿节点完成回滚。
状态语言定义示例:
json
{
"Name": "reduceInventoryAndBalance",
"Comment": "先扣库存再扣余额",
"StartState": "ReduceInventory",
"Version": "0.0.1",
"States": {
"ReduceInventory": {
"Type": "ServiceTask",
"ServiceName": "inventoryAction",
"ServiceMethod": "reduce",
"CompensateState": "CompensateReduceInventory",
"Next": "ReduceBalance",
"Input": ["$.[businessKey]", "$.[count]"],
"Status": {
"#root == true": "SU",
"#root == false": "FA",
"$Exception{java.lang.Throwable}": "UN"
}
},
"ReduceBalance": {
"Type": "ServiceTask",
"ServiceName": "balanceAction",
"ServiceMethod": "reduce",
"CompensateState": "CompensateReduceBalance",
"Next": "Succeed",
"Input": ["$.[businessKey]", "$.[amount]"],
"Status": {
"#root == true": "SU",
"#root == false": "FA",
"$Exception{java.lang.Throwable}": "UN"
},
"Catch": [
{ "Exceptions": ["java.lang.Throwable"], "Next": "CompensationTrigger" }
]
},
"CompensateReduceInventory": {
"Type": "ServiceTask",
"ServiceName": "inventoryAction",
"ServiceMethod": "compensateReduce",
"Input": ["$.[businessKey]"]
},
"CompensateReduceBalance": {
"Type": "ServiceTask",
"ServiceName": "balanceAction",
"ServiceMethod": "compensateReduce",
"Input": ["$.[businessKey]"]
},
"CompensationTrigger": {
"Type": "CompensationTrigger",
"Next": "Fail"
},
"Succeed": { "Type": "Succeed" },
"Fail": { "Type": "Fail", "ErrorCode": "PURCHASE_FAILED", "Message": "purchase failed" }
}
}
关键状态类型:
| 类型 | 说明 |
|---|---|
| ServiceTask | 调用服务任务,通过 CompensateState 指定补偿节点 |
| Choice | 单条件选择路由 |
| CompensationTrigger | 触发补偿流程,反向执行已成功的补偿节点 |
| Succeed | 状态机正常结束 |
| Fail | 状态机异常结束 |
| SubStateMachine | 调用子状态机 |
运行机制:
服务2(账户) 服务1(库存) 事务协调者 事务管理器 服务2(账户) 服务1(库存) 事务协调者 事务管理器 #mermaid-svg-AGNzkaByrnLK16jC{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-AGNzkaByrnLK16jC .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-AGNzkaByrnLK16jC .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-AGNzkaByrnLK16jC .error-icon{fill:#552222;}#mermaid-svg-AGNzkaByrnLK16jC .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-AGNzkaByrnLK16jC .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-AGNzkaByrnLK16jC .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-AGNzkaByrnLK16jC .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-AGNzkaByrnLK16jC .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-AGNzkaByrnLK16jC .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-AGNzkaByrnLK16jC .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-AGNzkaByrnLK16jC .marker{fill:#333333;stroke:#333333;}#mermaid-svg-AGNzkaByrnLK16jC .marker.cross{stroke:#333333;}#mermaid-svg-AGNzkaByrnLK16jC svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-AGNzkaByrnLK16jC p{margin:0;}#mermaid-svg-AGNzkaByrnLK16jC .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-AGNzkaByrnLK16jC text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-AGNzkaByrnLK16jC .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-AGNzkaByrnLK16jC .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-AGNzkaByrnLK16jC .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-AGNzkaByrnLK16jC .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-AGNzkaByrnLK16jC #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-AGNzkaByrnLK16jC .sequenceNumber{fill:white;}#mermaid-svg-AGNzkaByrnLK16jC #sequencenumber{fill:#333;}#mermaid-svg-AGNzkaByrnLK16jC #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-AGNzkaByrnLK16jC .messageText{fill:#333;stroke:none;}#mermaid-svg-AGNzkaByrnLK16jC .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-AGNzkaByrnLK16jC .labelText,#mermaid-svg-AGNzkaByrnLK16jC .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-AGNzkaByrnLK16jC .loopText,#mermaid-svg-AGNzkaByrnLK16jC .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-AGNzkaByrnLK16jC .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-AGNzkaByrnLK16jC .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-AGNzkaByrnLK16jC .noteText,#mermaid-svg-AGNzkaByrnLK16jC .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-AGNzkaByrnLK16jC .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-AGNzkaByrnLK16jC .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-AGNzkaByrnLK16jC .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-AGNzkaByrnLK16jC .actorPopupMenu{position:absolute;}#mermaid-svg-AGNzkaByrnLK16jC .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-AGNzkaByrnLK16jC .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-AGNzkaByrnLK16jC .actor-man circle,#mermaid-svg-AGNzkaByrnLK16jC line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-AGNzkaByrnLK16jC :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 正向流程(状态机引擎驱动) 补偿流程(CompensationTrigger 触发,逆序执行补偿节点) 注册全局事务 ReduceInventory(扣减库存) 成功(状态=SU) ReduceBalance(扣减余额) 异常(状态=FA) CompensateReduceInventory(恢复库存) 补偿成功
XA 模式
XA 模式基于 X/Open 组织定义的 XA 协议,通过数据库原生两阶段提交实现强一致性,一阶段 Prepare 不提交事务,二阶段统一 Commit 或 Rollback。
XA 事务与普通事务的区别:普通事务执行完立即提交,只能保证单库一致性;XA 事务 Prepare 后不提交,等待全局决策才统一提交或回滚,从而实现跨库原子性。
sql-- 普通事务:执行完直接提交,锁立即释放 START TRANSACTION; UPDATE account SET balance = balance - 100 WHERE id = 1; COMMIT; -- 一步到位 -- XA 事务:Prepare 后事务不提交,锁持续持有,等待全局决策 XA START 'xa_1'; UPDATE account SET balance = balance - 100 WHERE id = 1; XA END 'xa_1'; XA PREPARE 'xa_1'; -- 事务未提交,行锁持有 -- ... 等待协调者通知 ... XA COMMIT 'xa_1'; -- 收到提交指令后才真正提交
接入方式:
java
@Service
public class OrderService {
@GlobalTransactional
public void createOrder(Order order) {
// 与 AT 模式使用方式相同
// 区别在于数据源配置需支持 XA
orderRepository.save(order);
inventoryFeignClient.deduct(order.getProductId(), order.getQuantity());
}
}
数据源配置:
yaml
seata:
data-source-proxy-mode: XA
运行机制:
数据库 资源管理器 事务协调者 事务管理器 数据库 资源管理器 事务协调者 事务管理器 #mermaid-svg-xqEFtYyMufQPvoxs{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-xqEFtYyMufQPvoxs .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-xqEFtYyMufQPvoxs .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-xqEFtYyMufQPvoxs .error-icon{fill:#552222;}#mermaid-svg-xqEFtYyMufQPvoxs .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-xqEFtYyMufQPvoxs .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-xqEFtYyMufQPvoxs .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-xqEFtYyMufQPvoxs .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-xqEFtYyMufQPvoxs .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-xqEFtYyMufQPvoxs .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-xqEFtYyMufQPvoxs .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-xqEFtYyMufQPvoxs .marker{fill:#333333;stroke:#333333;}#mermaid-svg-xqEFtYyMufQPvoxs .marker.cross{stroke:#333333;}#mermaid-svg-xqEFtYyMufQPvoxs svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-xqEFtYyMufQPvoxs p{margin:0;}#mermaid-svg-xqEFtYyMufQPvoxs .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-xqEFtYyMufQPvoxs text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-xqEFtYyMufQPvoxs .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-xqEFtYyMufQPvoxs .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-xqEFtYyMufQPvoxs .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-xqEFtYyMufQPvoxs .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-xqEFtYyMufQPvoxs #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-xqEFtYyMufQPvoxs .sequenceNumber{fill:white;}#mermaid-svg-xqEFtYyMufQPvoxs #sequencenumber{fill:#333;}#mermaid-svg-xqEFtYyMufQPvoxs #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-xqEFtYyMufQPvoxs .messageText{fill:#333;stroke:none;}#mermaid-svg-xqEFtYyMufQPvoxs .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-xqEFtYyMufQPvoxs .labelText,#mermaid-svg-xqEFtYyMufQPvoxs .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-xqEFtYyMufQPvoxs .loopText,#mermaid-svg-xqEFtYyMufQPvoxs .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-xqEFtYyMufQPvoxs .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-xqEFtYyMufQPvoxs .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-xqEFtYyMufQPvoxs .noteText,#mermaid-svg-xqEFtYyMufQPvoxs .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-xqEFtYyMufQPvoxs .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-xqEFtYyMufQPvoxs .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-xqEFtYyMufQPvoxs .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-xqEFtYyMufQPvoxs .actorPopupMenu{position:absolute;}#mermaid-svg-xqEFtYyMufQPvoxs .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-xqEFtYyMufQPvoxs .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-xqEFtYyMufQPvoxs .actor-man circle,#mermaid-svg-xqEFtYyMufQPvoxs line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-xqEFtYyMufQPvoxs :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 阶段一:Prepare 阶段二:Commit 阶段二:Rollback 注册全局事务 执行业务操作 开启 XA 事务 执行 SQL(不提交) 注册分支事务 返回执行结果 全局提交 XA Commit 提交 XA 事务 提交成功 全局回滚 XA Rollback 回滚 XA 事务 回滚成功
Seata 控制台
Seata 提供了 Web 控制台,可以查看全局事务状态、分支事务详情、事务日志等信息。
配置方式(Seata Server 服务端):
yaml
seata:
server:
console:
enabled: true
port: 7091
访问地址:http://localhost:7091
主要功能:
| 功能 | 说明 |
|---|---|
| 事务列表 | 查看全局事务状态(进行中、已提交、已回滚) |
| 事务详情 | 查看分支事务信息、执行状态 |
| 全局锁 | 查看当前持有的全局锁 |
| 事务日志 | 排查事务执行过程 |
Prometheus 指标监控:Seata 支持 Prometheus 指标导出,配合 Grafana 可实现更丰富的可视化监控。此配置在 Seata Server(服务端)的 application.yml 中,客户端无需配置。
yaml
seata:
metrics:
enabled: true
registry-type: compact
exporter-list: prometheus
exporter-prometheus-port: 9898
访问 http://localhost:9898/metrics 可获取 Prometheus 格式的指标数据。