一、前言
在單體應用中,事務一般由關系型數據庫本身來保證,通過 ACID 特性實現數據一致性。但隨著微服務架構的普及,應用被拆分為多個獨立服務,數據可能分散在不同數據庫、不同存儲引擎中,傳統的單機事務無法再覆蓋。
這就引出了 分布式事務 的問題:如何在多服務、多數據庫場景下,仍然保證數據一致性?
本文將結合 Spring Boot 實際開發,對常見的幾種分布式事務方案進行解析:
XA 方案(兩階段提交,強一致性)
Seata(柔性事務,常用于阿里系微服務)
本地消息表 + 最終一致性(高可用場景的常見落地方案)
同時會總結它們的 優缺點、常見坑點與適用場景。
二、分布式事務常見場景
在 Spring Boot 開發中,以下場景經常涉及分布式事務:
訂單系統:創建訂單 → 扣減庫存 → 扣減余額
支付系統:支付成功 → 修改訂單狀態 → 發送消息通知 → 更新積分
營銷系統:用戶下單 → 觸發優惠券核銷 → 更新活動數據
這些流程往往跨越多個服務和數據庫,如果其中一步失敗,就可能導致數據不一致,例如:
庫存已扣減,但訂單未生成
訂單已支付,但未發貨
優惠券已核銷,但活動未更新
因此需要合理的分布式事務方案來保證一致性。
三、XA 方案(兩階段提交)
1. 原理
XA 是 分布式事務標準協議,基于兩階段提交(2PC,Two Phase Commit):
階段一:事務協調者向所有數據庫發送
prepare
,各數據庫執行但不提交,返回可提交狀態階段二:如果所有數據庫都返回成功,則發送
commit
,否則發送rollback
2. 在 Spring Boot 中的實現
常見實現方式是 Atomikos、Narayana 等第三方事務管理器,也可以結合 JTA 來管理。
示例配置(Atomikos):
spring:jta:enabled:?trueatomikos:properties:service:?com.atomikos.icatch.standalone.UserTransactionServiceFactory
3. 優缺點
? 優點
強一致性保證
對開發透明,業務代碼幾乎不用改
? 缺點
性能差:兩階段提交會增加鎖時間,導致吞吐量下降
擴展性差:跨多數據源時容易成為瓶頸
單點風險:協調者掛掉可能導致事務懸掛
4. 適用場景
適合金融級場景(如銀行轉賬)——必須保證強一致性,但對性能要求相對次要。
四、Seata(柔性事務)
1. 原理
Seata 是阿里開源的分布式事務解決方案,支持 AT 模式、TCC 模式、Saga 模式:
AT 模式:自動代理數據源,類似本地事務 + Undo Log 回滾,開發成本低
TCC 模式:需要業務實現 Try/Confirm/Cancel 三個接口,粒度更細,性能更高
Saga 模式:長事務補償,適合跨服務調用鏈較長的業務
2. 在 Spring Boot 中集成
依賴:
<dependency><groupId>io.seata</groupId><artifactId>seata-spring-boot-starter</artifactId><version>1.7.1</version>
</dependency>
配置:
seata:enabled:?trueapplication-id:?order-servicetx-service-group:?my_tx_groupservice:vgroup-mapping:my_tx_group:?default
使用:
@GlobalTransactional
public?void?createOrder(Order?order)?{orderDao.save(order);inventoryService.deduct(order.getId());
}
3. 優缺點
? 優點
透明代理數據庫,AT 模式對開發友好
多種事務模式可選,適配不同業務場景
社區活躍,生態完善
? 缺點
需要額外部署 Seata Server
Undo Log 占用空間,長事務性能下降
某些復雜 SQL(批量更新/存儲過程)支持有限
4. 適用場景
電商下單、庫存扣減等典型 高并發場景,對性能要求較高,能接受短時間的不一致。
五、本地消息表(最終一致性)
1. 原理
核心思想是 業務數據 + 消息發送放在同一個本地事務中,通過消息隊列來保證最終一致性:
業務服務在本地事務中寫入業務表 + 消息表
定時任務掃描消息表,發送消息到 MQ
消費者消費 MQ 消息,執行后續邏輯
成功后更新消息表狀態
2. 在 Spring Boot 中實現
數據庫表:
CREATE?TABLE?t_outbox_message?(id?BIGINT?PRIMARY?KEY?AUTO_INCREMENT,content?TEXT?NOT?NULL,status?TINYINT?DEFAULT?0,create_time?TIMESTAMP?DEFAULT?CURRENT_TIMESTAMP
);
業務代碼:
@Transactional
public?void?createOrder(Order?order)?{orderDao.insert(order);outboxDao.insert(new?OutboxMessage(order));
}
定時任務發送消息:
@Scheduled(fixedRate?=?5000)
public?void?sendPendingMessages()?{List<OutboxMessage>?messages?=?outboxDao.findPending();for?(OutboxMessage?msg?:?messages)?{mqProducer.send(msg);outboxDao.markSent(msg.getId());}
}
3. 優缺點
? 優點
無需額外中間件,簡單可靠
高可用,適合最終一致性場景
? 缺點
開發成本較高,需要寫消息表邏輯
延遲一致性(可能有幾秒的延遲)
消息補償、冪等處理邏輯復雜
4. 適用場景
訂單系統、積分系統、日志收集等對 最終一致性容忍度高 的業務。
六、三種方案對比
方案 | 一致性 | 性能 | 開發成本 | 典型場景 |
---|---|---|---|---|
XA | 強一致 | 低 | 低 | 銀行轉賬、核心金融業務 |
Seata | 最終一致(AT/TCC) | 中高 | 中 | 電商下單、庫存、支付 |
本地消息表 | 最終一致 | 高 | 高 | 營銷、積分、日志系統 |
七、常見坑點總結
XA 事務懸掛:協調者掛掉可能導致數據庫鎖未釋放
Seata Undo Log 膨脹:要定期清理,否則影響性能
本地消息表補償失敗:要考慮消息冪等、死信隊列機制
八、結語
分布式事務沒有“銀彈”,不同方案適合的場景完全不同:
金融強一致 → 優先考慮 XA
電商高并發 → 選擇 Seata AT/TCC
最終一致性即可 → 本地消息表是首選
在 Spring Boot 實踐中,建議結合 業務場景 + 性能要求 + 容錯能力 來選擇合適的方案,而不是盲目套用。