化繁為簡 - 騰訊計費高一致TDXA的實踐之路

騰訊技術工程發表於2020-02-10

化繁為簡 - 騰訊計費高一致TDXA的實踐之路

導語:騰訊計費是孵化於支撐騰訊內部業務千億級營收的網際網路計費平臺,在如此龐大的業務體量下,騰訊計費要支撐業務的快速增長,同時還要保證每筆交易不錯賬。採用最終一致性或離線補償的方案往往會帶來較多的處理風險或投訴。因此,我們提出了一種通用的基於應用層的長事務解決方案,將複雜的分散式一致性問題化繁為簡。


1 引言

英國計算機專家Hoare所言,軟體設計構建有兩種方法,一是使其儘可能簡單,從而一目瞭然確定其中不存在缺陷;另一種方法則是使其極為複雜,以至於看不出什麼明顯的缺陷。然而,實現第一種方法要困難的多。

騰訊計費是孵化於支撐騰訊內部業務千億級營收的網際網路計費平臺,目前彙集國內外主流支付渠道,提供賬戶管理、精準營銷、安全風控、稽核分賬、計費分析等多維度服務。平臺承載了公司每天數億收入大盤,為上百個國家(地區)、萬級業務程式碼、 百萬級結算商戶提供服務,託管賬戶總量 300 多億,是一個全方位的一站式計費平臺。

在如此龐大的業務體量下,騰訊計費要支撐業務的快速增長,同時還要保證每筆交易不錯賬。然而在分散式場景下要滿足ACID,保證所有操作均執行成功才提交結果,隨著分散式規模的擴大要達成一致性的時間週期越長。例如,Bitcoin的Scalability問題,為了滿足一致性一筆交易的平均確認時間需要10分鐘。因此,為了適應複雜的業務場景出現了Base理論,使用最終一致性來代替強一致性。在計費場景下,通常的一些最佳實踐是:

  • 先長款後短款原則。由平臺方掌握主動權,即使出現異常情況下也可以通過補償的方式保證一致性。例如,先支付後發貨,若發貨失敗通過重試機制達到最終一致。

  • 臨界資源訪問儘量使用樂觀鎖。例如,在讀多寫少場景使用樂觀鎖實現互斥,在保證一致性的前提下提高併發處理能力。

  • 遠端服務呼叫保證可重入。例如,已經支付成功的訂單,如果再次提交也不允許重複支付。

  • 非關鍵服務非同步化。例如,對於支付旁路的操作,通常藉助訊息中介軟體進行解耦處理,並保證最終執行完成。

  • 服務全鏈路實時監控。關鍵操作實時上報,監控每一步的成功率和轉化率,做到異常的實時發現。

通過上述等機制,在異常情況下可做到讀修復,寫修復,非同步修復等,以保證事前和事中的交易高一致。但考慮到問題的閉環,我們也建立了一套完善的針對實時訂單,非同步訂單,離線訂單的三級對賬機制,以做到事後的保證。

然而,對於日均過億的交易量,採用最終一致性或補償性的方案往往會帶來較多的處理風險及投訴,需要我們做到實時的一致性。同時,計費系統的邏輯多且複雜,目前涵蓋了近百個特點迥異的支付渠道,異常處理強依賴開發者的經驗,需要由平臺統一處理異常問題來提高服務的容錯性。隨著業務系統的發展,系統會有不斷的新功能迭代,加強邏輯的可管理性從而降低開發門檻,也是我們需要考慮的。

化繁為簡是我們的目標,為了應對上述挑戰,結合我們在計費領域的多年工程實踐經驗,決定著力打造一套通用的基於應用層的分散式高一致解決方案TDXA (Titan Distribute eXtended Architecture),將複雜的分散式一致性問題交給引擎平臺處理使業務開發更加聚焦,主要實現以下目標。

  • 分散式事務的原子性,保證交易實時高一致。

  • 自動的異常處理,提高容錯性。

  • 基於狀態機的流程管理,加強流程的可管理性。

TDXA通過在內部業務的試點,基於TDXA實現的計費服務,上線後通過對賬統計發現異常的交易訂單數量明顯減少,整體服務質量提高。同時,由於TDXA遮蔽了複雜的異常處理,業務開發效率也隨之提高,相同需求的開發工作週期較之前減少了近50%。

化繁為簡 - 騰訊計費高一致TDXA的實踐之路


2 設計思路

2.1 分散式事務

