目前雲計算、大數據、互聯網領域的大部分系統都採用了SOA、微服務化的架構。一個涉及端到端全鏈路的業務操做每每會由多個服務和數據庫實例共同完成。所以,在一致性要求較高的業務場景中,如何保證多個服務之間RPC調用後的數據一致將成爲關鍵點。數據庫
1、分佈式系統/SOA/微服務架構的特色:網絡
在大型分佈式系統中要同時可以知足,分佈式一致性(Consistency)、可用性(Availability)和分區容忍性(Partitiontolerance),是不存在的。在大多數狀況下只能知足其中的2項,而實現系統的最終一致性(Base理論)。架構
( 1 ) CAP特色:併發
a.一致性(Consistency):( 一樣數據在分佈式系統的各個節點上都是一致的)框架
b.可用性(Availability):( 全部在分佈式系統活躍的節點都可以處理操做且能響應查詢)異步
c.分區容忍性(Partition Tolerance) :(若是出現了網絡故障、一部分節點沒法通訊,可是系統仍可以工做)分佈式
( 2 ) ACID特色:微服務
a.原子性(Atomicity)性能
一個事務(transaction)中的全部操做,要麼所有完成,要麼所有不完成,不會結束在中間某個環節。事務在執行過程當中發生錯誤,會被回滾(Rollback)到事務開始前的狀態,就像這個事務歷來沒有執行過同樣。測試
b.一致性(Consistency)
事務的一致性指的是在一個事務執行以前和執行以後數據庫都必須處於一致性狀態。若是事務成功地完成,那麼系統中全部變化將正確地應用,系統處於有效狀態。若是在事務中出現錯誤,那麼系統中的全部變化將自動地回滾,系統返回到原始狀態。
c.隔離性(Isolation)
指的是在併發環境中,當不一樣的事務同時操縱相同的數據時,每一個事務都有各自的完整數據空間。由併發事務所作的修改必須與任何其餘併發事務所作的修改隔離。事務查看數據更新時,數據所處的狀態要麼是另外一事務修改它以前的狀態,要麼是另外一事務修改它以後的狀態,事務不會查看到中間狀態的數據。
d.持久性(Durability)
指的是隻要事務成功結束,它對數據庫所作的更新就必須永久保存下來。即便發生系統崩潰,從新啓動數據庫系統後,數據庫還能恢復到事務成功結束時的狀態。
2、分佈式事務的基本介紹
分佈式事務服務(Distributed Transaction Service,DTS)是一種分佈式事務框架,用來確保在大規模分佈式/微服務環境下端到端業務操做的最終一致性。
由CAP定理可知,任何大型的分佈式系統/微服務在一致性、可用性和分區容忍這三點上只能保證其中的兩點。因爲在分佈式系統中常常發生丟包、網絡故障,分區容忍性是必需要知足的,同時爲了兼顧高可用性,絕大部分系統都將強一致性需求轉化成最終一致性的需求,並經過冪等機制保證了數據的最終一致性。
3、經常使用的分佈式技術介紹
( 1 ) 本地消息表(經典的ebay模式)
該方案的核心思想在於分佈式系統在處理任務時經過消息日誌的方式來異步執行。消息日誌能夠存儲至本地文本、數據庫或消息隊列,而後再經過業務規則定時任務或人工自動重試。以在線支付系統的跨行轉帳爲例:
第一步,僞代碼以下,對用戶id爲A的帳戶扣款1000元,經過本地事務將事務消息(包括本地事務id、支付帳戶、收款帳戶、金額、狀態等)插入至消息表:
Begin transaction
update user_account set amount = amount - 1000 where userId = 'A'
insert into trans_message(xid,payAccount,recAccount,amount,status) values(uuid(),'A','B',1000,1);
end transaction
commit;
第二步,通知對方用戶id爲B,增長1000元,一般經過消息MQ的方式發送異步消息,對方訂閱並監聽消息後自動觸發轉帳的操做;這裏爲了保證冪等性,防止觸發重複的轉帳操做,須要在執行轉帳操做方新增一個trans_recv_log表用來作冪等,在第二階段收到消息後,經過判斷trans_recv_log表來檢測相關記錄是否被執行,若是未被執行則會對B帳戶餘額執行加1000元的操做,並會將該記錄增長至trans_recv_log,事件結束後經過回調更新trans_message的狀態值。
( 2 ) 消息中間件
a.非事務消息中間件
這裏仍然以上面跨行轉帳爲例,咱們很難保證在扣款完成以後對MQ投遞消息的操做就必定能成功。這樣一致性彷佛很難保證。如下僞代碼說明了消息投遞的異常:
try{
boolean result = dao.update(model);//更新數據庫失敗拋出異常
if(result){
mq.send(model);//若是MQ超時或者接收方處理失敗,拋出異常
}
}catch(Exception ex){
rollback();//若是異常回滾
}
對於以上的運行狀況主要有如下幾種:
操做數據庫成功,向MQ中投遞消息也成功,該屬於正常狀況,一切都OK。
操做數據庫失敗,不會向MQ中投遞消息了。
操做數據庫成功,可是向MQ中投遞消息時失敗,向外拋出了異常,剛剛執行的更新數據庫的操做將被回滾。
從上面分析的幾種狀況來看,基本上能確保,發送消息的可靠性。咱們再來分析下消費者端的問題:
接收者取出消息後,消費者對應的業務操做要執行成功。若是業務執行失敗,消息不能失效或者丟失。須要保證消息與業務操做一致。
儘可能確保消息的冪等性。若是出現重複消息投遞,可以進行冪等而不對業務產生影響。
b.支持事務的消息中間件
Apache開源的RocketMQ中間件可以支持一種事務消息機制,確保本地操做和發送消息的異步處理達到本地事務的結果一致。
第一階段,RocketMQ在執行本地事務以前,會先發送一個Prepared消息,而且會持有這個消息的接口回查地址。
第二階段,執行本地事物操做。
第三階段,確認消息發送,經過第一階段拿到的接口地址URL執行回查,並修改狀態,若是本地事務成功,則修改狀態爲已提交,不然修改狀態爲已回滾。
其中,若是第三階段的確認消息發送失敗後,RocketMQ會有定時任務掃描集羣中的事務消息,若是發現仍是處於prepare狀態的消息,它會向消息發送者確認本地事務是否已執行成功。RocketMQ會根據發送端設置的策略來決定是回滾仍是繼續發送確認消息。這樣就保證了消息的發送與本地事務同時成功或同時失敗。
再回到上面轉帳的例子,若是用戶A的帳戶餘額已經減小,且消息已經發送成功,做爲消費者用戶B開始消費這條消息,這個時候就會出現消費失敗和消費超時兩個問題,解決超時問題的思路就是一直重試,直到消費端消費消息成功,整個過程當中有可能會出現消息重複的問題,就須要採用前面說的冪等方案來進行處理。
分佈式事務—2PC協議
爲了解決大型分佈式/微服務系統中的一致性問題,業界比較流行的作法是採用比較著名的有二階提交協議(2 Phase Commitment Protocol)和三階提交協議(3 PhaseCommitment Protocol)。考慮到性能問題,三階段提交協議目前較少被採用。本文也主要介紹二階段協議。
2PC協議
二階段提交協議是分佈式系統中較爲經典的處理數據一致性的解決方案。在大型的集羣環境中,對於單體微服務自己而言雖然可以經過代碼質量、Mock測試等方法來確保自身服務的可用性,可是沒法可以保證其餘服務的可用性。當一個全鏈路的端到端業務操做,經常會跨多個節點、多個應用,爲了可以保證全局事務的ACID特性,須要引入一個協調組件(這裏稱之爲TM)來控制全部服務參與者(這裏稱之爲RM)的操做結果,根據全部參與者的反饋結果來決定整個分佈式事務到底是提交仍是回滾的結果。
第一階段:稱爲準備(prepare)階段。事務協調者向各個服務應用發送prepare請求,服務應用在獲得請求後作預處理操做,預處理多是作預檢查,也多是把請求臨時存儲,能夠理解爲是一種試探性地提交。下面是通常的步驟:
a.事務協調者會問全部的參與者服務,是否能夠提交操做。
b.各個參與者開始事務執行的準備工做:如資源上鎖,預留資源,寫回滾/重試的log。
c.參與者響應協調者,若是事務準備工做成功,則迴應「能夠提交」,不然迴應拒絕提交。
第二階段:稱爲提交(commit)/回滾(rollback)階段。是指事務真正提交或者回滾的階段。若是事務協調者發現事務參與者有一個在prepare階段出現失敗,則會要求全部的參與者進行回滾。若是協調者發現全部的參與者都prepare操做都是成功,那麼他將向全部的參與者發出提交請求,這時全部參與者纔會正式提交。由此保證了要求所有提交成功,要麼所有失敗。下面是具體步驟:
a.若是全部的參與者都回應「能夠提交」,那麼協調者向全部參與者發送「正式提交」的命令。參與者完成正式提交,並釋放全部資源,而後迴應「完成」,協調者收集各個服務的「完成」迴應後結束事務。
b.若是有一個參與者迴應「拒絕提交」,那麼協調者向全部的參與者發送「回滾操做」,並釋放全部的資源,而後迴應「回滾完成」,協調者收集各個服務應用的「回滾」返回後,取消總體的分佈式事務。
下圖爲二階段的成功和失敗示例圖:
二階段提交協議解決的是分佈式系統/微服務架構中數據強一致性的問題,其原理簡單,但缺點也是存在,主要缺點以下:
a.單點問題:協調者在整個二階段中的做用很是重要,一旦部署協調者組件服務的節點出現不可用宕機狀況,那麼會影響整個分佈式系統的正常運行。
b.同步阻塞:二階段提交執行過程當中,全部服務參與者須要服從協調者的統一調度,期間處於阻塞狀態,會必定程度上影響整個系統的效率。