1、基本知識概念
(一)數據庫事務4大特性:ACID
數據庫事務須要實現下面4大特性:sql
- A-Atomicity-原子性,指的是事務要麼成功提交,要麼失敗回滾
- C-Consistency-一致性,指事務操做先後,數據庫的完整性不會產生變化,由於事務不可能執行一半,致使數據庫內容不正確
- I-Isolation-隔離性 事務不能互相影響,簡單來講就是事務的中間狀態不該該被其餘事務察覺
- D-Durability-持久性 一旦事務提交則會永久保存在數據庫中
其中,只有隔離性根據數據庫不一樣的隔離級別有不一樣的標準,其餘三點都是數據庫事務必須具有的。數據庫
(二)分佈式事務
單機事務機制能夠有數據庫本地事務實現良好的控制,但分佈式事務因爲涉及多個數據源。這種狀況主要有兩種:緩存
- 一種是分庫分表致使了一臺服務器多數據源,這種狀況能夠經過二階段提交實現簡單的事務控制。
- 一種是應用的微服務化,一個請求由多個服務協做完成,而多個服務又配置了不一樣的數據源。這種狀況若是是支付等嚴格要求數據一致性的場景能夠用TCC事務模型控制,若是是要求併發性能,但對數據一致性要求不高能夠用最終一致性方案控制。
在介紹事務控制模型以前先介紹一下針對分佈式事務,咱們須要關注的指標:服務器
1.CAP理論
- C-Consistency -一致性,一個節點數據更新,其餘節點保存的數據也應該同步更新
- A-Availability-可用性,節點收到請求,合理時間內必須返回基於請求合理的響應
- P-Partition tolerance-分區容錯性 可以容許部分節點網絡故障 CAP理論表示這三個指標必然不能同時知足,最多隻能知足其中兩個。這裏簡單推論一下:
同時知足這三個指標的狀況至關於認可當一個節點沒法與其餘節點通訊的時候,還能保證在合理時間返回與其餘節點一致的合理響應。這顯然是不可能的。由此能夠得出3個指標不能同時知足。網絡
由於P是分佈式的基礎,咱們設計分佈式系統不可能不考慮P,若是不考慮P那麼這個分佈式系統是極其不穩定的,因此針對CAP理論,通常選擇知足CP或AP。架構
- 選擇CP意味着當出現部分節點通訊掛了,爲了一致性必須阻塞請求,等待數據同步。
- 選擇AP意味着當出現部分節點通訊掛了,爲了可用性,針對請求,節點只能返回一個不能保證一致性的返回給請求方。
順帶一提,基於此ZooKeeper集羣是注重的是CP,當50%的節點掛了以後,那服務發現功能也就掛了。EureKa集羣注重CA,註冊中心只要有一臺可以使用,則不會影響正常的服務發現,另外客戶端還會緩存服務、消費者數據。併發
2.BASE理論
BASE理論算是對CAP理論的一種指導性意見。框架
- BA-Basically Available-基本可用,分佈式系統出現不可預料故障的時候,要儘可能將故障減少到不影響核心功能。
- S-Soft state-軟狀態,容許部分節點的數據存在必定的延時,這個延時不影響可用性,這是對強一致的一種妥協,由於真正的強一致是全部節點數據時時刻刻都保持一致,而CAP理論要求的C忽略了網絡通訊延遲。
- E-Eventually consistent -最終一致性,最終一致是指通過一段時間後,全部節點數據都將會達到一致。
整體上來講BASE理論強調的是用最終一致代替了CAP理論的強一致,用基本可用代替了CAP理論的徹底可用。異步
2、分佈式事務解決方案模型
(一)二階段提交-2PC- XA Transactions
1.角色
在二階段提交中涉及的角色有兩個:分佈式
- 事務管理器,指的是調度者,通常來講是單點服務器。
- 本地資源管理器,通常指的是數據庫服務,如Oracle,Mysql。
2.應用場景
單點應用,多數據源狀況下。
3.原理
原理主要是利用了本地資源管理器自帶的事務機制。
4.過程
- 第一階段,線程開啓每一個數據源的事務並執行業務邏輯,並阻塞等待每一個數據源undo,redo日誌寫入完畢,就差提交
- 第二階段,若是每一個數據源都沒有發生異常,表示能夠提交,則線程commit全部數據庫的事務,若是有數據源異常了,則回滾全部事務。
強調一點,二階段提交主要針對於,單點服務器多數據源,若是是多點服務,將引入事務管理器之間的分佈式問題,因此二階段提交模型只適用於單點服務,多數據源,強烈依賴數據庫的事務支持。
5.缺點
缺點主要有:
- 單點問題,若是事務管理器宕機,資源管理器將阻塞而且鎖住資源
- 同步阻塞,準備就緒到事務管理器發送請求這段時間對於資源管理器來講是沒必要要的阻塞與佔用資源時間(資源管理器經過鎖機制保證隔離性),對於性能上是一種浪費。
- 沒有徹底解決數據不一致,不能保證事務管理器發出的commit命令操做正確到達本地資源管理器,若是通訊失敗了,事務管理器是沒法察覺的,結果仍是不一致的。
(二)TCC模型
1.角色
涉及的角色有微服務應用,不一樣數據源,同一個TCC管理框架,之因此要引用TCC管理框架的緣由在於對於Try操做失敗成功的感知,若是涉及微服務調用鏈是極其複雜的。
1.應用場景
微服務應用,不一樣數據源,不要求高併發,一致性要求較高。
2.過程
(1) 第一階段-Try
根服務調用各個服務的Try方法,各個服務根據業務鎖定資源-業務層面上的,沒有引入數據庫事務,就是將業務涉及的資源存入各個服務的本地方便以後回滾的時候恢復,而後返回給根服務是否執行成功。
(2) 第二階段-Confirm或Cancel
- Confirm,根服務獲取各個服務try是否執行成功,若成功則調用各個服務的Confirm方法,confirm開始把鎖定的資源正式刷到業務數據中,若是confirm不成功,則一直重試,務必保證confirm成功。
- Cancel,若是各個服務的try有一個不成功,則調用各個服務的cancel方法,釋放鎖定的資源,若是調用不成功,也會重試。
注意:Try階段成功意味着數據庫層面的操做大機率是沒有問題的,後續的異常只會發生在極端狀況下,因此這是tcc根據try操做,去不停重試confirm或cancel的緣由。
3.缺點
- confirm和cancel方法由於有可能被重複調用,因此得從代碼上保證冪等,對代碼侵入性有點強,複雜度搞。
- 因爲整個TCC過程須要鎖定資源因此對高併發場景不太友好。
(三)本地消息表方案模型
1.角色
2.思路
不關注MQ丟不丟消息,只關注根服務的本地事務,若是本地事務執行成功,那麼記錄所需發送的消息表也被插入成功。
此時惟一須要關注的只是消息可以被消費者消費掉,用輪詢本地消息表的方式重複發送消息保證最終消息一致性便可。
3.具體方案
根服務完成事務後,將須要與其餘服務協調的請求消息存入本地消息表並將狀態置爲待確認。而後有一個後臺線程定時輪詢消息表,將待確認消息推送給MQ,MQ再推送給消費者,消費者收到消息後,再消費消息。當消費完消息後,消費者經過必定方式如zookepper或接口回調根服務,而後根服務修改消息表狀態爲已完成。
3.缺點
強依賴於本地的消息表,數據庫壓力較大。
(四)最終一致性方案模型
1.角色
- 上游根服務
- 可靠消息服務
- MQ消息中間件
- 下游服務(可能有多個)
2.應用場景
主要應用於不要求強一致,併發要求度高,微服務異步調用場景中。
3.思路
這個模型主要關注三點:
- 在根服務本地事務完成和失敗的狀況下,確保消息可以正確投遞到消費者或取消投遞。
- 在下游服務本地事務失敗的狀況下,確保消息可以被重發。
- 不關注各個優秀MQ框架消息可靠性的保證,即默認消息可以被丟失。
爲了解決上述問題,引入了可靠消息服務,用以協調上游服務的事務狀態和下游事務狀態。經過維護消息的三種狀態:待確認、已發送、已成功,確保事務的最終一致性。
3.過程
(1) 上游根服務調用可靠消息服務。
上游根服務同步調用可靠消息服務,參數包含調用下游服務的參數,這些數據由可靠消息服務存入本身的表中,並將這條記錄的狀態置爲待確認。
(2) 上游根服務執行完本身的業務以後在再次調用可靠消息服務。
上游根服務執行完本身的業務以後,將業務執行結果傳入可靠消息服務,可靠消息服務根據業務失敗結果,若失敗則刪除消息表中關於這個業務的全部消息表記錄,若成功則經過MQ發送消息給下游服務,而後更新剛剛存入的記錄將狀態置爲已發送。注意可靠消息服務的這個方法中,判斷業務成功失敗,需安排在同一個本地事務(由於發送消息和更新記錄必須保證原子性)。
(3) 下游服務接收消息以後消費消息,執行業務完成後回調可靠消息服務。
下游服務接收消息以後消費消息,執行業務完成後回調可靠消息服務。可靠消息服務受到請求後,將本地記錄狀態置爲已完成。
4.注意
(1) 如何保證上游服務對可靠消息服務的100%可靠投遞
- 第一次調用若是失敗,這種狀況上游根服務會收到可靠消息服務錯誤返回,這種狀況上游根服務直接放棄流程。
- 第二次調用若是失敗,這種狀況可靠消息服務後臺定時檢查記錄中爲待確認消息的記錄,若是待確認狀態時間較長,則對上游服務發起請求,看上游服務是否完成了本身的業務邏輯。若完成,則自動更新記錄狀態,反之刪除記錄。(上游服務須要維護本身的事務執行狀態在本地表中)
(2) 如何保證下游服務對可靠消息服務的100%可靠投遞
可靠消息服務定時檢查記錄中已發送狀態維持時間超時的記錄,而後重複發送請求給下游服務。因此這裏同時也要求下游服務方法的冪等性。
(4) 另外
這個模型和本地消息表服務很像,區別在於抽離了本地消息表,簡化了根服務的操做,雖然消息表仍是存在在可靠消息服務當中,但與業務庫相獨立,必定程度提升了性能。另外RocketMQ針對這種模型作了優秀的架構實現,至關於集成了上述的可靠消息服務的功能,而且沒有消息表的存在,消息的狀態由RocketMQ維護,因爲我也沒有實際使用過RocketMQ,因此就不展開介紹了。基於其餘沒有提供可靠消息確認機制,
(五)另外幾種想到的解決辦法
使用RabbitMQ的消息確認機制和消費者開啓手動ACK是可以確保消息必定被消費。惟一須要關注的問題在於如何處理本地事務失敗、消息者成功消費形成的數據不一致的狀況。
對於這種狀況,能夠這麼作:
- 在發送消息的時候,綁定兩個監聽隊列到一個交換機上。一個監聽隊列負責對這種狀況進行補償,補償操做爲判斷本地事務是否成功,若不成功則進行補償。一個監聽隊列負責完成下游服務。
3、小結
其實這些模型有不少都有類似的地方,也能作不少變通,不少經過回調、輪詢、長鏈接監聽、同步調用的方式其實均可以根據具體場景互相替換或選擇不一樣的優秀架構。另外須要注意的就是,引入分佈式事務是須要維護成本和性能成本的,不少狀況下,須要根據業務來看需不須要引入分佈式事務。不是很嚴格的狀況,用監控、日誌記錄的方式採起人工補償的策略有可能成本更小。