在工程上,作為完整的計費服務,一致性不僅需要考慮資料層的資料一致性,同時也要考慮應用層的邏輯一致性。比如,轉賬這個行為,資料層可以保證每次資料的更新一致性,而應用層如果只減不加,使用者看到的賬戶資料也是不一致的。交易的一致性,簡單講就是收對錢,發對貨。看似簡單的描述,但在實現中卻面臨了很多約束,導致容易出現一致性問題。比如:

  • 計費功能模組多,業務邏輯複雜

  • 支付交易鏈路長,一筆流程多達幾十次rpc

  • 業務請求峰值高,分散式服務節點多

  • 請求響應時間低,需要保證使用者體驗

  • 系統部署環境差,網路超時錯誤多

在OLTP場景下,使用者的一次購買請求通常會涉及多個後端服務的操作,如果其中某一個服務呼叫出現了二義性錯誤(網路超時),此時應該返回失敗還是成功,之前呼叫過的服務是否需要回滾?倘若請求量小的情況下,可以藉助人工來處理部分呼叫異常,但是當請求量大了以後,人工介入的成本就非常高。

化繁為簡 - 騰訊計費高一致TDXA的實踐之路

計費服務的整體質量要求至少達到四個9,同時不允許交易出現壞賬,對外提供事務一致性保障。從一致性的本質來看,要麼保證在一個業務邏輯中包含的服務都成功,要麼都失敗。在計費場景下,後端服務可能涉及介面,資料庫,分散式快取,訊息佇列等,我們希望做到針對不同後端資源的長事務一致性。

化繁為簡 - 騰訊計費高一致TDXA的實踐之路

我們的做法是,通過引入分散式事務管理器(Transaction Manager),將全域性事務分解為多個子事務,並交給不同的資源管理器(Resource Manager)處理。同時,針對不同的資源型別抽象為不同的事務處理模型,幫助業務實現自動的事務提交或回滾。面對複雜的長事務流程,業務可以自定義組合不同的事務模型,並根據業務自身特點指定不同的異常處理策略(Error Strategies),讓業務開發像處理單機事務一樣來保證整體分散式事務的一致性。通過這樣的方法,可以有效地幫助業務開發減少異常處理,更專心地實現業務核心邏輯。下面分別對不同的事務模型說明如下。

  • TCC事務模型
    分別代表Try,Confirm,Cancel。此事務模型類似兩階段提交協議(2PC),用於保證分散式事務的一致性。與2PC不同的是,對資源的加鎖是業務資源級別的隔離,減小了資源鎖的粒度,因此在事務沒有完成提交或回滾時,可以允許執行其他事務。TM根據所有RM的反饋來決定提交或中止事務,如果所有的RM全部執行成功則提交,但只要有一個失敗則回滾事務。此模式需要後端服務提供相應的資源操作介面,並保證冪等。呼叫關係如圖2.1所示。

化繁為簡 - 騰訊計費高一致TDXA的實踐之路
  • DB事務模型
    DB事務模型相比TCC事務模型,主要針對原生的資料庫操作,減少了對業務的侵入性,由引擎根據使用者提交的SQL實現跨資料庫例項的操作。因為基於傳統的關係型資料庫本地事務特性,DB事務模型不允許跨連線,而TCC事務模型沒有這個限制。針對這個問題,對於包含非同步處理的事務流程,一種做法是,引擎會先把已經執行的SQL進行COMMIT,並根據後續接收到的非同步通知結果來決定繼續正常執行,還是執行SQL反事務。另外可以結合資料庫的外部XA特性,通過兩階段提交的方式,對於Prepare成功的操作實現資料庫事務的跨連線,根據非同步執行的結果來決定是Commit還是Rollback。呼叫關係如圖2.2所示。

化繁為簡 - 騰訊計費高一致TDXA的實踐之路
  • TRY_BEST事務模型
    此事務模型表示盡最大努力執行,主要用在邏輯上可以保證一定執行成功的RPC呼叫。例如,用於一些通知類介面。在整個事務執行流中,建議放在最後執行,以保證整個事務的執行成功。呼叫關係如圖2.3所示。

化繁為簡 - 騰訊計費高一致TDXA的實踐之路

除了以上常見的幾種事務處理模型,我們也在不斷完善新的事務模型。其中每種事務模型都定義了相應的回撥介面由業務實現(如下表所示),同時在使用上也有一些約束準則要求。

  1. TRY_BEST和TCC兩種模式呼叫的RPC介面需要保證冪等。

  2. 三種模式支援混合使用,但是需要保證呼叫順序,最佳實踐是優先執行可以回滾的操作。例如,TRY_BEST和TCC兩種模式混合使用,應該先執行TCC的子事務,然後再執行TRY_BEST子事務,這樣如果執行TCC子事務失敗可以回滾,如果TRY_BEST子事務執行失敗可以通過補償最終保證成功。

