有人的地方就有江湖。
哪裏有江湖,哪裏就有紛爭。
在電子商務和其他業務中,系統壹般由多個獨立的服務組成。如何解決分布式調用時數據的壹致性?
具體業務場景如下。比如壹個業務操作同時調用服務A、B、C,需要同時滿足或者成功;或者同時出現故障A、B、C可能是不同部門開發的遠程服務,部署在不同的服務器上。
在分布式系統中,如果不想犧牲壹致性,CAP理論告訴我們只能放棄可用性,這顯然是不可接受的。為了便於討論,首先簡要介紹數據壹致性的基本理論。
強壹致性
弱壹致性
最終壹致性
在工程實踐中,為了保證系統的可用性,大多數互聯網系統將強壹致性的要求轉化為最終壹致性的要求,通過保證系統的冪等性來保證數據的最終壹致性。但在電子商務等場景下,數據壹致性的解決方案不同於常見的互聯網系統(如MySQL主從同步),群友討論分為以下六種解決方案。
業務集成方案主要采用接口集成到本地執行的方法。例如,在問題場景中,服務A、B和C可以集成到壹個用於業務的服務D中,並且這個服務D可以轉換成壹個本地事務。比如服務D包括本地服務和服務E,服務E是本地服務A ~ C的集成..
優點:分布式事務被解決(規避)。
缺點:很明顯,原計劃拆分的業務耦合在壹起,業務職責不明確,不利於維護。
因為這種方法有明顯的缺點,所以通常不推薦使用。
該方案的核心是通過消息日誌異步執行需要分布式處理的任務。消息日誌可以存儲在本地文本、數據庫或消息隊列中,然後可以通過業務規則自動或手動發起重試。人工重試更多應用於支付場景,事後問題通過對賬系統處理。
消息日誌方案的核心是保證服務接口的冪等性。
考慮到網絡通信失敗、數據包丟失等原因,如果接口不能保證冪等性,就很難保證數據的唯壹性。
易貝的主要觀點如下。
堿:酸的替代品
這個方案是易貝建築師丹·普裏切特2008年發表給ACM的壹篇文章。這是壹篇解釋基本原理或終極壹致性的經典文章。本文討論了基本原則和酸性原則在確保數據壹致性方面的基本區別。
如果ACID為分區數據庫提供了壹致的選擇,那麽如何實現可用性呢?答案是
基礎(基本可用,軟狀態,最終壹致)
基礎的可用性是通過支持局部故障而不是全局系統故障來實現的。這裏有壹個簡單的例子:如果用戶被分成五個數據庫服務器,基礎設計鼓勵類似的處理,壹個用戶數據庫的故障只影響這個特定主機的20%用戶。這裏不涉及魔法,但它確實帶來了更高的感知系統可用性。
這篇文章描述了壹個最常見的場景。如果生成壹個交易,就需要在交易表中添加記錄,修改用戶表的金額。這兩個表屬於不同的遠程服務,所以涉及到分布式事務壹致性的問題。
本文提出了壹個經典的解決方案,將主修改操作和更新用戶表的消息放在壹個本地事務中。同時,為了避免重復消耗用戶表消息帶來的問題,實現多次重試的冪等性,增加了更新記錄表updates_applied來記錄處理後的消息。
該系統的偽代碼如下
(單擊以全屏縮放圖片)
基於上述方法,在第壹階段,通過本地數據庫的事務保證,添加事務表和消息隊列。
第二階段,單獨讀出消息隊列(但不刪除),通過判斷更新記錄表updates_applied檢測相關記錄是否被執行。未執行的記錄會修改用戶表,然後在updates_applied中增加壹條操作記錄,事務執行成功後刪除隊列。
通過以上方法,實現了分布式系統的最終壹致性。關於易貝計劃的更多信息,請參考文章末尾的鏈接。
隨著業務規模的不斷擴大,電子商務網站普遍要面臨拆分之路。就是把原來單壹的應用拆分成幾個不同職責的子系統。比如以前面向用戶、客戶、運營的功能可能放在壹個系統裏,現在分成幾個子系統,比如訂單中心、代理管理、運營系統、報價中心、庫存管理。
在分裂中首先要面對的是什麽?
當初單個應用的所有功能都在壹起,存儲也在壹起。比如操作要取消訂單,直接更新訂單表狀態,然後更新庫存表就可以了。因為它是單個應用程序並存儲在壹起,所以所有這些都可以在壹個事務中完成,關系數據庫可以確保壹致性。
但是拆分之後就不壹樣了。不同的子系統有各自的存儲。比如訂單中心只管理自己的訂單庫,庫存管理也有自己的庫。然後當操作系統取消訂單時,通過接口調用調用訂單中心和庫存管理的服務,而不是直接操作庫。這就涉及到壹個“分布式事務”的問題。
分布式事務有兩種解決方案。
1.首先使用異步消息。
如上所述,在使用異步消息消費者時,需要實現冪等性。
冪等有兩種方式。壹種方式是業務邏輯保證等冪性。例如,收到支付成功的消息後,訂單狀態將變為支付完成。如果當前狀態是支付完成,收到另壹條支付成功的消息意味著該消息重復,將直接作為消息處理。
換句話說,如果業務邏輯不能保證冪等性,則應該添加重復數據刪除表或類似的實現。對於生產者端,將消息庫放在業務數據庫的同壹個實例上,在同壹個本地事務中發送消息和業務操作。發送消息時,消息不是立即發送,而是在消息庫中插入壹條消息記錄,然後在提交事務時異步發送消息。如果消息發送成功,消息庫中的消息將被刪除。如果消息隊列服務異常或者網絡出現問題,消息沒有發送成功,那麽消息就會留在這裏,另壹個服務會不斷地把這些消息掃出來,重新發送。
2.有些服務不適合異步消息傳遞,事務中的所有參與者都需要同步獲得結果。實際上,這種情況的實現與上面類似,在每個參與者的本地業務庫的同壹個實例上有壹個事務記錄庫。
比如a同步調用b和c。a在本地交易成功時更新本地交易記錄狀態,B和C相同。如果A調用B壹次失敗,可能是B真的失敗了,也可能是調用超時,B實際上成功了。然後壹個中心服務比較三方的交易記錄,做出最終決定。假設現在三方的交易記錄是A成功,B失敗,C成功。那麽有兩種方法可以最終決定,根據具體的場景:
對場景B做壹個特殊的解釋:比如B是壹個存貨扣款服務,第壹次調用的時候因為某種原因失敗了,但是重試的時候存貨已經變成了0,所以不可能重試成功。這時,A和C必須回滾。
那麽可能有人會認為,在業務庫的同壹個實例中放壹個消息庫或者交易記錄庫會侵犯業務,業務應該關心這個庫。設計合理嗎?
其實可以依靠運維的手段來簡化開發的侵擾。我們的方法是讓DBA在公司所有的MySQL實例上預先初始化這個庫,通過框架層(消息客戶端或者事務RPC框架)在後臺透明地操作這個庫。業務開發人員只需要關心自己的業務邏輯,不需要直接訪問這個庫。
總結壹下,其實兩種方法的基本原理差不多,都是將分布式事務轉化為多個本地事務,然後依靠重試來達到最終的壹致性。
事務創建的壹般過程
我們將事務創建過程抽象為壹系列可擴展的功能點,每個功能點可以有多個實現(具體實現之間存在組合/互斥關系)。把所有的功能點按照壹定的流程串起來,事務創建的流程就完成了。
面臨的問題
每個功能點的實現可能依賴於外部服務。那麽如何保證服務之間的數據壹致呢?比如鎖定優惠券服務的調用超時,不確定優惠券是否鎖定成功。我該怎麽辦?再比如,車票鎖定成功,但扣除庫存失敗。我該怎麽辦?
方案選擇
過多的服務依賴將導致管理復雜性增加和穩定性風險增加的問題。試想壹下,如果我們依靠10個服務,9個服務都執行成功了,最後壹個失敗了,那麽前9個服務會回滾嗎?這個成本還是很高的。
因此,在將大流程拆分成若幹個小的本地事務的前提下,針對相關業務寫入的非實時性和非強壹致性,在本地事務成功執行後,我們選擇發送消息通知和異步執行相關事務的方案。
消息通知往往不能保證100%成功;並且在消息被通知之後,接收方的服務是否能夠被成功執行仍然是未知的。前壹個問題可以通過重試來解決;後者可以通過事務消息來保證。
所以目前只有業務場景需要實時同步,有很強的壹致性需求。在交易創建過程中,兩個典型的場景是鎖券和扣庫存。
要保證多個系統之間的數據壹致性,乍壹看,需要引入壹個分布式事務框架來解決。但是引入類似兩階段提交的非常重的分布式事務框架,會帶來復雜度的急劇增加;在電子商務領域,絕對強壹致性過於理想化,我們可以選擇準實時最終壹致性。
在交易創建過程中,我們先創建壹個隱形訂單,然後針對證券被鎖定、庫存被同步扣款時的異常調用(失敗或超時)向MQ發送報廢消息。如果消息發送失敗,本地將進行時間步進異步重試;優惠券系統和庫存系統收到消息後,會判斷是否需要做業務回滾,從而準實時保證多個本地交易的最終壹致性。
還有壹種業界常用的支付寶的xts方案,是支付寶在2PC的基礎上改進的。主要想法如下。大部分信息引自官網。
分布式事務服務簡介
分布式事務服務(DTS)是壹種分布式事務框架,用於保證大規模分布式環境中事務的最終壹致性。DTS在體系結構上分為xts客戶端和xts服務器。前者是嵌入在客戶端應用程序中的JAR包,主要負責寫入和處理交易數據。後者是獨立的系統,主要負責異常交易的恢復。
核心特征
傳統關系數據庫的事務模型必須遵守ACID原則。在單數據庫模式下,ACID模型可以有效保證數據的完整性,但是在大規模分布式環境下,壹個業務往往跨越多個數據庫。如何保證這些數據庫之間的數據壹致性需要其他有效的策略。在JavaEE規範中,使用2PC (2 Phase Commit)來處理跨DB環境下的事務問題,但2PC是壹種反可伸縮的模式,即在事務處理過程中,參與者需要持有資源,直到整個分布式事務結束。這樣,當業務規模達到1000萬以上的水平時,2PC的局限性就會越來越明顯,系統擴展性會變得很差。基於此,我們采用BASE的思想實現了壹套類似於2PC的分布式事務方案,也就是DTS。DTS充分保證了分布式環境下的高可用性和可靠性,同時兼顧了數據壹致性的要求。它最大的特點是保證數據最終壹致。
簡單地說,DTS框架具有以下特征:
下面是分布式事務框架的流程圖。
實現
與2PC協議相比
1.電子商務業務
公司支付部門通過接入其他第三方支付系統為業務部門提供支付服務。支付服務是基於Dubbo的RPC服務。
對於業務部門來說,需要調用電商部門的訂單支付。
從業務規則上,需要同時保證業務數據的實時性和壹致性,即支付成功必須加分。
我們采用的方式是同步調用,先處理本地事務業務。考慮到積分業務相對簡單,業務影響低於支付,積分平臺提供了增、退的接口。
具體流程是調用整合平臺增加用戶積分,然後調用支付平臺進行支付處理。如果處理失敗,catch方法調用集成平臺的撤銷方法,撤銷本次處理的積分訂單。
(點擊圖片全屏放大)
2.用戶信息變更
分布式服務需要更多的支撐系統,尤其是我們基於消息和日誌的最終壹致性方案,需要考慮消息的積壓、消耗、監控和告警。
在分區數據庫中,犧牲壹些壹致性來換取可用性可以極大地提高可伸縮性。
英文版:http://queue.acm.org/detail.cfm? id = 1394128
中文版:http://article.yeeyan.org/view/167444/125572
感謝李玉福、於、蘑菇街七公的提議,以及其他許多小組成員對本文內容的貢獻。
本文由李玉福和蒂姆·楊編輯。請註明來自@高可用性架構。