這是一個開撕的話題,我經歷過太多的關於分佈式事務的需求:“有沒有簡單的方案,像使用數據庫事務那樣,解決分佈式數據一致性的問題”。特別是微服務架構流行的今天,一次交易需要跨越多個“服務”、多個數據庫來實現,傳統的技術手段,已經無法應對和滿足微服務情況下這些複雜的場景了。針對微服務下的交易業務如何保障數據一致性,本文儘量做到理論結合實踐,將我們在實際產品中用到的分佈式事務實現機制,和大家扒一扒,希望能幫到各位。
談到分佈式事務,必須先把CAP拿出來說說事……,當然還有BASE……
從架構的角度來看,業務拆分(數據分區)、數據一致性、性能(可用性)永遠是個平衡的藝術:
-
在微服務架構下,爲了獲得更高的性能與靈活性,將業務應用拆分爲多個,交易跨多個微服務編排,數據一致性的問題產生;
-
爲了解決數據一致性問題,需要採用不同的事務機制來保障,這又會產生性能(可用性)問題;
在計算機世界裏,爲了解決一件事情,另外的問題就會接踵而至,從另一個層面印證了IT架構永遠是一種平衡的藝術。
“BASE”其核心思想是根據業務特點,採用適當的方式來使系統達到最終一致性(Eventual consistency);在互聯網領域,通常需要犧牲強一致性來換取系統的高可用性,只需要保證數據的“最終一致”,只是這個最終時間需要在用戶可以接受的範圍內;但在金融相關的交易領域,仍然需要採用強一致性的方式來保障交易的準確性與可靠性。
接下來爲大家介紹業界常見的事務處理模式,包括兩階段提交、三階段提交、Sagas長事務、補償模式、可靠事件模式(本地事件表、外部事件表)、可靠事件模式(非事務消息、事務消息)、TCC等。不同的事務模型支持不同的數據一致性。如果讀者對這幾種分佈式事務比較熟悉,可以直接參考下圖並結合自身業務需求選擇合適的事務模型。
兩階段提交、三階段提交
這種分佈式事務解決方案目前在各種技術平臺上已經比較成熟:JavaEE架構下面的JTA事務(各應用服務器均提供了實現,Tomcat除外)。
但目前兩階段提交、三階段提交存在如下的侷限性,並不適合在微服務架構體系下使用:
-
所有的操作必須是事務性資源(比如數據庫、消息隊列、EJB組件等),存在使用侷限性(微服務架構下多數使用HTTP協議),比較適合傳統的單體應用;
-
由於是強一致性,資源需要在事務內部等待,性能影響較大,吞吐率不高,不適合高併發與高性能的業務場景;
Sagas長事務
在Sagas事務模型中,一個長事務是由一個預先定義好執行順序的子事務集合和他們對應的補償子事務集合組成的。典型的一個完整的交易由T1、T2、……、Tn等多個業務活動組成,每個業務活動可以是本地操作、或者是遠程操作,所有的業務活動在Sagas事務下要麼全部成功,要麼全部回滾,不存在中間狀態。
Sagas事務模型的實現機制:
-
每個業務活動都是一個原子操作;
-
每個業務活動均提供正反操作;
-
任何一個業務活動發生錯誤,按照執行的反順序,實時執行反操作,進行事務回滾;
-
回滾失敗情況下,需要記錄待衝正事務日誌,通過重試策略進行重試;
-
衝正重試依然失敗的場景,提供定時衝正服務器,對回滾失敗的業務進行定時衝正;
-
定時衝正依然失敗的業務,等待人工干預;
Sagas長事務模型支持對數據一致性要求比較高的場景比較適用,由於採用了補償的機制,每個原子操作都是先執行任務,避免了長時間的資源鎖定,能做到實時釋放資源,性能相對有保障。
Sagas長事務方式如果由業務去實現,複雜度與難度並存。在我們實際使用過程中,開發了一套支持Sagas事務模型的框架來支撐業務快速交付。
開發人員:業務只需要進行交易編排,每個原子操作提供正反交易;
配置人員:可以針對異常類型設定事務回滾策略(哪些異常納入事務管理、哪些異常不納入事務管理);每個原子操作的流水是否持久化(爲了不同性能可以支持緩存、DB、以及擴展其它持久化方式);以及衝正選項配置(重試次數、超時時間、是否實時衝正、定時衝正等);
Sagas事務框架:提供事務保障機制,負責原子操作的流水落地,原子操作的執行順序,提供實時衝正、定時衝正、事務攔截器等基礎能力;
Sagas框架的核心是IBusinessActivity、IAtomicAction。IBusinessActivity完成原子活動的enlist()、delist()、prepare()、commit()、rollback()等操作;IAtomicAction主要完成對狀態上下文、正反操作執行。
限於文章篇幅,本文不對具體實現做詳述;後面找時間可以詳細介紹基於Sagas長事務模型具體的實現框架。Sagas長事務需要交易提供反操作,支持事務的強一致性,由於沒有在整個事務週期內鎖定資源,對性能影響較小,適合對數據要求比較高的場景中使用。
補償模式
Sagas長事務模型本質上是補償機制的複雜實現,如果實際業務場景上不需要複雜的Sagas事務框架支撐,可以在業務中實現簡單的補償模式。補償過程往往也同樣需要實現最終一致性,需要保證取消服務至少被調用一次和取消服務必須實現冪等性。補償模式可以參見同事田向陽的技術文章《微服務架構下數據一致性保證(三)》http://dwz.cn/3TVJaB
補償機制不推薦在複雜場景(需要多個交易的編排)下使用,優點是非常容易提供回滾,而且依賴的服務也非常少,與Sagas長事務比較來看,使用起來更簡便;缺點是會造成代碼量龐大,耦合性高,對應無法提供反操作的交易不適合。
可靠事件模式(本地事件表、外地事件表)
可靠事件模式屬於事件驅動架構,當某件重要事情發生時,例如更新一個業務實體,微服務會向消息代理髮佈一個事件。消息代理會向訂閱事件的微服務推送事件,當訂閱這些事件的微服務接收此事件時,就可以完成自己的業務,也可能會引發更多的事件發佈。
可靠事件模式在於保證可靠事件投遞和避免重複消費,靠事件投遞定義爲:
-
每個服務原子性的業務操作和發佈事件;
-
消息代理確保事件傳遞至少一次;避免重複消費要求服務實現冪等性。
基於事件模式,需要重點考慮的是事件的可靠到達,在我們產品實際支持過程中,通常有本地事件表、外部事件表兩種模式:
1. 本地事件表方法將事件和業務數據保存在同一個數據庫中,使用一個額外的“事件恢復”服務來恢復事件,由本地事務保證更新業務和發佈事件的原子性。考慮到事件恢復可能會有一定的延時,服務在完成本地事務後可立即向消息代理髮佈一個事件。
-
微服務在同一個本地事務中記錄業務數據和事件;
-
微服務實時發佈一個事件立即通知關聯的業務服務,如果事件發佈成功立即刪除記錄的事件;
-
事件恢復服務定時從事件表中恢復未發佈成功的事件,重新發布,重新發布成功才刪除記錄的事件;
其中第2條的操作主要是爲了增加發布事件的實時性,由第三條保證事件一定被髮布。本地事件表方式業務系統和事件系統耦合比較緊密,額外的事件數據庫操作也會給數據庫帶來額外的壓力,可能成爲瓶頸。
2. 外部事件表方法將事件持久化到外部的事件系統,事件系統需提供實時事件服務以接受微服務發佈事件,同時事件系統還需要提供事件恢復服務來確認和恢復事件。
-
業務服務在事務提交前,通過實時事件服務向事件系統請求發送事件,事件系統只記錄事件並不真正發送;
-
業務服務在提交後,通過實時事件服務向事件系統確認發送,事件得到確認後,事件系統才真正發佈事件到消息代理;
-
業務服務在業務回滾時,通過實時事件向事件系統取消事件;
-
如果業務服務在發送確認或取消之前停止服務了怎麼辦呢?事件系統的事件恢復服務會定期找到未確認發送的事件向業務服務查詢狀態,根據業務服務返回的狀態決定事件是要發佈還是取消;
該方式將業務系統和事件系統獨立解耦,都可以獨立伸縮。但是這種方式需要一次額外的發送操作,並且需要發佈者提供額外的查詢接口。
基於可靠事件的事務保障模式可以有很多的變種實現,比如對消息可靠性不高的話,可以將本地表的方式換做緩存方式。爲了提高消息投遞的效率,可以將多次消息合併爲投遞模式。爲了提供強一致性的事務保障,甚至可以將本地消息表持久化(保障發方法消息可靠落地)+遠程消息表持久化(保障接收方消息可靠落地)結合的模式。
在我們的流程產品中針對業務和流程的分佈式事務解決方案就採用了多次消息合併投遞+本地緩存+遠程消息表持久化的模式,接下來爲大家介紹具體的使用方式。
使用場景
在實際業務項目中通常採用業務與流程分佈式部署的模式,業務系統通過遠程接口訪問流程引擎,業務數據同流程數據存放到各自的數據庫中。
在這種場景中,如果業務系統的流程操作和業務操作交叉在一起,當流程操作成功,而業務操作失敗時,就會造成業務回滾,而流程在引擎端已經創建,導致業務系統和流程引擎狀態不一致。
在業務應用中對一個事務中的流程操作採用本地緩存+批量投遞+遠程落地的模式(如果需要在客戶端確保消息可靠性,可以將本地緩存換成本地表的方式);在流程引擎端在消息投遞來之後,做了消息表落地的工作,保障可靠執行。在我們流程產品中流程引擎對外提供的客戶端提供了統一的分佈式事務API,和使用傳統本地事務一樣進行操作,保證了透明性,簡化開發人員的複雜度。分佈式事務API支持兩種協議模式:
-
HTTP + 二進制序列化的模式
-
WebService模式
後續我們會增加Restful風格的接口。
可靠事件模式在互聯網公司中有着較大規模的應用,該方式適合的業務場景非常廣泛,而且能夠做到數據的最終一致性,缺點是該模式實現難度較大,依賴數據庫實現可靠性,在高併發場景下可能存在性能瓶頸,需要在公司層面搭建一套標準的可靠事件框架來支撐。
可靠事件模式(非事務消息、事務消息)
可靠事件模式的事件通知可以採用消息的模式來實現,其實現原理和本地事件表、外部事件表一致,本文就不在詳述。目前常用的消息框架ActiveMQ、RabbitMQ、Kafka、RocketMQ可以用來作爲消息投遞的渠道。注意:Kafka通常不適合,因爲Kafka的設計存在丟消息的場景。
目前市面上支持事務的消息產品比較少,RocketMQ雖然實現了可靠的事務模式,但並沒有開源出來、沒有開源出來、沒有開源出來,順便說一下國內的開源有太多需要改進的空間(關鍵點不開源,開源後沒有持續的投入)。
TCC模式
一個完整的TCC業務由一個主業務服務和若干個從業務服務組成,主業務服務發起並完成整個業務活動,TCC模式要求從服務提供三個接口:Try、Confirm、Cancel。
-
Try:完成所有業務檢查
預留必須業務資源
-
Confirm:真正執行業務
不作任何業務檢查;只使用Try階段預留的業務資源;Confirm操作滿足冪等性;
-
Cancel:
釋放Try階段預留的業務資源;Cancel操作滿足冪等性;
整個TCC業務分成兩個階段完成:
第一階段:主業務服務分別調用所有從業務的try操作,並在活動管理器中登記所有從業務服務。當所有從業務服務的try操作都調用成功或者某個從業務服務的try操作失敗,進入第二階段。
第二階段:活動管理器根據第一階段的執行結果來執行confirm或cancel操作。如果第一階段所有try操作都成功,則活動管理器調用所有從業務活動的confirm操作。否則調用所有從業務服務的cancel操作。
TCC模式的詳細描述可以參見同事田向陽的技術文章《微服務架構下數據一致性保證(三)》http://dwz.cn/3TVJaB。
需要注意的是第二階段confirm或cancel操作本身也是滿足最終一致性的過程,在調用confirm或cancel的時候也可能因爲某種原因(比如網絡)導致調用失敗,所以需要活動管理支持重試的能力,同時這也就要求confirm和cancel操作具有冪等性。
總結
六種分佈式事務的實現模式從數據一致性、事務級別、吞吐量、實現的複雜度各有優劣,下圖爲大家提供選擇依據。
站在架構設計的角度,針對數據一致性需要把業務因素考慮進來,這有利於團隊在技術上作出更合理的選擇。根據具體業務場景,評估出業務對事務的優先級,更有利於作出架構上的取捨。我們經常接觸的證券、金融、支付等行業,對數據一致性要求極高,需要嚴格的實時保證要求;但對於基於社交類的應用場景,可以採用局部實時一致,最終全局一致的能力。因此大家在實踐過程中,一定要把技術與業務結合,選擇適合自身業務的技術方案。
作者介紹
劉相,來自普元,十年IT行業經驗,專注於企業軟件平臺。在SOA、分佈式計算、企業架構設計等領域有一定了解。著有《SpringBatch批處理框架》一書。