化繁為簡 - 騰訊計費高一致TDXA的實踐之路

最後,以使用者轉賬為例看如何實現混合型別的事務流程。首先在第一階段,呼叫訂單服務建立一筆訂單(try),然後提交轉賬sql1和sql2。第一階段執行成功後並對使用者發起一條訊息通知(do)。若第一階段執行成功,在第二階段,框架會自動完成第一階段的確認過程(confirm和commit);若第一階段出現異常,框架也會自動完成回滾過程(cancel和rollback)。業務開發需要保證業務邏輯上的合理性,將可以回滾的操作先執行,而由框架保證整體事務的一致性。

化繁為簡 - 騰訊計費高一致TDXA的實踐之路

具體互動過程如下圖所示。

化繁為簡 - 騰訊計費高一致TDXA的實踐之路

2.2 自動的異常處理

根據墨菲定律,事情如果有變壞的可能,不管這種可能性有多小,它總會發生。我們的系統服務也是,雖然大多數情況都是可以正常對外提供服務的,但是在少數情況下也會出現服務不可用,網路超時等問題。因此,當服務呼叫出現異常時,如何保證事務可以繼續自動執行完成?一種可行的方案是,將事務狀態儲存和業務事務通過資料庫本地事務打包成一個原子事務,當異常出現時,可以通過一個訊息重放服務將事務繼續執行完成。

此方案的特點是,存在一定的業務侵入性,並且依賴單機DB的處理效能。為了解決這樣限制,提出了另一種基於訊息中介軟體的方案,首先訊息中介軟體本身需要保證資料的可靠性,同時,在客戶端通過引入一個生產代理Proxy,來解決在無法及時寫入的情況下,使用本地儲存充當一個緩衝區。通過結合本地佇列和遠端分散式佇列構成一個可用性更高,延遲更低的分散式訊息佇列方案。

化繁為簡 - 騰訊計費高一致TDXA的實踐之路
|

因此,我們需要訊息中介軟體滿足以下要求:

  • 一致性:計費場景要求資料一條不能丟,這是最基本的訴求。

  • 高可用:需具容災能力,在異常情況下能夠自動修復。

  • 海量儲存:在移動網際網路時代,產生大量的交易資料,需要具備海量堆積能力。

  • 快速響應:在億級支付場景下,要求能提供平滑的響應時間,儘可能控制在10ms內。

下面是我們基於Pulsar實現的一套高可靠的訊息中介軟體解決方案,整體架構如下圖所示。

化繁為簡 - 騰訊計費高一致TDXA的實踐之路
|

關於訊息中介軟體的實踐經驗,我們團隊在Apache Pulsar Meetup北京站也做過一次分享,可以參考另外這篇文章Apache Pulsar 在騰訊計費場景下的應用


2.3 狀態機的流程管理

由於計費流程的複雜性,通常一筆完整的支付請求需要至少幾十次的rpc呼叫。我們希望加強對流程的約束管理以減少可能的邏輯錯誤。我們的主要解決思路是,通過狀態圖定義服務呼叫流程,基於事件進行驅動來完成整個流程。其中,每個節點表示一個rpc操作,每個rpc操作分為三個獨立過程(Pre/RPC/Post);每條邊上可以定義多個運算元,通過運算元的返回值實現服務路由。

化繁為簡 - 騰訊計費高一致TDXA的實踐之路
|

同時,通過引入有限狀態機白名單的方式,保證業務狀態的流轉是合理的,可規避一些不必要的業務邏輯錯誤,減少異常錯誤的發生。例如,不會出現已經支付的訂單被再次提交支付,即Double Pay的問題。

化繁為簡 - 騰訊計費高一致TDXA的實踐之路

業務開發只需要關心業務節點註冊的Pre和Post回撥介面的具體實現,以及每個業務節點操作結果的返回運算元,其餘工作由框架完成整個流程的驅動,以及分散式事務的提交或回滾。同時,我們支援將當前的事務狀態通過Json描述出來,並轉換成流程圖,讓複雜的流程更加直觀。

化繁為簡 - 騰訊計費高一致TDXA的實踐之路



3 系統架構

騰訊計費高一致TDXA,旨在幫助業務解決長事務的一致性問題,通過使用狀態機來控制事務的執行路由,基於外掛化序號產生器制,提供多種子事務處理模型,實現對不同型別資源的分散式事務處理,完成自動化的提交或回滾。並提供規範的二進位制對接協議,以及Java等語言二次開發能力。整體架構如下圖所示。

