在單體應用向微服務架構轉型的過程中,本地事務已不再滿足系統一致性需求,為了解決這一問題,前人在對性能和數據一致性反覆權衡的過程中總結了許多典型的協議和算法,各有優劣。本文我們將深入探討 Freewheel 如何實現無單點故障的可擴展分布式事務實現模型。
為什麼需要分布式事務?
當應用程式有嚴格的數據一致性要求時,ACID 事務是必須的,如果一個事務涉及的所有操作能夠放在一個服務內部,且共用一個資料庫,那麼只用在一個方法裡同一個事務下操作資料庫即可。然而為了提升系統整體的可靠性,方便各個模塊獨立演化,系統從單體應用演進為微服務架構。隨著數據體量的增長,數據源也從 Mysql 擴展到關係型資料庫 Amazon Aurora 和 NoSQL 資料庫(Amazon DynamoDB),基於多樣化索引和查詢數據的需求,引入了搜素引擎(ApacheSolr 和 ElasticSearch ) ,多服務交互、多數據源並存產生了分布式事務。
Freewheel 分布式事務應用場景有三個:
- 多服務,同數據源: 業務單元跨越多個獨立服務,服務訪問同一個數據源,如 MySQL。
- 單服務,不同數據源: 業務單元涉及一個獨立服務,但這個服務訪問多個數據源,如 MySQL,DynamoDB。
- 多服務,不同數據源: 業務單元跨越多個獨立服務,每個服務訪問不同數據源,如 MySQL,DynamoDB。
綜合考慮 Freewheel 的業務需求後,我們實現了多引擎資料庫分布式事務。
多引擎資料庫分布式事務設計
Freewheel 分布式事務方案主要設計目標如下:
數據強一致性:確保該事務範圍內的所有操作都可以全部成功或者全部失敗,事務具有原子性、一致性、隔離性、持久性 4 個特性。
- Atomicity(原子性):一個事務中的所有操作,要麼全部完成,要麼全部不完成,不會結束在中間某個環節。事務在執行過程中發生錯誤,會被恢復到事務開始前的狀態,就像這個事務從來沒有執行過一樣。
- Consistency(一致性):在事務開始之前和事務結束以後,資料庫的完整性沒有被破壞。完整性包括外鍵約束、應用定義等約束不會被破壞。
- Isolation(隔離性):資料庫允許多個並發事務同時對其數據進行讀寫和修改的能力,隔離性可以防止多個事務並發執行時由於交叉執行而導致數據的不一致。
- Durability(持久性):事務處理結束後,對數據的修改就是永久的,即便系統故障也不會丟失。
系統高可用:遵循「design for failure」的設計原則,硬體層面,採用服務節點多 region 多 AZ 部署和節點故障快速自恢復的策略來保證系統的高可用。軟體層面,設計 failover 機制應對服務異常。
可擴展:應用 Auto Scaling 服務,它會基於設定的負載壓力,自動進行擴展和縮容,來保證服務正常運行。
易用性:分布式事務應用 API:易學,易懂,易記,系統設計上無業務侵入,沒有額外的編碼或測試工作。
多引擎資料庫分布式事務技術選型
常見分布式事務解決方案對照表:
方案 |
優點 |
缺點 |
XA |
|
|
兩階段提交 |
|
|
補充事務(TCC) |
|
|
Saga |
|
|
Seata |
|
|
結合 Freewheel 強一致性業務需求,多數據源分布式事務將由 XA、2PC 和 Seata 這些解決方案組合而成。
- Seata 框架設計思想
- 基於 XA 的 Aurora 分支事務
- 基於 2PC 的 DynamoDB 分支事務
多數據源分布式事務解決方案
架構解析
Freewheel 分布式事務依託在 Freewheel 數據訪問層中間件(DAL)上,這個中間件是由 Freewheel 平台團隊自主研發的,它的目標是為上游應用提供更好的數據訪問,為下游數據源提供更好的保護。為了方便描述,下文均用 DAL 來作為 Freewheel 數據訪問層中間件的簡稱。
分布式事務由這三個組件來協商處理:
- Transaction Coordinator (TC):事務協調器,維護全局事務的運行狀態,負責協調並驅動全局事務的提交或回滾。
- Transaction Manager (TM):控制全局事務的邊界,負責開啟一個全局事務,並最終發起全局提交或全局回滾的決議。
- Resource Manager (RM):控制分支事務,負責分支註冊、並接收事務協調器的指令,驅動分支(本地)事務的提交和回滾。
為了預防死鎖,並且減少 DAL RM 和 TC 之間的 交互,降低對 TC 的依賴,同一個分布式事務操作放在同一個 DAL 節點,由此,DAL RM 可以方便的在單節點控制和協調分支事務,完成全局事務的提交和回滾。
以多服務,不同數據源(Aurora 與 DynamoDB)為例,描述 Freewheel 分布式事務過程。
- A Service TM 向 TC 申請開啟一個全局事務,全局事務創建成功並生成一個全局唯一的 XID。
- XID 在微服務調用鏈路的上下文中傳播。
- TM 向 TC 發起針對 XID 的全局提交或回滾決議。
- TC 向 DAL RM 發起全局提交或回滾決議。
- DAL RM 對 XID 下管轄的全部分支事務完成提交或回滾請求。
數據訪問層資源管理器(DAL RM)實現
基於業務需求,DAL 分布式事務支持的數據源為 MySQL 和 AWS DynamoDB,下面章節闡述了這兩個數據源 ACID 技術實現。
分布式事務設計中新建了事務控制表、事務記錄表、索引表及業務鏡像表:
- 事務控制表:記錄事務運行狀態開始、提交、回滾。
- 事務記錄表:存儲正在發生事務信息。
- 索引表:記錄記錄主鍵 ID, 用於實現排它性對其他事務更新與普通更新。
- 業務鏡像表:存儲業務原值。
Aurora/MySQL
採用 MySQL XA 2PC 來保證 ACID,原因如下:
- Mysql 5.7 版本已經支持 XA,目前 Aurora 也是 Mysql 5.7.x。
- XA 強一致性。
- 不侵入業務,這會減少業務方的工作量。
這個時序圖描述了 RM 對 MySQL 事務的工作流程:
一個事務操作,由同一個 DAL RM 處理,相同 DB 下業務事務處理,放在一個 XA 操作里:
- 節省 XA 連接 fd。
- 減少 DAL RM 和 Aurora 之間的 XA 交互次數。
- 避免多個 XA 分支事務上的數據操作衝突。
SQL CRUD 語句應該使用觸發行鎖的索引操作,否則會觸發表鎖,影響系統吞吐量。
AWS DynamoDB
DynamoDB 提供了本地事務接口 TransactGetItems 和 TransactWriteItems, 它等效於 MySQL 批量操作,對於相互間有上下文或者依賴的操作並不可用,這限制了它在應用中的使用場景,詳細信息請參考 TransactGetItems 和 TransactWriteItems 。
DynamoDB 本身沒有分布式事務機制,DAL 結合 DynamoDB 功能屬性,對提供的插入、更新、刪除和查詢接口,設計 2PC 機制 來滿足 DynamoDB 的 事務屬性。
下表顯示了分布式事務操作 (DisTxDAL) 和其他操作之間的隔離級別。
Operation |
Other Operation |
Isolation Level |
DisTxDAL |
DisTxDAL |
可序列化 |
DisTxDAL |
DAL DDB write interface |
可序列化 |
DisTxDAL |
DAL DDB read interface |
讀取已提交|讀取未提交 (可選) |
更新接口實現方法
一階段
- 應用本地事務原子地備份事務記錄及備份索引
- 應用本地事務原子地更新附加事務信息業務值及備份業務原值到鏡像表
二階段
- 如果決議是提交,應用本地事務原子地移除業務記錄事務屬性、刪除鏡像記錄、刪除備份事務記錄
- 如果決議是回滾,應用本地事務原子地恢復業務記錄、刪除鏡像記錄、刪除備份事務記錄
插入接口實現方法
一階段
- 應用本地事務原子地備份事務記錄及備份索引
- 插入帶有事務屬性信息的業務記錄
二階段
- 如果決議是提交,應用本地事務原子地移除業務事務屬性、刪除備份記錄
- 如果決議是回滾,應用本地事務原子地刪除業務記錄、刪除備份記錄
刪除接口實現方法
一階段
- 應用本地事務原子地備份事物記錄及備份索引
- 更新帶有事務屬性信息的業務記錄, 標註為刪除操作
二階段
- 如果決議是提交,應用本地事務原子地刪除事務記錄、刪除業務記錄
- 如果決議是回滾,應用本地事務原子地恢復業務記錄、刪除備份記錄
查詢接口實現方法
事務進行中的數據含有事務屬性信息,xid 表示事務全局事務 ID, operation 表示事務操作接口 create、update、delete,這裡 item 表示業務數據元素。
操作接口 |
事務開始 |
事務進行 |
事務結束(提交) |
事務結束(回滾) |
create |
None |
item: item value operation:create xid: xid value |
item: item value operation:create xid: value |
None |
update |
item: item value |
item: item new value operation:update xid: xid value |
item: item new value operation:update xid: xid value |
item: item value |
delete |
item: item value |
item: item value operation:delete xid: xid value |
None |
item: item value |
基於業務數據變更表及寫接口實現方法,實現了在讀提交與讀未提及查詢方法:
- 判斷記錄是否含有事務屬性,如果無,返回記錄,否則到步驟 2
- 判讀隔離級別,如果讀提交,步驟 3,如果讀未提交,步驟 5
- 記錄事務操作是 create,返回空,否則如果是 delete,去除事物屬性信息,然後返回,否則步驟 4
- 本地事務原子地讀取鏡像表與業務表,如果鏡像表值存在,返回,否則返回業務表值,都不存在返回空
- 如果記錄事務操作是 delete,返回空,否則返回記錄
數據訪問層事務管理器(DAL TM)實現
為了方便用戶使用,分布式事務 API 里封裝了與事務協調器及 DAL 資源管理器的交互過程,交互過程對應用是透明的,下面是分布式事務 API:
type DistributedTransApi interface {
DisTxDAL(ctx context.Context, fn TranFunc) error
}
type TranFunc func(ctx context.Context) error
複製代碼
微服務之間,微服務與資料庫訪問層之間採用 google rpc 調用,服務之間關鍵數據都是基於 context metadata,如果經過兩層服務交互,就會導致 context metadata 丟失。舉例來說,A 服務調用 B 服務,B 服務調用 C 服務,那麼 C 服務就會缺失 A 服務 context metadata,針對這種情況,DAL 提供了通用函數用於提取 DAL 相關的元數據,供應用方按需添加。
func ExtractDalMetadata(ctx context.Context) (metadata.MD, error)
複製代碼
數據訪問層事務協調器(DAL TC)實現
協調器主要功能點:
- 分配事務 XID,維護全局事務的運行狀態,負責協調和驅動全局事務的提交或回滾;
- 故障轉移:提交/回滾。
分配事務 XID
全局分配唯一事務 ID,供 DAL TM 獲取,此數據需要在同一事務的業務服務間傳輸共享。
事務協調
維護全局事務的運行狀態,負責協調和驅動全局事務的提交或回滾。
故障轉移
DAL TC 是 HA 多節點實例,引入 ETCD leader 選舉機制來保證只有一個 TC 實力承擔 failover 功能,詳細信息在系統高可用章節軟體層面 failover。
系統高可用
硬體層面
為了預防硬體故障對高可用的影響,DAL,TC 和 ETCD 服務均是多 Region 多 AZ 部署,並且基於服務的特性配置了相應的服務策略:
- ETCD 採用自恢復策略
- DAL 和 TC 服務採用了彈性伸縮策略
美東美西均部署相應服務作為服務災備策略,下圖是美東地區的解釋圖(美西類似)。
軟體層面 Failover
在 DAL 應用或者業務應用遇到異常退出時,軟體層面 Failover 機制是為了能不發生死鎖,並且繼續處理未完成分布式事務,實現方法如下:
- DAL TM 接口添加超時控制,由應用設置事務的超時時間,默認是 60 秒
- DAL RM 在事物開始、提交或者回滾階段存儲 xid 信息,如過期時間,運行狀態等,在業務接口調用里存儲業務處理記錄
- DAL TC 輪詢地從事務控制表里獲取超時事務。基於事務狀態處理:如果 start or rollback:觸發 Rollback,如果 commit:觸發 commit
原文連結:
https://www.infoq.cn/article/vo46BP1DR6MAJzWOPOmf