分佈式事務瞭解嗎?你們的多個服務間數據一致性解決方案是什麼?

前言

看標題就知道,這個又是個在面試中被問到的問題。這個問題其實是在我上次換工作的時候面試被問到過幾次,之前也沒在意過,覺得這個東西可能比較深奧,我直接說不理解吧。但是隨着Java開發這個行業越來越卷,這次換工作一定要做好充足的準備。把之前落下的坑都填好,再出去受虐(面試)。

什麼是分佈式事務

我們都知道本地事務是有四個特性的:原子性(atomicity)、一致性(consistency)、隔離性(isolation)和持久性(durability)。

本地事務的ACID一般都是靠關係型數據庫來完成的,非關係型數據庫一般也可以考數據庫來實現,redis這種不能回滾的弱事務除外。

但是在分佈式系統中一次操作由多個服務協同完成,這種一次事務操作涉及多個系統通過網絡協同完成的過程稱爲分佈式事務。

多個服務之間的可以是同一個數據庫,也可以是多個數據庫。

另外如果是在同一個服務中,使用了多個數據源連接了不同的數據庫,當一個事務需要操作多個數據源的時候也是屬於分佈式事務。

CAP

CAP理論是目前分佈式系統中的處理分佈式事務的理論基礎。主要是在目前分佈式系統中都無法同時滿足如下三個屬性:

  • 一致性(Consistency):多個服務的數據需要保持在同一時刻的數據一致性。

  • 可用性(Availability):指單個系統提供的服務需要一直保持可用狀態,對於每一個請求,都能及時的響應,超時或不無響應則認爲系統不可用。

  • 分區容錯性(Partition Tolerance):分佈式系統再遇到任何網絡分區故障時,仍能夠保證對外提供滿足一致性和可用性的服務,除非整個網絡環境發生故障。

在分佈式系統中。一個服務最多隻能保證上面其中任何兩個屬性,並不能同時滿足。

在保證分區容錯性的時候並不能保證數據的一致性和服務的可用性。如果要提高服務的可用性,就要增加多個結點,雖然節點越多可用性越好,但是數據一致性就會越差。

這樣在分佈式系統設計中,同時滿足“一致性”、“可用性”和“分區容錯性”幾乎是不可能的。

CAP應用組合

  • 【CA】放棄分區容錯性:放棄分區容錯性,也要保證網絡可用,最簡單的做法就是將所有數據都放在同一個節點上,雖然這樣無法保證100%系統不出錯,但至少不會出現由於網絡分區帶來的負面影響。

  • 【CP】放棄可用性:放棄可用性,是指一但遇到網絡分區或其他系統問題時,那受到影響的服務需要等待一定時間,應用等待期間系統無法對外提供正常服務。即短時間內不可用。

  • 【AP】放棄一致性:所謂的放棄一致性,其實並不是完全的不需要一致性,而是放棄強一致性,保證了數據最終一致性。

BASE理論

在分佈式系統中,往往追求的是可比性,一般重要程度比一致性高。所以就又出現了另一個理論,就是BASE理論,是對CAP理論的一個擴充。

  • Base Availability(基本可用);

  • Soft state(軟狀態);

  • Eventually consistent(最終一致性);

BASE理論是對CAP理論中的一致性和可用性的一種權衡的結果,主要思想就是:無法做到強一致,但每個應用可以根據自身業務的特點,採用適當的方式來使系統達到最終的一致性。

分佈式事務的解決方案

分佈式事務的解決方案,目前市面上是有幾類的方式的。

  • 2PC(兩階段提交)、3PC(三階段提交);

  • TCC方案;

  • 本地消息表;

  • 可靠消息最終一致性方案;

  • 最大努力通知方案;

2PC(兩階段提交)

兩階段提交主要是將提交事務和執行事務分爲了兩步。

第一階段:事務協調器通知參與者準備提交事務,參與者準備成功之後向協調者返回成功,若有一個參與者返回的是準備不成功,那麼事務執行失敗。

第二階段:事務協調器根據各個參與者的第一階段的返回結果,發起最終提交事務的請求,若有一個參與者提交失敗,則所有參與者都執行回滾,事務執行失敗。

這種屬於強一致性的實現,因爲在多個服務間的事務執行過程中,有可能第一個服務的事務已經提交了,第二服務提交失敗了,雖然說可以讓第二個服務的事務回滾但是第一個服務有可能事務已經執行完成了,無法進行回滾了。所以多數情況下是將第二個服務其實是進行重試提交,然後直到重試成功爲止,重試到一定次數後仍沒有成功就需要預警出來人工干預了。

兩階段提交是一種儘量保證強一致性的分佈式事務,因此它是同步阻塞的,而同步阻塞就導致長時間鎖定資源問題,所以總體而言效率低,並且存在單點故障的問題(有可能協調者掛,也有可能協調者和其中的某個服務掛了,協調者就不清楚掛了的那個服務到底是執行沒執行事務了),所以在極端情況下還是存在數據不一致的風險。

另外就是2PC其實更適合這種多數據源的情況,並且數據源都是關係型數據庫。這樣可以讓兩個數據庫中的事務都同時處於prepare階段,提交的時候兩個數據庫中的事務一起commit。

3PC(三階段提交)

3PC其實就是比較2PC多了一個預提交階段,3PC的第一階段做的事情其實是詢問參與者是否有條件執行事務,主要目的就是檢查一下是否都可用。第二階段纔是和2PC的第一階段一樣呢。