化繁為簡 - 騰訊計費高一致TDXA的實踐之路

對每個模組的功能簡要說明如下:

  • TM: 主要實現流程的註冊,分散式事務控制,以及異常策略處理。

  • CM: TM的配置管理,包括流程註冊配置資訊,節點的屬性資訊,事務型別。

  • Producer: MQ的生產代理,事務的持久化,及異常情況的事務重試。

  • Consumer: MQ的消費代理,執行業務指定的事務補償策略。

  • RM: 實現外部介面協議轉換,及提供具體的業務資源介面,由TM呼叫。

  • TDMQ: 保證訊息完整性和低延遲的分散式訊息佇列。

TDXA的內部工作流程,主要分為三個部分:

  • 初始化流程

a)    初始化時,TDXA將從配置服務獲取要載入的所有註冊so或xml流程配置,並依次載入。
b)    載入後通過回撥協議介面取得要註冊的流程名及對應的註冊函式。
c)    通過呼叫註冊函式完成流程註冊。

  • 正常執行流程

a)    通過解析業務請求中的事務流程名,建立需要執行的流程,並將請求資料新增到事務的資料池中。
b)    初始化事務狀態為begin,並通過轉移條件決定下一個要執行的狀態。
c)    執行當前狀態的預處理和對應服務的執行呼叫。
d)    根據執行結果判斷服務路由,若沒有找到滿足的路由,則根據業務指定的錯誤策略完成事務恢復。
e)    重複執行c~d,直至達到狀態end,或發生異常情況。
f)    最後根據事務的執行結果,設定事務返回資訊。

  • 事務恢復流程

a) 反序列化分散式訊息佇列通知的事務資訊。
b) 對未完成的事務斷點執行。

為了方便業務快速接入,我們實現了一套前端自助化流程配置和功能驗證系統。希望能夠幫助業務在接入時做到儘量能所見即所得。前端每生成一個節點, 就會生成一份對應的後臺程式碼, 支援分Tab頁程式碼編解功能。為方便重度開發人員,頁面上也可以下載整體的含框架程式碼,開發者可以選擇使用其他的IDE進行開發。

化繁為簡 - 騰訊計費高一致TDXA的實踐之路


4 系統可靠性

4.1 邏輯可靠性

TDXA內部提供了一些必要的邏輯異常檢測機制,幫助業務規避不必要的錯誤,包括但不限於:1,註冊介面引數檢測。2,DAG(Directed Acyclic Graph)合理性檢測,需要滿足,僅有一個連通分量,不能迴環(但允許自環),每個節點都能達到終止節點保證閉環。3,執行時異常流程錯誤告警等。

TDXA對於業務異常的流程處理原則分為兩類。對於業務類錯誤,引擎會把控制權交給開發者,由開發者根據返回值資訊決定事務流程如何流轉;對於系統類錯誤,引擎會返回E_PROCESSING錯誤碼給前端,然後內部藉助訊息中介軟體進行非同步重試處理,其中重試次數和時間間隔均可由業務決定。

4.2 服務可靠性

在服務可靠性方面,TDXA中的事務管理器TM是與業務服務整合在一起的,並通過訊息中介軟體實現事務的持久化。考慮可能出現的以下幾種元件異常情況,以及如何實現故障遷移。

情況一:在TM進行事務持久化前,TM出現故障。此時,可以通過原始的事務請求進行重放。

化繁為簡 - 騰訊計費高一致TDXA的實踐之路

情況二:在TM完成事務持久化後,TM出現故障。此時,未完成的事務可以由叢集中其他的TM繼續完成處理。

化繁為簡 - 騰訊計費高一致TDXA的實踐之路

情況三:在TM進行事務持久化時,TDMQ出現故障。此時,通過本地的TDMQ Proxy非同步重試。

化繁為簡 - 騰訊計費高一致TDXA的實踐之路

情況四:在TM進行事務恢復時,TDMQ出現故障。此時,會選取新的可用的Broker進行消費,可能出現多次消費,需要業務保證冪等處理。

化繁為簡 - 騰訊計費高一致TDXA的實踐之路


5 系統效能優化

事務的核心是鎖,而事務和效能是兩個相悖的特性,因此在滿足分散式事務的前提下,也需要考慮對系統效能的調優,通常的優化方法包括:1,儘可能減少鎖的覆蓋範圍。2,MVCC多版本併發控制協議,讀寫不衝突。3,選擇正確的鎖型別。悲觀鎖適合併發爭搶比較嚴重的場景,而樂觀鎖適合併發爭搶不太嚴重的場景。TDXA在效能調優方面做了以下考慮。

