在分布式時代,分庫分表是很常見的,微服務系統(tǒng)中,各個系統(tǒng)通常使用獨立的數(shù)據(jù)庫,所以,事務很難靠數(shù)據(jù)庫本身保證,只能靠業(yè)務系統(tǒng)來解決。
例如支付寶中的余額寶、花唄,具體不清楚,但猜測應該就是2個服務,不是同一個數(shù)據(jù)庫,我們還花唄的時候通常都是從余額寶中扣除的,這就是分布式事務,一個系統(tǒng)中扣減錢,一個系統(tǒng)中增加錢。
下面我們分析下最終一致性的實現(xiàn)方案,最終一致性通常都是使用消息中間件來實現(xiàn)的,系統(tǒng)結(jié)構(gòu)如下:

解釋下上圖的流程:用戶向系統(tǒng)A發(fā)起轉(zhuǎn)賬請求,A先在自己的數(shù)據(jù)庫中扣錢,然后通過消息中間件告訴B應該加錢,B收到后在自己的數(shù)據(jù)庫中加錢。
這里有個關(guān)鍵問題,A更新數(shù)據(jù)庫和給消息中間件發(fā)消息是2個操作,如下兩個場景怎么處理:
- 先更新數(shù)據(jù)庫,成功了,但發(fā)送消息失敗了,重發(fā)多次還是失敗
- 先發(fā)消息,成功了,但數(shù)據(jù)庫更新失敗,消息撤不回來了
就是因為這2個操作不是原子性的,先執(zhí)行哪一步都會有問題。
那我們就嘗試著把更新數(shù)據(jù)庫和給消息中間件發(fā)消息放到一個事務中可以嗎?這樣不就能實現(xiàn)原子性操作了嗎?
實際上這樣時存在問題的,例如:
- 如果消息發(fā)送失敗,具體問題出在哪兒?是消息中間件根本就沒收到消息,還是收到消息后response時出錯了?如果是根本沒收到還好一點,如果是收到了但響應失敗就麻煩了,導致A數(shù)據(jù)庫回滾,沒有扣錢,但B收到消息了,加錢了。
- 如果發(fā)消息時網(wǎng)絡延遲很高怎么辦,數(shù)據(jù)庫事務一直被拖著,性能差,風險高。
故而放入一個事務中這種方法是不可取的。
為了保證原子性,可以變通一下,添加一個消息表,A不直接往消息中間件中發(fā)消息,而是把消息寫入消息表,然后通過一個后臺程序不斷的把消息寫入消息中間件。

這個后臺程序源源不斷的把消息表中的消息發(fā)到消息中間件,如果失敗就重試,可以保證:
- 消息不會丟失
- 順序不亂
但此時會有消息重復的情況,因為消息發(fā)送失敗可能是寫入失敗,也可能是寫入成功但響應失敗,所以消息可能會重復發(fā)送,這個問題需要系統(tǒng)B來處理。
系統(tǒng)B需要考慮2個問題:
- 消息丟失
- B從消息中間件中拿到消息,還沒處理完就宕機了,這條消息怎么辦?
此時就需要通過ACK機制處理,消費成功的發(fā)送ACK,對于沒有ACK的消息,消息中間件會再次推送。
但ACK機制也存在消息重復的情況,比如B已經(jīng)處理完一條消息,發(fā)ACK時失敗了,那么這條消息就還會被推過來。
或者像上面說到的后臺程序發(fā)消息時可能重復。
對于重復消息問題,可以加一個判重表,記錄處理成功的消息,每次收到消息時,先通過判重表判斷一下,如果重復了就不處理,實現(xiàn)冪等性。
最終,整體的結(jié)構(gòu)就變?yōu)槿缦滤荆?/p>

以上就是通過最終一致性解決分布式事務問題的基本思路,A 保證消息不丟,B 保證消息不漏、冪等。