3PC出現的目的是爲了解決,2PC階段協調組和參與者都掛了之後新選舉的協調者不知道當前應該提交還是應該回滾的問題。

如果新的協調者來的時候發現一個參與者處於預提交或提交階段,代表以及過了,所有參與者的確認階段,這樣就直接提交事務就可以了。

所以說新出現的預提交階段目的是爲了讓協調者知道,每個參與者目前都是什麼階段,後面該如何同步各個參與者的狀態。

但是3PC還是不能保證,當協調者和某個參與者都掛了的時候,重新連接上的參與者是否已經執行了事務。

TCC

TCC和上面的兩種方案對比更像是 ,分佈式服務之間的事務解決方案。應用面更廣一些。

TCC的全稱是指:Try、Confirm、Cancel

  • Try:對事務參與者的資源的鎖定與預留。

  • Confirm:這個階段是在各個參與者服務中執行真正的事務操作。

  • Cancel:如果任何一個服務的業務方法執行出錯,那麼這裏就需要進行補償,就是對已經執行的業務執行回滾操作。

這種方式比較繁瑣,每一次事務都要定義三個操作,try-confirm-cancel。而且TCC對業務的侵入性比較大,每個業務都要寫相應得到撤銷方法。而且如果撤銷方法有不成功的情況,還有保證冪等。

但是還是有場景使用的,想一些涉及到支付、交易等這種強一致性,但又是多個服務的場景,使用TCC時比較合理的。這樣能嚴格保證分佈式事務要麼都成功,要麼都失敗回滾。

本地消息表

本地消息表的思想主要是依靠各個服務之間的本地事務來保證的。

就是在服務的本地建立一張消息表,一般是在數據庫中。 當執行分佈式事務的時候執行完本地操作後,在本地的消息表中插入一條數據。

然後將消息發送到MQ中,下一個服務接收到消息後執行本地操作,操作成功後更新消息表中的狀態。

如果下一個服務執行失敗了,那麼消息表中的狀態是不會變的,這樣就靠定時任務去刷消息表來進行重試,但是這樣需要保證被重試的服務是冪等的,這樣就保證最終數據一致。

可靠消息

可靠消息實際上指的是靠消息中間件來實現分佈式事務。

比如A公司的RocketMQ就用消息中間件實現了分佈式事務。

例如A系統會先發一個prepared消息到MQ中,消息發送成功了,再執行本地事務,本地事務執行成功了告訴MQ事務執行成功了。否則發送回滾消息。

B系統接收到prepared消息後開始執行本地事務,事務執行成功了,也是告訴MQ發送執行成功。

MQ 會自動定時輪詢所有 prepared 消息回調你的接口,問你,這個消息是不是本地事務處理失敗了,所有沒發送確認的消息,是繼續重試還是回滾?一般來說這裏你就可以查下數據庫看之前本地事務是否執行,如果回滾了,那麼這裏也回滾吧。這個就是避免可能本地事務執行成功了,而確認消息卻發送失敗了。

這個時候就需要自己實現反查接口。

如果這個方案裏,要是系統 B 的事務失敗了咋辦?重試咯,自動不斷重試直到成功,如果實在是不行,要麼就是針對重要的資金類業務進行回滾,比如 B 系統本地回滾後,想辦法通知系統 A 也回滾;或者是發送報警由人工來手工回滾和補償。

最大努力通知

最大努力通知,其實也算是一種最終一致性的方案。

主要是當A系統執行完本地事務後,發送消息給MQ,然後去讓B系統執行事務操作,如果B系統執行完成了,就消費消息,若B系統執行失敗了,則執行重試,重試多次直到成功。若達到一定次數後還沒成功就只能人工干預了。

總結

可靠消息那部分是因爲我沒有用過RocketMQ,所以並沒有認真寫,因爲我目前在的是騰訊系的公司所以。。。

總結一下面試的時候如果被問到這個問題怎麼辦,其實就是根據實際的業務場景來看,像涉及到交易,訂單等這種強一致性的場景,可以使用TCC,雖然說對也侵入性大,但是最終目的是很好的保證了。 還有就是對於時效性要求不是很強的,我覺得的最大努力通知也可以的。

最後說一下我們的目前使用的方案; 也算是最大努力通知的一種吧,當在一個web服務中,調用多個服務時,如何保證多個服務執行時數據一致性的。

當事務請求調用服務A時,如果服務A的操作執行失敗了,那麼直接事務執行失敗。

如果執行服務A的事務成功了,但是執行服務B的事務失敗了,那麼我們會先將失敗的請求落地(請求參數和被調用方信息入到消息表),然後將請求拋到消息隊列中去進行重試,通過消息隊列的ACK機制,保證我們重試消息最終可以被消費成功。

主要重試次數是3次,每次的重試的時間間隔不一樣,重試三次之後如果消息還沒有被ACK,那麼就直接發送預警通知給開發人員,進行人工干預。

如果發送消費失敗了,我們還有定時任務去定時刷我們的數據庫裏的消息表,來保證消息一定會被髮送。

另外在真實涉及到支付,訂單交易的場景時,我們主要也是使用類似TCC的方式來保證的。

說是類似是因爲,我們只有CC。要麼成功,要麼不成功,就直接調用回撤接口進行回滾。

作者:紀莫
原文鏈接:https://www.cnblogs.com/jimoer/p/14164364

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章