優化1:通常引入事務協調者會增加網路呼叫次數,因此會減少系統的併發處理能力。為了儘量減少不必要的網路呼叫,我們將事務協調者與業務服務整合在一起實現,提供了外掛和配置的方式註冊業務事務。

優化2:允許不相關的事務可以並行執行,即pipeline操作。例如,b0~b3與c0~c3是兩個獨立的操作流程允許並行執行。

化繁為簡 - 騰訊計費高一致TDXA的實踐之路

優化3:對於TCC事務模型,操作相同資源的不同事務是獨立的,不需要等待執行。例如,事務T1在沒有confirm或cancel時,事務T2可以繼續執行try而不需要等待事務T1完成。

化繁為簡 - 騰訊計費高一致TDXA的實踐之路

優化4:針對資料庫外部XA的效能優化。在XA模型下,如果由於不穩定的網路通訊導致事務沒有提交,會影響所有其他事務都在等待。我們針對資料庫的網路模型增加了執行緒池的處理模式提高了I/O的處理能力,較原生的半同步方式提高了近5倍的效能。具體可以參考計費平臺部高一致性儲存層架構變遷之路---分散式MySQL資料庫(TDSQL)架構分析

化繁為簡 - 騰訊計費高一致TDXA的實踐之路


6 下一步方向

騰訊計費作為承載每年千億級業務營收的計費平臺,保證支付交易高一致,每筆收支不錯賬是我們的核心能力。分散式服務相比單體服務,為我們提供了更好的擴充套件性和靈活性,但也帶來了複雜的一致性問題。騰訊計費高一致TDXA在此背景下應運而生,旨在實現長事務的一致性,支援多種資源的混合分散式事務,在異常出現時可以實現零人工介入處理。讓複雜的問題在這裡變得簡單,化繁為簡是我們的目標。最後,考慮到極端情況,我們還會藉助三級對賬,通過實時訂單,非同步訂單,離線訂單的三級策略,來保障異常情況下的交易一致性。

騰訊計費通過多年的實踐探索,我們構建了一套完整的金融級計費解決方案,TDXA作為其中的一個重要組成部分,其他的還包括TDSQL,TSM,TBC等。目前,TDXA已經在騰訊計費內部業務廣泛應用,包括,大額的企業支付,騰訊雲計費,騰訊計費開放版等,在實際場景中得到了驗證,減少了不必要的人力成本,業務服務質量得到顯著提高。

最後,我們在解決了一些問題的同時,也遇到一些新的問題。

  1. 兼顧易用性和效能。目前,TCC的模式,將一次操作分成兩個獨立的子事務,不會阻塞其他的事務執行,可以保證較高的併發處理能力。但是依賴業務的介面實現資源的互斥隔離,對業務的實現存在一定侵入性;而DB的模式,目前是通過資料庫的外部XA事務實現的,可以支援原生SQL的分散式事務,減少了對業務的侵入性,但是由於資源是通過資料庫行鎖進行隔離的,只有提交或回滾的事務釋放資源後才能繼續處理其他的事務。易用性和效能的平衡是我們繼續優化方向。

  2. 支援更多的開發者。考慮到執行效率,TDXA的核心是通過C++實現的,並提供了C的介面給業務使用。考慮到可以推廣到更多的開發者,我們基於C的介面使用JNI封裝了Java的介面,從而也可以支援Java技術棧的開發。除此之外,越來越多新的高階開發語言出現,比如GoLang。如何將我們的能力普適給更多的開發者,也是我們後續需要考慮的。

  3. 事務隔離性要求。TDXA保證了長事務的一致性,但是卻犧牲了一定的隔離性。兩個事務並行執行的時候,其中一個事務可能讀取了另一個未提交事務的資料,即髒讀。對於大多數業務場景可能問題不大,可以通過事務補償來保證一致性,但是在某些業務場景下可能是不允許的。

  4. 支援更多的資源型別事務。TDXA的目標是希望業務可以像處理單機事務一樣處理分散式事務,目前已經支援RPC,DB等常見的介面。不難發現,對資源的加鎖都是後端介面提供的能力,為了支援更多的資源型別,在不依賴後端的前提下我們應該如何支援。

化繁為簡 - 騰訊計費高一致TDXA的實踐之路

化繁為簡 - 騰訊計費高一致TDXA的實踐之路

相關文章