在系統變的複雜後,分佈式、微服務等架構技術,就要考慮到應用在系統中了。尤爲數據量大了後,就須要對數據庫進行拆分。數據庫
如:註冊的用戶數據,量大了後,就須要考慮分庫分表架構
一旦數據庫進行了分拆,那就出現不少頭疼的問題,其中之一就是事務問題。那咱們就來看看問題是怎麼出現的?app
先來上個圖異步
進行數據拆分後,就相似上面的架構。分佈式
上圖中咱們就拿用戶的數據進行舉例,用戶量一旦幾千萬時,就須要進行分庫分表;上圖就分了3個庫,每一個庫都保證了高可用。微服務
這樣的架構設計,會遇到事務問題,咱們來看看具體的業務場景:用戶A轉帳100元給用戶B,這個業務比較簡單,咱們來分析一下里面具體的步驟:優化
一、用戶A的帳戶先扣除100元
二、再把用戶B的帳戶加100元spa
邏輯很簡單,上僞代碼架構設計
代碼也是比較清晰的,感受沒有什麼問題,那咱們來分析一下問題在哪?設計
咱們看到在轉帳業務中,有兩步,一個是操做用戶A扣錢,一個是操做用戶B加錢
若是在同一個數據庫中進行,能夠保證這兩步操做,要麼同時成功,要麼同時不成功。這樣就保證了轉帳的數據一致性。
可是若是用戶A的數據在集羣A中,用戶B在集羣B中呢?由於他們不在同一個事務中;如用戶A扣款成功,但用戶B加錢失敗了;那就坑了,數據不完整了。
相似這種問題在微服務架構會更多,由於各個服務都是獨立的模塊,都是遠程調用,都無法在同一個事務中,都會遇到事務問題。
那怎麼解決?網上有一些方案,如:兩階段提交,TCC等,還有經常使用就是最終一致性方案。就給你們介紹一下如何利用消息中間件去解決。那咱們就把方案調整一下,加入消息中間件,看看如何優化。
上圖就是利用消息中間件的方式,把扣款業務和加錢業務異步化,扣款成功後,發送「扣款成功消息」到消息中間件;加錢業務訂閱「扣款成功消息」,再對用戶B加錢
系統怎麼知道給用戶B加錢呢?是消息體裏面包含了源帳戶和目標帳戶ID,以及錢數
這個時候也許小夥伴們會問,應該也有問題吧:場景一:先扣款後發消息
先扣款再發送消息,萬一發送消息失敗了,那用戶B就無法加錢
那把順序調整一下場景二:先發消息,後扣款
扣款成功消息發送成功,但用戶A扣款失敗,可加錢業務訂閱到了消息,用戶B加了錢
你們應該發現了問題所在,也就是無法保證扣款和發送消息,同時成功,或同時失敗;致使數據不一致。
由於上面的問題,RocketMq消息中間件把消息分爲兩個階段:Prepared階段和確認階段Prepared階段(預備階段)
該階段主要發一個消息到rocketmq,但該消息只儲存在commitlog中,但consumeQueue中不可見,也就是消費端(訂閱端)沒法看到此消息。
commit/rollback階段(確認階段)
該階段主要是把prepared消息保存到consumeQueue中,即讓消費端能夠看到此消息,也就是能夠消費此消息。
咱們用圖來講明下:
整個流程:
一、在扣款以前,先發送預備消息二、發送預備消息成功後,執行本地扣款事務三、扣款成功後,再發送確認消息四、消息端(加錢業務)能夠看到確認消息,消費此消息,進行加錢
確認消息說明
注意:上面的確認消息能夠爲commit消息,能夠被訂閱者消費;也能夠是Rollback消息,即執行本地扣款事務失敗後,提交rollback消息,即刪除那個預備消息,訂閱者沒法消費
咱們來分析一下異常場景:
異常1:若是發送預備消息失敗,下面的流程不會走下去;這個是正常的異常2:若是發送預備消息成功,但執行本地事務失敗;這個也沒有問題,由於此預備消息不會被消費端訂閱到,消費端不會執行業務。異常3:若是發送預備消息成功,執行本地事務成功,但發送確認消息失敗;這個就有問題了,由於用戶A扣款成功了,但加錢業務沒有訂閱到確認消息,沒法加錢。這裏出現了數據不一致。
那RocketMq是怎麼解決的呢?
RocketMq如何解決上面的問題,核心思路就是【狀態回查】,也就是RocketMq會定時遍歷commitlog中的預備消息。
由於預備消息最終確定會變爲commit消息或Rollback消息,因此遍歷預備消息去回查本地業務的執行狀態,若是發現本地業務沒有執行成功就rollBack,若是執行成功就發送commit消息。
上面的異常3,發送預備消息成功,本地扣款事務成功,但發送確認消息失敗;由於RocketMq會進行回查預備消息,在回查後發現業務已經扣款成功了,就補發「發送commit確認消息」;這樣加錢業務就能夠訂閱此消息了。
這個思路其實把異常2也解決了,由於本地事務沒有執行成功,RocketMQ回查業務,發現沒有執行成功,就會發送RollBack確認消息,把消息進行刪除。
小夥伴們在回查業務中,如何判斷本地事務是否執行成功?
若是本地事務執行了不少張表,那是否是咱們要把那些表都要進行判斷是否執行成功呢?這樣是否是太麻煩了,並且和業務很耦合。
有沒有更好的方式呢?就是設計一張Transaction表,將業務表和Transaction綁定在同一個本地事務中,若是扣款本地事務成功時,Transaction中應當已經記錄該TransactionId的狀態爲「已完成」。當RocketMq回查時,只須要檢查對應的TransactionId的狀態是不是「已完成」就好,而不用關心具體的業務數據。
原文地址: