基於服務的分散式事務(上)

點小藍字加關注!
作者丨Willem Jiang
傳統資料庫事務
在傳統單體應用架構下,我們通常會將業務資料儲存在一個數據庫中,應用各模組直接對資料庫進行操作業務資料。由資料庫提供基於 ACID [1] 的事務保證。
-
A是Atomic 原子性 :事務作為整體來執行,要麼全部執行,要麼都不執行。
-
C是Consistency 一致性 :事務應確保資料從一個一致的狀態轉變為另一個一致的狀態。
-
I是 Isolation 隔離性 :多個事務併發執行時,一個事務的執行不應影響其他事務的執行。
-
D是Durability 永續性 :已提交的事務修改資料會被持久保持。
例如一個電商的下單操作,就涉及到使用者系統、庫存系統、支付系統以及配送系統等一系列的協同操作。我們在執行下單操作的過程中,如果出現庫存短缺,或者使用者賬戶餘額不足的情況,這個下單操作就會涉及到一系列的相關業務系統呼叫。如果這些子系統連線同一個資料庫,我們可以通過資料庫提供的事務原子性機制將庫存數量校驗以及使用者餘額校驗的工作,和執行具體的下單業務操作組合成為一個數據庫事務操作。通過資料庫事務原子性來保證系統各個模組的呼叫要麼都成功,要麼都失敗(取消)。 同時,由於資料庫提供一致性和永續性保證,保證瞭如果事務執行成功並提交,本次業務操作的資料在立即生效的同時不會產生異議。 同時資料庫提供了不同級別的資料鎖機制保證應用多個執行緒同時讀取或者更新資料的過程中不會相互影響,從而來保證業務操作的隔離性。
微服務的分散式事務
隨著微服務架構的流行,很多大型的業務流程被拆分成為了多個功能單一的基礎服務,大家會根據業務的訴求在這些基礎服務之上編寫一些組合呼叫服務來滿足業務訴求。為了保證微服務能夠獨立開發部署執行,通常我們會採用一個微服務對應一個數據庫的架構,將內部資料經微服務封裝之後,以服務的方式對外暴露。這樣以往基於資料庫來實現的資料操作,就變成了多個對外提供服務的微服務系統的協同完成操作。因為單個微服務只知道自己的服務執行情況,為了保證分佈事務的一致性,參與分散式事務的微服務通常會依託協調器完成相關的 一致性協調操作 [2] 。
在十多年前分散式事務的實現方案有CORBA的 Object Transaction Service(OTS)、J2EE的 Java Transaction API 以及 Java Transaction Service。這些事務管理以及事務服務的技術都是建立在ACID事務的概念上的。協調器依託於底層的資源互動協議實現資源的佔用以及提交的操作,通過 兩階段提交 [3] 的方式實現分散式事務的強一致操作。兩階段提交將分散式事務操作分為準備和提交兩個階段:系統在準備解決階段完成資源操作, 如果準備階段中出現問題,支援回滾操作,但是在提交階段是不允許出錯的。兩階段在保證事務原子性上做了很多工作,但是兩階段提交最大的問題是在分散式事務執行過程中, 所有參與事務的節點資源都是被鎖定的,系統不允許其他節點訪問鎖定的資源,在這種執行下很難進一步提升系統的執行效率。
如上所述,在ACID的事務執行過程中,為了保證事務的隔離性,通常我們會採用讀寫加鎖的方式,通過序列處理資料方式,保證多個事務在同時執行的過程中不會相互影響。也就是說只有當事務提交併且儲存修改記錄或者回退取消修改記錄之後,其他的事務才能繼續執行。然而對於由多個事務組成的長時間執行的事務來說,如果在整個事務的執行過程都採用這種機制來保證事務的隔離性是一種很低效的解決方案。
那我們有什麼辦法即提高系統執行效率,又能保證事務的資料一致性呢?
答案是採用補償的方式來解決這一問題。
基於補償的事務實現
補償是指我們將一個事務分成一個本地執行的正常操作事務和一個邏輯上對之前的操作進行補償的事務。這樣採用補償事務的方式,我們可以把一個長時間執行的事務分化成若干個可以立即提交的本地事務呼叫,而不是一個長時間佔用鎖資源的巨型事務。 這樣做的最大好處就是極大降低鎖佔用的時間。作為代價,補償方式的取消操作和以往的實現方式有很大的不同,我們需要執行一個單獨的ACID事務來完成對之前已提交的事務的邏輯補償。
下圖展示了一個典型的分散式事務呼叫, 使用者請求觸發事務初始服務, 事務初始服務會順序呼叫兩個事務參與服務(服務A,服務B)。由於這兩個事務參與服務之間沒有聯絡,當事務參與服務執行出現了問題,需要一個協調器參與相關的恢復操作。
這裡我們可以根據補償執行的不同將其分成兩組不同的補償方式:
-
不完美補償 - 反向操作會留下之前原始事務操作的痕跡,一般來說我們是會在原始事務記錄中設定取消狀態。
-
完美補償 - 反向邏輯會徹底清理之前的原始事務操作,一般來說是不會保留原始事務交易記錄,使用者是感知不到事務取消之前的狀態資訊的。
對於採用不完美的補償方式的系統( Saga實現 [4] )來說:
我們的補償事務邏輯和其他的事務邏輯相比沒有什麼不同, 系統只需要像執行其他業務邏輯一樣執行相關的補償操作即可,無需設定特殊的處理邏輯來恢復事務執行之前的狀態。
以我們常見的銀行ATM取款業務為例,銀行賬戶預先進行扣減的操作,如果取款不成功,其邏輯恢復操作就是通過沖正的方式將預先扣減的款項打回到使用者賬戶,我們可以通過檢視賬戶的交易記錄找到扣減和衝正的記錄資訊。
下圖展示的內容就是當初始服務呼叫分別呼叫服務A和服務B,服務B執行出現錯誤,這個時候我們事務協調器會呼叫服務A的衝正方法將系統狀態恢復到執行服務呼叫之前的狀態。
對於採用完美補償方式的系統( Try-Cancel/Confirm [5] 實現)來說:
為了讓系統能夠在補償操作徹底清除事務執行的情況,我們會藉助兩階段提交協議來完成這部分的功能。
在TCC方式下,cancel補償顯然是在第二階段需要執行業務邏輯來取消第一階段產生的後果。 try是在第一階段執行相關的業務操作,完成相關業務資源的佔用,例如預先分配票務資源,或者檢查並重新整理使用者的賬戶信用額度。 在cancel階段釋放相關的業務資源,例如釋放預先分配的票務資源或者恢復之前佔用的使用者信用額度。
那我們為什麼還要加入confirm操作呢?
這需要從業務資源的使用生命週期來入手。在try過程中,我們只是佔用的業務資源,相關的執行操作只是出於待定狀態,只有在確認操作執行完畢之後,業務資源才能真正被確認。例如訂票業務的try操作,我們只是佔用了相關的票務資源。目的是防止票務資源被其他使用者佔用,但是業務還沒有執行完畢,票務提供方還不能將被佔用的票務資源統計為已售出票務。 只有相關票務資源被確認售出的之後,票務提供方才能將其統計為已售出票務資源。
ServiceComb Pack架構介紹
通過上面的分析我們可以發現一個有意思的現象,每一步事務的操作都有可能會根據業務的執行情況提供一個補償操作,通過一個事務管理系統來協調這個補償操作可以幫我們大大降低業務流程建模的複雜度。在分散式事務實現過程中, 協調器的作用非常重要, 各個事務的參與方需要跟協調器建立好良好的溝通, 由協調器統一排程完成相關事務的執行或者取消的操作。
ServiceComb Pack架構如下圖所示,主要包含兩個元件,即Alpha和Omega,其中:
Alpha充當協調者的角色,主要負責對事務的事件進行持久化儲存以及協調子事務的狀態,使其最終得以與全域性事務的狀態保持一致,即保證事務中的子事務要麼全執行,要麼全不執行。
Omega是微服務中內嵌的一個agent,負責對監控本地事務執行情況並向Alpha上報事務執行事件,並在異常情況下根據alpha下發的指令執行相應的補償或重試操作。
Omega可以通過向Alpha傳送訊息的方式向Alpha實時傳遞事務執行的進展,但是Alpha怎麼知道這些Omega上傳的訊息是相互關聯的呢? 我們通過在服務呼叫過程中插入唯一的全域性事務ID,並在後續的呼叫其它服務過程中傳遞這個全域性事務ID。通過全域性事務ID可以從彙總到Alpha事件中找到事件與之相關聯的所有事件,通過對這些事件資訊進行分析,我們可以完整地追蹤到與分散式事務執行情況。
Omega會以切面程式設計的方式嚮應用程式注入相關的處理模組,幫助我們構建分散式事務呼叫的上下文。Omega在事務處理初始階段處理事務的相關準備的操作,並在事務執行完畢後做一些清理的操作,例如建立分散式事務起始事件,以及相關的子事件,根據事務執行的成功或者失敗生成相關的事務終止或者失敗事件。這樣帶來的好處是使用者的程式碼只需要新增幾個annotation來描述分散式事務執行範圍,以及與本地的事務處理恢復的相關函式資訊,Omega就能通過切面注入的程式碼追蹤本地事務的執行情況。Omega會將本地事務執行的情況以事件的方式通知給Alpha。由於單個Omega不可能知曉一個分散式事務下其他參與服務的執行情況,如此一來,就需要Alpha扮演一個十分重要的協調者的角色。Alpha將收集到的分散式事務事件資訊整理彙總,通過分析這些事件之間的關係可以瞭解到分散式事務的執行情況。Alpha通過向Omega下發相關的執行指令由Omega執行相關提交或恢復操作,實現分散式事務的最終一致性。
在瞭解過Pack實現的部分細節之後, 我們可以從下圖進一步瞭解ServiceComb Pack架構下,Alpha與Omega內部各模組之間的關係圖。
整個架構分為三個部分:
1 |
一個是Alpha協調器(支援多個例項提供高可用支援)。 |
2 |
二是注入到微服務例項中的Omega。 |
3 |
三是Alpha與Omega之間的互動協議。 |
目前ServiceComb Pack支援Saga 以及TCC兩種分散式事務協調協議實現。 |
Omega包含了與分析使用者分散式事務邏輯相關的模組:
-
事務註解模組(Transaction Annotation)
-
事務攔截器(Transaction Interceptor)
-
分散式事務執行相關的事務上下文(Transaction Context)
-
事務回撥(Transaction Callback)
-
事務執行器(Transaction Executor)
以及負責與Alpha進行通訊的 事務傳輸(Transaction Transport)模組 。
事務註解模組 是分散式事務的使用者介面,使用者將這些標註新增到自己的業務程式碼之上用以描述與分散式事務相關的資訊,這樣Omega就可以按照分散式事務的協調要求進行相關的處理。如果大家擴充套件自己的分散式事務,也可以通過定義自己的事務標註來實現。
事務攔截器 這個模組我們可以藉助AOP手段,在使用者標註的程式碼基礎上新增相關的攔截程式碼,獲取到與分散式事務以及本地事務執行相關的資訊,並藉助事務傳輸模組與Alpha進行通訊傳遞事件。
事務上下文為Omega內部提供了一個傳遞事務呼叫資訊的一個手段,藉助前面提到的全域性事務ID以及本地事務ID的對應關係,Alpha可以很容易檢索到與一個分散式事務相關的所有本地事務事件資訊。
事務執行器 主要是為了處理事務呼叫超時設計的模組。由於Alpha與Omega之間的連線有可能不可靠,Alpha端很難判斷Omega本地事務執行超時是由Alpha與Omega直接的網路引起的還是Omega自身呼叫的問題,因此設計了事務執行器來監控Omega的本地的執行情況,簡化Omega的超時操作。目前Omega的預設實現是直接呼叫事務方法,由Alpha的後臺服務通過掃描事件表的方式來確定事務執行時間是否超時。
事務回撥 在Omega與Alpha建立連線的時候就會向Alpha進行註冊,當Alpha需要進行相關的協調操作的時候,會直接呼叫Omega註冊的回撥方法進行通訊。 由於微服務例項在雲化場景啟停會很頻繁,我們不能假設Alpha一直能找到原有註冊上的事務回撥, 因此我們建議微服務例項是無狀態的,這樣Alpha只需要根據服務名就能找到對應的Omega進行通訊。
事務傳輸模組 負責Omega與Alpha之間的通訊,在具體的實現過程中,Pack通過定義相關的Grpc描述介面檔案定義了TCC 以及Saga的事務互動方法, 同時也定義了與互動相關的事件。我們藉助了Grpc所提供的雙向流操作介面實現了Omega與Alpha之間的相互呼叫。 Omega和Alpha的傳輸建立在Grpc多語言支援的基礎上,為實現多語言版本的Omega奠定了基礎。
-
-
Alpha為了實現其事務協調的功能,首先需要通過 事務傳輸(Transaction Transport) 接收Omega上傳的事件, 並將事件存在 事件儲存(Event Store) 模組中,Alpha通過 事件API(Event API) 對外提供事件查詢服務。Alpha會通過 事件掃描器(Event Scanner) 對分散式事務的執行事件資訊進行掃描分析,識別超時的事務,並向Omega傳送相關的指令來完成事務協調的工作。由於Alpha協調是採用多個例項的方式對外提供高可用架構, 這就需要 Alpha叢集管理器(Alpha Cluster Manger) 來管理Alpha叢集例項之前的協調。使用者可以通過 管理終端(Manage console) 對分散式事務的執行情況進行監控。
目前Alpha的事件儲存是構建在資料庫基礎之上的。為了降低系統實現的複雜程度,Alpha叢集的高可用架構是建立在資料庫叢集基礎之上的。 為了提高資料庫的查詢效率,我們會根據全域性事務 的 執行情況將資料儲存分成了線上庫以及存檔庫,將未完成的分散式事務事件儲存在線上庫中, 將已經完成的分散式事務事件儲存在存檔庫中。
事件API 是Alpha對外暴露的Restful事件查詢服務。 該模組功能首先應用在 Pack的驗收測試 [6] 中,通過事件API驗收測試程式碼可以很方便的瞭解Alpha內部接收的事件。驗收測試通過模擬各種分散式事務執行異常情況(錯誤或者超時),比對Alpha接收到的事務事件來驗證相關的其他事務協調功能是否正確。
管理終端 是一個js的前端介面, 管理終端通過訪問事件API提供的Rest服務,向用戶提供分散式事務執行情況的統計分析,並且可以追蹤單個全域性事務的執行情況,找出事務的失敗的根源。在Pack 0.3.0 中實現了一部分功能,後續還需要進一步完善,歡迎大家參與進來。
Alpha叢集管理器 負責Alpha例項註冊工作,管理Alpha中單個服務的執行情況, 並且為Omega提供一個及時更新的服務列表。 通過叢集管理器使用者可以輕鬆實現Alpha服務例項的啟停操作,以及Alpha服務例項的滾動升級功能。目前這部分的模組還在設計開發中,歡迎對此有興趣的朋友加入到我們的開發隊伍中來。
小結
本文從分散式事務需要解決的問題入手,向大家介紹了建立在補充基礎之上的基於服務的分散式事務的解決思路。接下來我們結合具體的示例介紹了完美的補償(TCC)和非完美補償(Saga)兩種分散式事務協調協議,最後結合ServiceComb Pack的實現原理詳細介紹了ServiceComb Pack的架構實現。
在基於服務的分散式事務下篇中,我們將結合具體的示例向大家介紹TCC以及Saga分散式事務協調協議的互動細節,以及如何使用ServiceComb Pack編寫TCC 以及Saga 應用。
參考連結詳情
[1]ACID
https://en.wikipedia.org/wiki/ACID
[2]一致性協調操作 https://www.enterpriseintegrationpatterns.com/patterns/conversation/CoordinatedAgreement.html
[3]兩階段提交
https://zh.wikipedia.org/wiki/%E4%BA%8C%E9%98%B6%E6%AE%B5%E6%8F%90%E4%BA%A4
[4]Saga
https://microservices.io/patterns/data/saga.html
[5]Try-Cancel/Confirm
https://dzone.com/articles/transactions-for-the-rest-of-us
[6]Pack的驗收測試
https://github.com/apache/servicecomb-pack/tree/master/acceptance-tests
長按二維碼關注我們
你點的每個贊,我都認真當成了喜歡