寫在前面
寫這篇文章的背景是有個跟我關系不錯的小伙伴去某大型互聯網公司面試,面試官問了他關於分布式事務的問題,不巧的是他確實對分布式事務掌握的不是很深入,面試的結果挺遺憾的。不過,這位小伙伴還是挺樂觀的,讓我寫寫關於【分布式事務】的系列文章,他想提升自己關於分布式事務的短板,那我就寫一個【分布式事務】專題吧,專題的內容計划是從原理、框架源碼到企業級實現,這篇文章也算是整個專題的開篇吧。希望能夠為小伙伴們帶來實質性的幫助。
本地事務
本地事務流程
在介紹分布式事務之前,我們先來看看本地事務。首先,我們先來一張圖。
由上圖,我們可以看出,本地事務由資源管理器(比如DBMS,數據庫管理系統)在本地進行管理。
本地事務的優缺點
本地事務具備相應的優點,也有其不足。
優點:
- 支持嚴格的ACID屬性。
- 可靠,事務實現的效率高(只是在本地操作)。
- 可以只在RM(資源管理器)中操作事務。
- 編程模型簡單。
缺點:
- 缺乏分布式事務的處理能力。
- 數據隔離的最小單元由RM(資源管理器決定),開發人員無法決定數據隔離的最小單元。比如:數據庫中的一條記錄等。
ACID屬性
說起事務,我們不得不提的就是事務的ACID屬性。
- A(Atomic):原子性,構成事務的所有操作,要么都執行完成,要么全部不執行,不可能出現部分成功部分失
敗的情況。 - C(Consistency):一致性,在事務執行前后,數據庫的一致性約束沒有被破壞。比如:張三向李四轉100元,
轉賬前和轉賬后的數據的正確狀態叫作一致性,如果出現張三轉出100元,李四賬戶沒有增加100元這就出現了數
據錯誤,就沒有達到一致性。 - I(Isolation):隔離性,數據庫中的事務一般都是並發的,隔離性是指並發的兩個事務的執行互不干擾,一個事
務不能看到其他事務運行過程的中間狀態。通過配置事務隔離級別可以避臟讀、重復讀等問題。 - D(Durability):持久性,事務完成之后,該事務對數據的更改會被持久化到數據庫,且不會被回滾。
分布式事務
隨着業務的快速發展,網站系統往往由單體架構逐漸演變為分布式、微服務架構,而對於數據庫則由單機數據庫架構向分布式數據庫架構轉變。此時,我們會將一個大的應用系統拆分為多個可以獨立部署的應用服務,需要各個服務之間進行遠程協作才能完成事務操作。
我們可以使用下圖來表示剛開始我們系統的單體架構。
上圖中,我們將同一個項目中的不同模塊組織成不同的包來進行管理,所有的程序代碼仍然是放在同一個項目中。
后續由於業務的發展,我們將其擴展為分布式、微服務架構。此時,我們將一個大的項目拆分為一個個小的可以獨立部署的微服務,每個微服務都有自己的數據庫,如下所示。
又比如,在我們的程序中,經常會在同一個事務中執行類似如下的代碼來完成我們的需求。
@Transactional(rollbackFor = Exception.class)
public void submitOrder() {
orderDao.update(); // 更新訂單信息
accountService.update(); // 修改資金賬戶的金額
pointService.update(); // 修改積分
accountingService.insert(); // 插入交易流水
merchantNotifyService.notify(); // 通知支付結果
}
上述代碼中的業務,僅僅在submitOrder()方法上添加了一個@Transactional注解,這能夠在分布式場景下避免分布式事務的問題嗎?很顯然是不行的。
如果上述代碼所對應的:訂單信息、資金賬戶信息、積分信息、交易流水等信息分別存儲在不同的數據里,而支付完成后,通知的目標系統的數據同樣是存儲在不同的數據庫中。此時就會產生分布式事務問題。
分布式事務產生的場景
跨JVM進程
當我們將單體項目拆分為分布式、微服務項目之后,各個服務之間通過遠程REST或者RPC調用來協同完成業務操作。典型的場景就是:商城系統中的訂單微服務和庫存微服務,用戶在下單時會訪問訂單微服務,訂單微服務在生成訂單記錄時,會調用庫存微服務來扣減庫存。各個微服務是部署在不同的JVM進程中的,此時,就會產生因跨JVM進程而導致的分布式事務問題。
跨數據庫實例
單體系統訪問多個數據庫實例,也就是跨數據源訪問時會產生分布式事務。例如,我們的系統中的訂單數據庫和交易數據庫是放在不同的數據庫實例中,當用戶發起退款時,會同時操作用戶的訂單數據庫和交易數據庫,在交易數據庫中執行退款操作,在訂單數據庫中將訂單的狀態變更為已退款。由於數據分布在不同的數據庫實例,需要通過不同的數據庫連接會話來操作數據庫中的數據,此時,就產生了分布式事務。
多服務單數據庫
多個微服務訪問同一個數據庫。例如,訂單微服務和庫存微服務訪問同一個數據庫也會產生分布式事務,原因是:多個微服務訪問同一個數據庫,本質上也是通過不同的數據庫會話來操作數據庫,此時就會產生分布式事務。
注意:跨數據庫實例場景和多服務單數據庫場景,本質上都是因為會產生不同的數據庫會話來操作數據庫中的數據,進而產生分布式事務。這兩種場景是大家比較容易忽略的。
分布式事務解決方案
知道了分布式事務產生的場景后,接下來,我們就聊聊分布式事務具體有哪些解決方案。
2PC方案
2PC即兩階段提交協議,是將整個事務流程分為兩個階段,准備階段(Prepare phase)、提交階段(commit
phase),2是指兩個階段,P是指准備階段,C是指提交階段。
這里,我們用MySQL數據庫舉例,MySQL數據庫支持兩階段提交協議,可以分為成功和失敗兩種情況。
成功情況
失敗情況
具體流程如下:
准備階段(Prepare phase): 事務管理器給每個參與者發送Prepare消息,每個數據庫參與者在本地執行事
務,並寫本地的Undo/Redo日志,此時事務沒有提交。
(Undo日志是記錄修改前的數據,用於數據庫回滾,Redo日志是記錄修改后的數據,用於提交事務后寫入數
據文件)
提交階段(commit phase): 如果事務管理器收到了參與者的執行失敗或者超時消息時,直接給每個參與者
發送回滾(Rollback)消息;否則,發送提交(Commit)消息;參與者根據事務管理器的指令執行提交或者回滾操
作,並釋放事務處理過程中使用的鎖資源。
使用2PC方案時,需要注意的是:必須在最后階段釋放鎖資源。
可靠消息最終一致性方案
可靠消息最終一致性方案是指當事務發起方執行完成本地事務后並發出一條消息,事務參與方(消息消費者)一定能
夠接收消息並處理事務成功,此方案強調的是只要消息發給事務參與方最終事務要達到一致。
事務發起方(消息生產方)將消息發給消息中間件,事務參與方從消息中間件接收消息,事務發起方和消息中間件
之間,事務參與方(消息消費方)和消息中間件之間都是通過網絡通信,由於網絡通信的不確定性會導致分布式事
務問題。 所以,我們在具體方案中會引入消息確認服務和消息恢復服務。
使用可靠消息最終一致性方案時需要注意幾個問題:
- 本地事務與消息發送的原子性問題。
- 事務參與方接收消息的可靠性問題。
- 消息重復消費的問題(需要實現冪等)。
TCC方案
TCC分為三個階段:
- Try 階段 是做業務檢查(一致性)及資源預留(隔離),此階段僅是一個初步操作,它和后續的Confirm 一起才能
真正構成一個完整的業務邏輯。 - Confirm 階段 是做確認提交,Try階段所有分支事務執行成功后開始執行 Confirm。通常情況下,采用TCC則
認為 Confirm階段是不會出錯的。即:只要Try成功,Confirm一定成功。若Confirm階段真的出錯了,需引
入重試機制或人工處理。 - Cancel 階段 是在業務執行錯誤需要回滾的狀態下執行分支事務的業務取消,預留資源釋放。通常情況下,采
用TCC則認為Cancel階段也是一定成功的。若Cancel階段真的出錯了,需引入重試機制或人工處理。
使用TCC分布式解決方案時需要注意空回滾、冪等、懸掛等問題。
最大努力通知型方案
此種方案主要用於多個不同系統之前保證數據的最終一致性,大體如下圖所示。
使用最大努力通知型方案需要注意冪等和數據的回查操作。
好了,今天就到這兒吧,后續我們會針對每種分布式事務解決方案進行具體介紹,下期見!!
重磅福利
微信搜一搜【冰河技術】微信公眾號,關注這個有深度的程序員,每天閱讀超硬核技術干貨,公眾號內回復【PDF】有我准備的一線大廠面試資料和我原創的超硬核PDF技術文檔,以及我為大家精心准備的多套簡歷模板(不斷更新中),希望大家都能找到心儀的工作,學習是一條時而郁郁寡歡,時而開懷大笑的路,加油。如果你通過努力成功進入到了心儀的公司,一定不要懈怠放松,職場成長和新技術學習一樣,不進則退。如果有幸我們江湖再見!
另外,我開源的各個PDF,后續我都會持續更新和維護,感謝大家長期以來對冰河的支持!!