分布式最終一致性事務
一、強一致性事務的瓶頸
在《分布式強一致性事務》一文中介紹了分布式事務的常用協議2PC二階段提交,雖然2PC能在很大程度上實現分布式事務中各節點的ACID,但也存在同步阻塞問題,協調者單點故障,協調者因網絡原因導致的通知不周或收不全參與者回復導致的異常等問題。
同時,即使能穩定的使用二階段提交實現分布式事務,但是2PC通信過程中產生的耗時是巨大的,類似淘寶網,下完一個訂單後可能需要與計費中心,訂單中心,入庫,出庫等很多子系統打交道,此過程中帶來的開銷是不能接受的,等到所有流程通知響應完再返回告知用戶下單成功,這樣的體驗也是極差的。
為此,我們會比較容易想到用MQ來代替這個過程,因為MQ具有異步發起,子操作並行以及解耦子系統的特性,可以極大的提高用戶體驗。
二、基於MQ的最終一致性
通過借助MQ隊列,處理完業務邏輯後發送消息給消費方,並確保消息是發送成功的,之後消費方消費MQ隊列來處理業務邏輯,如果消費並處理成功,則結束,如果沒有成功,則重試,直到成功。若重試成功,但業務處理失敗,則由人工介入處理(但消費成功,處理失敗的場景是極少見的)。
三、實現思路
通過MQ異步可靠發送,冪等等策略來保證數據的最終一致性,會不可避免的對業務有一定的侵入。
異步+可靠發送消費思路:
Producer:需要在業務表所在的庫額外建一個消息表,確認表。消息表記錄消息發送狀態。消息表和業務數據要在一個本地事務裏提交。消息表可以比較簡單,有消息id,消息對應執行的方法,創建時間,發送狀態即可,當業務邏輯執行成功,即向消費方發送消息,並向消息表插入一條消息,狀態為已發送。
Consumer:從MQ隊列消費完消息後,處理這個消息,並完成自己的業務邏輯。此時如果本地事務處理成功,那發送給生產方一個confirm消息,Producer會將這條消息插入確認表,狀態為已確認,表明整個流程已經處理成功了。如果處理失敗,可以考慮發送給Producer一個failed消息,Producer同樣會將這條消息插入確認表,只是狀態為確認失敗。
重試機制:Producer有一個定時任務,即掃描已發送成功但確認狀態為失敗的消息,當掃描到有失敗的消息時,此時有兩個選擇, 一是根據這條消息對應的執行方法,去作發送方業務邏輯上的回滾,或通過執行對應的反向sql進行回滾。二是去重發這條失敗,即Producer的重試機制,可以設置重試發送的次數,直到消費方確認成功,若到達重試次數仍未有確認成功的狀態,則將該消息丟入死信隊列,由人工介入重發或進行手工處理。
註:由於每個節點都可能同時是Producer也是Consumer,所以使用到該方案的服務節點都應該在業務表所在庫去創建消息表和確認表。
冪等機制:在上面的描述中,業務的執行,消息的發送和插入消息表由於在一個本地事務中,所以保證了可靠發送。而消費方處理失敗後,有發送方定時重發和私信隊列人工處理,也保證了可靠消費(實際生產中,消費方消費後處理業務失敗的場景極為少見)。這整個流程保證了可靠發送與可靠消費,但卻保證不了一個問題,即消息可能重復發送或重復消費。
比如定時任務設置的是五分鐘,當掃描到一條已發送但未確認的消息時,會進行發送,如果消費方處理時間較長,五分鐘內依舊得不到確認成功的答復,則可能掃描到該消息又進行了重發,這樣重復發送又可能導致了消費方重復消費,在一個調用過程中,保證消息只被發送一次和只被消費一次,是至關重要的。
此時引入冪等的概念就極其重要,可以在業務表所在庫中增加一張冪等表。如在Producer節點的冪等表記錄每條消息的發送次數,保證只能有一次發送,Consumer節點的冪等表記錄每條消息的消費次數,保證只能有一次消費,因為每條消息都有一個唯一的消息id,所以可以以消息id來作為判斷的依據。
當Producer產生一條消息時,則將這條消息記錄進冪等表,表示該條消息已發送。當消費方返回確認成功時,將消息插入確認表,狀態為成功,此時整個流程完結。當返回確認失敗時,除了要將消息插入確認表中,狀態為失敗外,還要將冪等表中該消息id對應的發送記錄置為0,方便後面定時器根據發送記錄進行重發。若對失敗消息的處理選擇的是邏輯上的回滾,則可以省去對冪等表的操作。
當Consumer消費一條消息,並處理完業務邏輯時,則將這條消息記錄進冪等表,表示該條消息已消費。當再次消費消息時,可前往冪等表進行判斷,存在相同的消息id時,則可以將該消息丟掉,不再重復消費。
以上描述僅提供參考,對於基於MQ的可靠時間,有許許多多的玩法,例如消費方消費消息,業務處理失敗後,也向Producer發送確認成功的消息,自己把這條消息丟自己的死信隊列去重試或人工幹預,所以一個完整的可靠事件SDK需要提供不同方案對應的不同解決問題的流程。
四、事務消息
事務消息是一個理想的方案,即消息的發送和消費是自帶事務的,即我們只要把消息扔到MQ,那麽這個消息肯定會被消費成功。
生產方不用擔心消息發送失敗,不用擔心消息丟失。而消費方如果消息處理失敗了,還有機會繼續消費,直到成功為止。
事務消息可以少去我們很多為了保持一致性而對業務邏輯上的侵入,但遺憾的是市面上大部分MQ中間件都不支持事務消息。
RocketMQ最新版本已經支持事務消息,但仍需經過時間的考驗和成功產品的出現,才能保證基於MQ的最終一致性的春天到來,真正解決分布式事務這一分布式系統中最令人頭疼的問題,沒有之一。
分布式最終一致性事務