更新時間:2019年7月20日
複製代碼
springCloud系列文章:java
springCloud的瞭解(一):juejin.im/post/5d2eca…mysql
springCloud的瞭解(二):juejin.im/post/5d2fd0…spring
springCloud的瞭解(三):juejin.im/post/5d3123…sql
昨天,咱們又學習了springCloud的剩餘的大部分組件,config配置中心,Feign聲明式服務調用,Turbin集羣監控,Bus消息總線。docker
關於SpringCloud的組件內容,咱們暫時先告一段落,固然在後續的文章中,我會陸續的深刻到組件的源代碼分析和組件的具體做用中去。今天咱們要開始學習在springCloud中的分佈式事務問題。數據庫
著名架構師Chris Richardson所言,目前springCloud落地主要存在的困難有以下幾方面:編程
1.單體應用拆分爲分佈式系統後,進程間的通信機制和故障處理措施變的更加複雜。bash
2.系統微服務化後,一個看似簡單的功能,內部可能須要調用多個服務並操做多個數據庫實現,服務調用的分佈式事務問題變的很是突出。restful
3.微服務數量衆多,其測試、部署、監控等都變的更加困難。網絡
隨着RPC框架的成熟,第一個問題已經逐漸獲得解決。例如dubbo能夠支持多種通信協議,springcloud能夠很是好的支持restful調用。對於第三個問題,隨着docker、devops技術的發展以及各公有云paas平臺自動化運維工具的推出,微服務的測試、部署與運維會變得愈來愈容易。
那麼,詳細點講,什麼是分佈式事務問題呢?
在springCloud中,有時候咱們寫一個看似簡單的功能,在程序的內部卻有可能調用到多個其餘的微服務,而這些微服務不但有多是集羣性部署,並且數據庫也有可能進行分庫分區,且數據庫也會進行集羣部署,那麼也會調用多個數據庫。
那麼,當咱們在進行這麼一個簡單的功能的時候,一旦某一步驟的調用出現了問題,數據沒有傳達到位,那麼極有可能使整個程序的數據不統一,不一致了。
簡單點說,在分佈式系統中,一個大的操做由無數個小的操做組成,那麼分佈式事務就是要保證這些小的操做,要麼所有都能成功,要麼所有都是失敗,從而保證了數據的一致性。
本質上來講,分佈式事務就是爲了保證不一樣數據庫或消息系統的數據一致性。
這就是任何一個開發者都必需要面對的一個問題——分佈式事務問題!
*ps:在正式開始以前,我又要給你們推薦一首歌了,盤尼西林的歌,《雨夜曼徹斯特》,也是從綜藝節目《樂隊的夏天》中聽到的,但願你們會喜歡。(^_−)☆
在開始具體的分析以前,咱們要先對分佈式事物的發展歷程有一個具體的瞭解。
在早期的時候,分佈式事務的解決方案是由2pc(兩階段提交)以及相應的變種3PC來實現(由於2PC有致命的問題,3PC經過拆分2PC的第一階段避免了極端狀況下的問題)。
隨着互聯網的發展,這種方案已經不能咱們對性能的追求,尤爲是阿里巴巴的螞蟻金融,對事務的要求是極其嚴格的,因而在2PC的基礎上出現了經典的TCC模式,來保證事務的一致性。
可是TCC模式對編程的要求極高,因而後面又出現了經過消息中間件來保證事務最終一致性的方案來解決事務問題。
直到2017年,阿里巴巴出現了GTS方案來保證事務強一致性的方案,並且提供了許多的服務給互聯網使用者,應該是當前最早進的分佈式事務處理方案。
這是整個分佈式事務的發展歷程。
補充一下內容:
強一致性:
當更新操做完成以後,任何多個後續進程或者線程的訪問都會返回最新的
更新過的值。這種是對用戶最友好的,就是用戶上一次寫什麼,下一次就
保證能讀到什麼。根據 CAP理論,這種實現須要犧牲可用性。
弱一致性:
系統並不保證續進程或者線程的訪問都會返回最新的更新過的值。用戶讀
到某一操做對系統特定數據的更新須要一段時間,咱們稱這段時間爲「不
一致性窗口」。系統在數據寫入成功以後,不承諾當即能夠讀到最新寫入
的值,也不會具體的承諾多久以後能夠讀到。
最終一致性:
是弱一致性的一種特例。系統保證在沒有後續更新的前提下,系統最終返
回上一次更新操做的值。在沒有故障發生的前提下,不一致窗口的時間主
要受通訊延遲,系統負載和複製副本的個數影響。DNS 是一個典型的最終
一致性系統。
複製代碼
在2pc中,咱們假設定有兩個角色,一個是協調者,一個是參與者。
參與者負責具體執行操做,通常來講就會打開本地數據庫事務,而後開始執行數據庫本地事務,但在執行完成後並不會立馬提交數據庫本地事務,而是將每次執行的狀況(成功或者失敗)報告給協調者,協調者經過總結參與者報告的內容進行分析,最後決定事務是否繼續所有執行,或者是所有回滾。
具體來說,能夠將整個流程分爲兩個階段。
第一階段是請求階段,是參與者將本身執行的狀況發送給協調者。參與者發送的,有多是成功,有多是失敗(程序執行出現故障,若是沒有收到參與者的消息,那麼就是超時,默認爲失敗)。
第二階段是提交階段,協調者獲取到了全部參與者發送的執行結果,經過分析這些執行結果,協調者來判斷,是整個程序繼續執行下去,仍是進行全體回滾,而後將這個決定發送給全部參與者。
這個方案的缺點是很是明顯的:
1.要等到全部的參與者執行完操做,並且參與者發送本身的結果給協調者
的時候,這兩點佔據了太多的公共資源。(由於要進行通訊,協調者和參
與者是份屬不一樣的微服務)
2.協調者在整個體系中過重要了,一旦出錯,會致使全部的參與者進入到
阻塞狀態。(實際上協調者即使宕機,可是在2PC中,會自動從參與者選
舉出一個協調者,可是參與者阻塞的狀態沒法獲得解決。)
3.當協調者統計參與者返回的數據進行分析,得出結果後,將結果發送給
全部的參與者,可是假如一部分的參與者接受結果的時候出錯或者通訊失
效,沒有獲得協調者發送的最終結果,那麼會致使數據的最終不一致。
複製代碼
也由於這些缺點,後面就出現了2PC的變種——3pc。
三階段提交方案,在二階段提交方案的基礎上,讓參與者和協調者都有了超時機制,並且將2PC的第一個階段拆分爲了兩個階段,先詢問,再鎖定資源,而後再真正提交。
第一階段:協調者向參與者發送詢問,參與者返回本身的結果給協調者,成功或者失敗。
第二階段:協調者根據參與者返回的結果,進行分析。將會出現兩種結果:
1.假如參與者返回的都是成功Yse,那麼協調者將會發送預執行消息給參
與者,而且協調者進入到預執行準備階段。而參與者接收到協調者發送
的指令後,將會把這些操做指令存儲到數據庫的事務日誌中,而且進行
預執行操做。若是參與者成功完成了指令,那麼就會返回結果ACK給協
調者,而且等待最終結果。
2.若是有一個參與者返回的是失敗No,或者有一個參與者超時了沒有返
回結果,那麼協調者將會發送中斷事務的指令給全部的參與者,讓參與
者進行事務中斷。
複製代碼
第三階段:當協調者受到了全部參與者的ACK結果,那麼協調者就會從預執行準備階段變成正式執行階段,而且把正式執行的指令發送給全部的參與者。參與者收到指令,正式執行事務操做,且成功後,再一次返回ACK成功指令給協調者。一樣,若是有一個參與者返回的是失敗No,或者有一個參與者超時了沒有返回結果,那麼協調者將會發送中斷事務的指令給全部的參與者,讓參與者進行事務中斷。
在2PC的準備階段和提交階段之間,插入預提交階段,這是一個緩衝,保證了在最後提交階段以前各參與節點的狀態是一致的。
這個方案的缺點:
當協調者發送中斷事務的指令給參與者的時候,只有一個參與收到了執
行事務中斷的指令,其餘參與者沒有收到,那麼其餘參與者會默認正式
執行事務操做。這樣就會致使了整個系統狀態和數據的不一致了。(一
旦事務參與者遲遲沒有收到協調者的正式執行請求,就會自動進行本地
正式執行,這樣相對有效地解決了協調者單點故障的問題,這就是3PC
加入的參與者的超時)
複製代碼
前面講的解決分佈式事務的方案,2PC和3PC都是在DB(數據庫)層面解決問題,是基於XA協議。 科普一下:
XA是一個分佈式事務協議,由Tuxedo提出。XA中大體分爲兩部分:
事務管理器和本地資源管理器。其中本地資源管理器每每由數據
庫實現,好比Oracle、DB2這些商業數據庫都實現了XA接口,而
事務管理器做爲全局的調度者,負責各個本地資源的提交和回滾
。總的來講,XA協議比較簡單,並且一旦商業數據庫實現了XA協
議,使用分佈式事務的成本也比較低。可是,XA也有致命的缺點
,那就是性能不理想,特別是在交易下單鏈路,每每併發量很高
,XA沒法知足高併發場景。XA目前在商業數據庫支持的比較理想
,在mysql數據庫中支持的不太理想,mysql的XA實現,沒有記錄
prepare階段日誌,主備切換回致使主庫與備庫數據不一致。許
多Nosql也沒有支持XA,這讓XA的應用場景變得很是狹隘。
複製代碼
做爲自己是基於XA協議的2PC和3PC,天然也繼承了XA協議的全部問題,因而後面出現了基於業務層的TCC事務處理方案。
TCC方案,確實與2PC協議有極大的相同處,甚至對比這兩張方案的流程,也基本上是一致的,它們最主要的區別是2PC是基於DB層面的處理方案,而TCC是基於業務層面的處理方案。
TCC的三個階段以下:
Try階段:主要是對業務系統作檢測及資源預留。
Confirm階段:確認執行業務操做。
Cancel階段:取消執行業務操做。
因爲TCC模式,是成長在業務層面的方案,那麼它確實是須要和業務代碼進行耦合的。
1.全部事務參與方都須要實現try,confirm,cancle接口。
2.事務參與者向事務協調者發起事務請求,事務協調者調用
全部事務參與者的try方法完成資源的預留,這時候並無
真正執行業務,而是爲後面具體要執行的業務預留資源,這
裏完成了一階段。
3.若是事務協調者發現有參與者的try方法預留資源時候發現
資源不夠,則調用參與者的cancle方法回滾預留的資源,須要
注意cancle方法須要實現業務冪等,由於有可能調用失敗(比
如網絡緣由參與者接受到了請求,可是因爲網絡緣由事務協調
器沒有接受到回執),那麼就要進行重試。
4.若是事務協調者發現全部參與者的try方法返回都OK,則事務
協調者調用全部參與者的confirm方法,不作資源檢查,直接
進行具體的業務操做。
5.若是協調者發現全部參與者的confirm方法都OK了,則分佈式
事務結束。
6.若是協調者發現有些參與者的confirm方法失敗了,或者因爲
網絡緣由沒有收到回執,則協調器會進行重試。
複製代碼
在第六步的時候,這裏若是重試必定次數後仍是失敗,那麼就會出現不一致,通常稱爲 heuristic exception。
heuristic exception 是不可杜絕的,可是能夠經過設置合適的超時時間,以及重試頻率和監控措施使得出現這個異常的可能性下降到很小。咱們最多見的是作事務補償。由於咱們操做的每一步都是要事務日誌記錄在數據庫,咱們能夠在最後調出事務日誌,經過事務日誌進行補償。(說白了,就是進行人工服務)
優勢在這:
解決了跨應用業務操做的原子性問題,在諸如組合支付、帳務
拆分場景很是實用。TCC實際上把數據庫層的二階段提交上提
到了應用層來實現,對於數據庫來講是一階段提交,規避了
數據庫層的2PC性能低下問題。
複製代碼
缺點在這:
TCC的Try、Confirm和Cancel操做功能需業務提供,開發成本
高,對程序的侵入性高。此外,其實現難度也比較大,須要
按照網絡狀態、系統故障等不一樣的失敗緣由實現不一樣的回滾
策略。爲了知足一致性的要求,confirm和cancel接口還必
須實現冪等。
複製代碼
由TCC模式,引伸出了一種事務處理方案,SAGA模式。這種模式最主要的不一樣是使用一個反向的業務操做,來撤銷以前的業務操做。SAGA模式,try階段直接操做目標字段,不要進行預處理,和TCC模式相比,SAGA不須要confirm操做。你們若是感興趣的,能夠去了解一下。
基於消息中間件的事務處理方案和TCC同樣,都是柔性的事務處理方案,只須要保證事務的最終一致性。
先給你們一張圖片,普通消息的流程圖,你們能夠經過圖片中的編號順序來了解整個流程: (圖片來自我參考的博客:www.jianshu.com/p/04bad986a…
..消息生成者發送消息。
..MQ收到消息,將消息進行持久化,在存儲中新增一條記錄
..返回ACK給消費者。
..MQ提交消息給對應的消費者,而後等待消費者返回ACK。
..若是消息消費者在指定時間內成功返回ACK,那麼MQ認爲。
消息消費成功,在存儲中刪除消息,即執行第6步;
..若是MQ在指定時間內沒有收到ACK,則認爲消息消費失敗
,會嘗試從新push消息,重複執行四、五、6步驟。
..MQ刪除消息。
複製代碼
咱們須要的是數據的一致性,那麼普通的消息的流程中,哪些流程會致使數據的不一致呢?
1.第1步,當消息生產者處理業務成功,可是由於宕機的緣由,
消息沒有發送到MQ那裏,致使了事務消息不一致,從而致使
了生產者和消費者的數據不一致了。
2.第4步,MQ發送給消息消費者的時候,由於通訊緣由,到這
消息消費者沒有收到消息,也致使了消息生產者和消息消
費者的數據不一致。
3.消息消費者處理業務成功,發送了ACK消息給MQ,可是MQ
由於處理超時了,返回的是失敗消息給消息生產者,致使
生產者事務回滾,也致使了消息生產者和消息消費者的數
據不一致。
複製代碼
遠程調用,結果最終可能爲成功、失敗、超時;而對於超時的狀況,處理方最終的結果多是成功,也多是失敗,調用方是沒法知曉的。
那麼,經過事務消息來處理呢?
如今目前較爲主流的MQ,好比ActiveMQ、RabbitMQ、Kafka、
RocketMQ等,只有RocketMQ支持事務消息。緣由就是RocketMQ
是阿里巴巴的消息中間件。
複製代碼
因爲傳統的處理方式沒法解決消息生成者本地事務處理成功與消息發送成功二者的一致性問題,所以事務消息就誕生了,它實現了消息生成者本地事務與消息發送的原子性,保證了消息生成者本地事務處理成功與消息發送成功的最終一致性問題。
(圖片來自我參考的博客:www.jianshu.com/p/04bad986a…
.. 事務消息與普通消息的區別就在於消息生產環節,生產者
首先預發送一條消息到MQ(這也被稱爲發送half消息)。
.. MQ接受到消息後,先進行持久化,則存儲中會新增一條狀
態爲待發送的消息。
.. 而後返回ACK給消息生產者,此時MQ不會觸發消息推送事件。
.. 生產者預發送消息成功後,執行本地事務。
.. 執行本地事務,執行完成後,發送執行結果給MQ。
.. MQ會根據結果刪除或者更新消息狀態爲可發送。
.. 若是消息狀態更新爲可發送,則MQ會push消息給消費者,
後面消息的消費和普通消息是同樣的。
複製代碼
注意點:
因爲MQ一般都會保證消息可以投遞成功,所以,若是業務沒有及時返回ACK結果,那麼就有可能形成MQ的重複消息投遞問題。所以,對於消息最終一致性的方案,消息的消費者必需要對消息的消費支持冪等,不能形成同一條消息的重複消費的狀況。
事務消息的缺點也很明顯,即MQ存儲了待發送的消息,若是出現了通信或者其餘的問題, 那麼MQ沒法感知到上游處理的最終結果。
那麼事務消息RocketMQ是如何解決這個問題呢?它的解決方案很是的簡單,就是其內部實現會有一個定時任務,去輪訓狀態爲待發送的消息,而後給消息生產者發送檢查請求,而消息生產者必須實現一個檢查監聽器,監聽器的內容一般就是去檢查與之對應的本地事務是否成功(通常就是查詢DB),若是成功了,則MQ會將消息設置爲可發送,不然就刪除消息。
固然,咱們自身也能夠經過本地消息實現事務的最終一致性,可是實現的理念和咱們如今瞭解的經過RocketMQ實現的思路是同樣的。
你們能夠去看看阿里巴巴在2017年在深圳峯會上出的一份報告:《破解世界性技術難題! GTS讓分佈式事務簡單高效》,路徑:jm.taobao.org/2017/04/13/…
這篇文章中,將GTS講述的很清楚,我就很少說了。
分佈式事務,一直是開發人員經常要面對的問題,當前的GTS方案,也許是當今階段最早進的方案之一。
可是技術一直在進步,也許在下一刻,會有着更多的更合適的方案,可是不管如何,核心思想都是爲了保證數據的一致性,避免髒數據的出現。
今天,學習的分佈式事務,很是複雜,單單只是粗淺的學習一下,感受其中的內容也實在是太多太多,另外GTS由於歷來沒有接觸過,沒有作詳細的解釋,等到後面有機會,會單獨給GTS寫一篇博文。
接下來,可能不會在springCloud這個大的命題下寫博文,會去了解一下java中的其餘內容。另外,我對Python其實也有很大的興趣,若是有機會的話,我會針對Python的學習過程來寫一些博文。
就這樣吧。