引言:分佈式事務是分佈式數據庫的基礎性功能,在2017年上海MySQL嘉年華(IMG)和中國數據庫大會(DTCC2018)中做者都對銀聯UPSQL Proxy的分佈式事務作了簡要介紹,受限於交流形式難以作全面細緻的探討,藉由本文進一步展開。前端
UP-2PC是面向分佈式數據庫的由中國銀聯自主研發的針對MySQL的2PC分佈式事務實現,以UPSQL Proxy(分佈式式數據庫代理)做爲事務管理器,UPSQL(MySQL銀聯定製版本)爲資源管理器。mysql
因爲MySQL在5.7中完全解決了xa prepare的高可用問題以及鏈接關閉自動回滾等問題,使得MySQL的xa真正可用。sql
若是瞭解分佈式事務與2PC,並考慮過以下議題(沒必要承認),可略過本節內容:數據庫
• TCC是應用層的2PC實現編程
• 2PC的實現難點在於事務管理器(TM)的異常處理安全
• 2PC與Paxos:Paxos用於相同數據的分佈式高可用存儲,2PC更擅長不一樣數據分佈式存儲的事務一致性性能優化
2PC協議(Prepare,Commit, Rollback)服務器
首先咱們要提到分佈式事務模型:X/Open DTP(Distributed Transaction Processing) Reference Model。網絡
• X/Open組織定義的一套分佈式事務的標準,定義了規範和API接口,由廠商進行具體的實現併發
• X/Open DTP定義了三個組件: AP,TM,RM
• AP(Application Program):使用分佈式事務的應用
• RM(Resource Manager):資源管理器,這裏能夠是一個DBMS系統,或者消息服務器管理系統,資源管理器必須實現XA定義的接口
• TM(Transaction Manager):事務管理器,負責協調和管理事務
一般把TM管理的分佈式事務稱爲全局事務,把RM管理的事務稱爲本地事務。
兩階段提交,是X/Open DTP的一個實現,主要在事務結束動做前加入了一個Prepare階段:
• Prepare階段:Prepare成功的數據,後續Commit必定會成功。
• Commit/Rollback階段:提交或回滾數據
圖例:2PC協議
DTP很早就指出資源管理器的類型不僅僅是數據庫,還能夠是消息服務。
TCC協議(Try, Confirm, Cancel)
TCC編程: 將業務邏輯拆解爲Try、Confirm和Cancel三個階段,實際上兩階段提交的變種。業務場景如:鎖單、下單和取消。
TCC編程須要保證冪等性,即:能夠被不斷調用接口(直至符合預期),於是須要保證屢次調用不會致使異常影響(如重複扣款等)。2PC的主要問題
2PC協議,主要是在以下場景的處理較爲複雜:
• 同步阻塞、應答超時
• TM宕機恢復
比較容易取得共識的結論:不一樣業務系統之間使用2PC。
那剩下的問題就簡單了, 相同業務系統之間是使用數據訪問層2PC仍是TCC?通常而言,基於研發成本考慮,會建議:新系統由數據庫層來實現統一的分佈式事務。
但對熱點數據,例如商品(票券等)庫存,建議使用TCC方案,由於TCC的主要優點正是能夠避免長時間鎖定數據庫資源進而提升併發性。
有一種廣爲流傳的觀點:"2PC到3PC到Paxos到Raft",即認爲:
• 2PC是Paxos的殘次版本
• 3PC是2PC的改進
上述2個觀點都是我所不認同的,更傾向於以下認知:
• 2PC與Paxos解決的問題不一樣:2PC是用於解決數據分片後,不一樣數據之間的分佈式事務問題;而Paxos是解決相同數據多副本下的數據一致性問題。例如,UP-2PC的數據存儲節點可使用MGR來管理統一數據分片的高可用
• 3PC只是2PC的一個實踐方法:一方面並無完整解決事務管理器宕機和資源管理器宕機等異常,反而由於增長了一個處理階段讓問題更加複雜
MySQL提供的2PC語法(MySQL XA)以下:
XA {START|BEGIN} xid [JOIN|RESUME]
XA END xid [SUSPEND [FOR MIGRATE]]
XA PREPARE xid
XA COMMIT xid [ONE PHASE]
XA ROLLBACK xid
XA RECOVER [CONVERT XID]
圖例:MySQL XA語法
咱們針對語法組合方式略微展開。
2PC提交流程爲:
XA {START|BEGIN} xid [JOIN|RESUME]
/*事務操做*/
XA END xid [SUSPEND [FOR MIGRATE]]
XA PREPARE xid
XA COMMIT xid
2PC回滾流程爲:
XA {START|BEGIN} xid [JOIN|RESUME]
/*事務操做*/
XA END xid [SUSPEND [FOR MIGRATE]]
XA PREPARE xid /*可選*/
XA ROLLBACK xid
注意回滾時Prepare階段不是必須的,因此能夠在以下場景中能夠省略Prepare階段,減小網絡交互:
• 本地事務未發生寫操做
• 本地事務須要回滾
固然沒有Prepare階段也就意味着,其從兩階段退化爲一階段了。
2PC退化爲一階段提交
XA語法下退化爲一階段提交的流程爲:
XA {START|BEGIN} xid [JOIN|RESUME]
/*事務操做*/
XA END xid [SUSPEND [FOR MIGRATE]]
XA COMMIT xid ONE PHASE
使用XA的一階段提交(XA COMMIT xid ONE PHASE)的一個典型場景爲在全局事務中只有該本地事務有寫操做,在UP-2PC由於使用"最後參與者"方案,因此最後參與者也會使用XA一階段提交。
這裏指當與MySQL的交互出現異常,例如與MySQL的會話斷開、MySQL宕機等,如何獲取並恢復處於Prepare狀態下的2PC事務,並進行提交或回滾處理。
mysql> XA RECOVER;
UP-2PC要解決的核心問題正是如何處理:事務管理器、資源管理器異常,以及可能的網絡。
• 事務管理器(TM)忽然宕機以及網絡異常,致使協調信息丟失。典型的:異常發生後,處於Prepare狀態的RM,正確進行後續處理(rollback和commit)
• 部分資源管理器(RM)忽然宕機以及網絡異常,致使的信息協調。典型的:全局事務內多個Prepare狀態,此時若部分RM發生本異常,則TM如何處理,可選方案:
1.全局事務向請求者應答成功,由事務事務管理器後續提交異常RM
2.回滾全部RM,並向前端應答事務提交失敗
經過上述探討,能夠發現2PC的主要難點在於:
• 事務狀態信息維護:全局事務狀態(TM)、本地事務狀態(RM)
• 根據事務狀態信息的異常處理策略
毫無疑問事務狀態信息不丟失,是實現安全2PC的基礎,因爲本地事務狀態信息能夠經過MySQL高可用集羣解決,因此咱們僅須要:保證全局事務狀態(TM)不丟失。
在給出UP-2PC的方案以前,咱們能夠先了解下已有的分佈式事務工程實踐經驗,「Distributed transactions in Spring, with and without XA」(Spring的分佈式事務,使用或不用XA;Spring分佈式事務的7種實現)是一篇不錯的文章,很好總結了既有實踐經驗,這裏僅對其中涉及XA的兩種優化方案進行說明:
• 一階段提交優化:若是全局事務只涉及一個RM,則不使用兩階段
• 最後參與者優化(Last Participant Support):選擇一個RM(最後參與者)不使用兩階段提交,全局事務提交時,先對」最後參與者」進行(一階段)提交.該策略又被稱爲:最後資源提交優化(Last Resource Commit Optimization)。
圖例:最後參與者優化
乍看起來「最後參與者優化」方案效果有限,但事實上該方案直接指明瞭UP-2PC的實現路徑,由於:
1.「一階段提交優化」能夠視爲「最後參與者優化」的特例,即只全局事務只包含一個RM時二者等效
2.「最後參與者優化」指出了最後參與者在全部RM中最爲重要,其事務提交結果決定着全局事務的結果,而前面也提到了全局事務狀態是最重要的,因此咱們天然推導出應該由最後參與者記錄全局事務狀態(咱們稱爲xa-log)
UP-2PC核心方案,包含3點:
1.利用RM節點分佈式存全局事務狀態信息(xa-log)
2.使用最後參與者協議
3.由最後參與者記錄全局事務狀態信息(xa-log)
xa-log中記錄了全局事務id所涉及的全部資源管理器節點列表,和對應的本地xid,以及全局事務狀態(commit|rollback)。
這裏僅對全局事務須要進行commit作下說明,若是最後參與者commit成功,則認爲全局事務commit成功,若最後參與者commit成功而其餘資源管理器異常,則交由異常處理程序輪詢xa-log表完成其餘資源管理器的xa commit操做。
最後參與者的選擇策略:若是第一個本地事務參與者若是發生了寫操做,則選擇其爲最後參與者;不然在其餘發生了寫操做的參與中隨機選擇。該選擇策略實現了最後參與者的分佈式隨機選擇,進而保證xa-log的分佈式隨機分佈。
圖例:UP-2PC實現
如圖所示,UP-2PC的實現要點在於:
1.全局事務提交,則由最後參與者記錄xa-log
2.最後參與者提交前,須要保證其餘參與則處於prepare狀態
3.在全部參與者事務提交結束後,刪除xa-log
在確保全局事務狀態(xa-log)安全保存後,就能夠針對2PC的各類異常進行處理。
不考慮TM異常,將異常按時間線進行區分:
1.全局事務commit以前:由於沒有執行本地xa和xa-log的相關操做,則按事務提交失敗處理
2.全局事務執行commit階段:
– xa-log記錄與prepare階段異常:則回滾全部本地事務
– 最後參與者commit異常:則查詢xa-log,如xa-log有全局事務的com mit記錄,則視爲事務提交成功;沒有則視爲提交失敗;查詢結果不肯定 (如最後參與者宕機等),則事務狀態不肯定,能夠考慮斷開與AP鏈接(即不該答AP事務狀態), 或保持鏈接直至獲得查詢結果未知。
– 其餘參與者commit異常:依然應答全局事務成功,交由後續異步commit。
其餘異常及異常處理: 以xa-log記錄爲準,對比xa-log與RM的xa-id信息進行對比分析進行提交或回滾操做。
須要指出的是因爲TM是集羣多機部署,因此xaid在編碼時須要能區分不一樣TM節點信息。
在已公開的同類產品中TDSQL與UP-2PC的解決思路最爲接近,這裏作下簡單對比。
圖例:TDSQL與UP-2PC對比
注:示例來自TDSQL的公開介紹材料,爲簡化描述略做修改
UP-2PC相對TDSQL的分佈式事務解決方案主要區別在於UP-2PC採用了最後參與者策略,由最後參與者進行xa-log記錄。
優勢:減小啓用一個數據庫會話、一次網絡交互、減小一個事務、退化一個2PC事務爲一階段
缺點:業務數據庫用戶須要具備xa-log的讀寫權限,於是具備運維風險,特別是在複用數據庫實例的狀況下
一次進一步展開思考,TDSQL不使用最後參與者策略的理由可能以下:
• 做爲雲上產品,應儘量避免運維風險
• XA業務場景佔比較少,性能影響有限
• 方便未來xa-log由其餘方式存儲
在實現UP-2PC方案後,XA分佈式事務的性能影響大約爲30%(依賴場景),雖然符合預期,但也說明須要進一步優化。
這裏給出UP-2PC兩個性能優化方案:
• 本地事務啓動優化
• MySQL XA協議優化
對於屬於不可重複讀的全局事務,咱們能夠延遲開啓本地事務以減小資源鎖定時間。
本地事務啓動優化的核心策略:
1.全局事務爲不可重複讀
2.本地事務只有發生了寫才須要開啓本地XA事務
針對以下業務場景:
/*事務隔離級別爲read-commited或read-uncommited*/
Begin;
select db1 and db2; //1st
update db1;
update db2;
select db1 and db2; //2nd
Commit;
咱們推演下全局事務管理器對上述語句的處理邏輯:
1.當第一次執行查詢操做時,因爲db1和db2都未發生寫操做,則沒必要開啓本地事務,在查詢結束後能夠將本地事務會話歸還鏈接池。
2.在遇到第一次寫入操做時,分別開啓對應的本地事務管理器。
3.在後續查詢時,因爲已持有本地事務鏈接,則使用該鏈接進行查詢操做。
而實際的場景中,可能操做以下:
Begin;
select db1 and db2;
update db1;
select db1 and db2;
Commit;
即全局事務內,查詢的節點數量多於更新的節點數量,經過本方案只會對寫入數據庫節點開啓本地XA事務,從而避免了沒必要要的事務開啓操做。
在實現UP-2PC的過程當中咱們一直有這樣一個設計指望:在全局事務只涉及一個本地事務,咱們可否對該事務不啓用xa協議?
在咱們實現「本地事務啓動優化」後,這個問題視乎解決了,由於讓最後參與者不使用XA協議就能夠輕鬆實現了上述目標,但最後參與者不使用XA協議可能會致使咱們沒法解決後面說起的分佈式死鎖問題。
因而咱們轉換思路,對咱們的設計目標的本質進行剖析,即XA一階段提交與傳統事務提交有什麼區別?
答案:多了一個xa end。
因而XA協議優化方案也就呼之欲出,即省略掉沒必要需要的XA END。
圖例:UP-2PC優化方案
如圖所示,經過減小了xa end階段,減小了交互次數天然提升了系統健壯性,也提升了性能。
在實現了基於2PC的分佈式事務,對外提供統一事務管理特徵,也會引入一些新問題,例如:
• 分佈式事務死鎖
• 對外XA協議支持(外部2PC)
• Savepoint支持
這裏僅對部分解決方案進行說明。
因爲分佈式事務管理多個RM,因此必然會帶來分佈式資源競爭和分佈式死鎖。例如:
• 會話a 和 b 開啓分佈式事務
• time 1: a write db1.resource1
• time 2: b write db2.resource2
• time 3: a write db2.resource2 -> 鎖:a 等待b的 db2.resource2
• time 4: b write db1.resource1 -> 鎖:b 等待a的 db1.resource1
在時間點4, a和b發生了資源循環等待,從而出現了分佈式死鎖。
如何解決分佈式死鎖呢,這裏提供一種死鎖檢測方法:
• 擴展innodb_trx表,增長事務xid的記錄
• TM層確保同一全局事務所對應的本地事務xid相同
這樣就能夠對全部全局事務的鎖信息進行分析,並發現分佈式死鎖。
下表爲擴展innodb_trx的效果,因爲代碼修改較爲簡單這裏就不累述。
另外一種方法是在本地事務中加入事務時間信息,在RM層根據時間前後順序,保證只會存在單一時序的資源等待,從而避免死鎖發生。
若是咱們將應用UP-2PC的數據庫集羣視爲一個總體,則實現分佈式事務的過程爲內部2PC,對外部應用提供2PC協議支持(即支持XA協議),則稱爲外部2PC。數據集羣的內部外部2PC,能夠與MySQL的內部外部2PC對應理解。
該邏輯較爲簡單,即在外部執行XA PREPARE時,咱們在xa-log中記錄全局事務狀態爲Prepare便可,須要指出的是:外部2PC下,內部的分佈式事務沒法使用前述的最後參與者優化方案。
圖例:外部兩XA協議實現
本文介紹了UP-2PC的具體實現,涵蓋了核心解決方案與異常處理策略、性能優化、帶來的新問題與解決方案。