天天看點

分布式事務方案:最終一緻性

在分布式時代,分庫分表是很常見的,微服務系統中,各個系統通常使用獨立的資料庫,是以,事務很難靠資料庫本身保證,隻能靠業務系統來解決。

例如支付寶中的餘額寶、花呗,具體不清楚,但猜測應該就是2個服務,不是同一個資料庫,我們還花呗的時候通常都是從餘額寶中扣除的,這就是分布式事務,一個系統中扣減錢,一個系統中增加錢。

下面我們分析下最終一緻性的實作方案,最終一緻性通常都是使用消息中間件來實作的,系統結構如下:

分布式事務方案:最終一緻性

使用者向系統A發起轉賬請求,A先在自己的資料庫中扣錢,然後通過消息中間件告訴B應該加錢,B收到後在自己的資料庫中加錢。

這裡有個關鍵問題,A更新資料庫和給消息中間件發消息是2個操作,如下兩個場景怎麼處理:

先更新資料庫,成功了,但發送消息失敗了,重發多次還是失敗

先發消息,成功了,但資料庫更新失敗,消息撤不回來了

都是因為這2個操作不是原子的,發做誰都有問題。

那看下這樣做是否可以,就是把更新資料庫和給消息中間件發消息放到一個事務中,這樣不就原子了嗎?

有問題,例如:

如果消息發送失敗,具體問題出在哪兒?是消息中間件根本就沒收到消息,還是收到消息後response時出錯了?如果是根本沒收到還好一點,如果是收到了但響應失敗就麻煩了,導緻A資料庫復原,沒有扣錢,但B收到消息了,加錢了。

如果發消息時網絡延遲很高怎麼辦,資料庫事務一直被拖着,性能差,風險高。

是以,放入一個事務中這種方法是不可取的。

為了保證原子性,可以變通一下,添加一個消息表,A不直接往消息中間件中發消息,而是把消息寫入消息表,然後通過一個背景程式不斷的把消息寫入消息中間件。

分布式事務方案:最終一緻性

這個背景程式源源不斷的把消息表中的消息發到消息中間件,如果失敗就重試,可以保證:

消息不會丢失

順序不亂

但會有消息重複的情況,因為消息發送失敗可能是寫入失敗,也可能是寫入成功但響應失敗,是以消息可能會重複,這個問題需要系統B來處理。

系統B需要考慮2個問題:

消息丢失

B從消息中間件中拿到消息,還沒處理完就當機了,這條消息怎麼辦?

需要通過ACK機制處理,消費成功的發送ACK,對于沒有ACK的消息,消息中間件會再次推送。

消息重複

ACK機制也存在消息重複的情況,比如B已經處理完一條消息,發ACK時失敗了,那麼這條消息就還會被推過來。

還有就是上面說的背景程式發消息時可能重複。

對于重複消息問題,可以加一個判重表,記錄處理成功的消息,每次收到消息時,先通過判重表判斷一下,如果重複了就不處理,實作幂等性。

這樣,整體結構就變為:

分布式事務方案:最終一緻性

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