事務的具體定義:事務提供一種機制將一個活動涉及的全部操做歸入到一個不可分割的執行單元,組成事務的全部操做只有在全部操做均能正常執行的狀況下方能提交,只要其中任一操做執行失敗,都將致使整個事務的回滾。簡單地說,事務提供一種「要麼什麼都不作,要麼作全套(All or Nothing)」機制。
舉例:不知道你是否遇到過這樣的狀況,去小賣鋪買東西,付了錢,可是店主由於處理了一些其餘事,竟然忘記你付了錢,又叫你從新付。又或者在網上購物明明已經扣款,可是卻告訴我沒有發生交易。這一系列狀況都是由於沒有事務致使的。這說明了事務在生活中的一些重要性。有了事務,你去小賣鋪買東西,那就是一手交錢一手交貨。有了事務,你去網上購物,扣款即產生訂單交易。mysql
知識腦圖:面試
ACIDsql
說到數據庫事務就不得不說,數據庫事務中的四大特性,ACID:數據庫
A:原子性(Atomicity)性能優化
一個事務(transaction)中的全部操做,要麼所有完成,要麼所有不完成,不會結束在中間某個環節。事務在執行過程當中發生錯誤,會被回滾(Rollback)到事務開始前的狀態,就像這個事務歷來沒有執行過同樣。
就像你買東西要麼交錢收貨一塊兒都執行,要麼要是發不出貨,就退錢。服務器
C:一致性(Consistency)網絡
事務的一致性指的是在一個事務執行以前和執行以後數據庫都必須處於一致性狀態。若是事務成功地完成,那麼系統中全部變化將正確地應用,系統處於有效狀態。若是在事務中出現錯誤,那麼系統中的全部變化將自動地回滾,系統返回到原始狀態。架構
I:隔離性(Isolation)併發
指的是在併發環境中,當不一樣的事務同時操縱相同的數據時,每一個事務都有各自的完整數據空間。由併發事務所作的修改必須與任何其餘併發事務所作的修改隔離。事務查看數據更新時,數據所處的狀態要麼是另外一事務修改它以前的狀態,要麼是另外一事務修改它以後的狀態,事務不會查看到中間狀態的數據。
打個比方,你買東西這個事情,是不影響其餘人的。異步
D:持久性(Durability)
指的是隻要事務成功結束,它對數據庫所作的更新就必須永久保存下來。即便發生系統崩潰,從新啓動數據庫系統後,數據庫還能恢復到事務成功結束時的狀態。
打個比方,你買東西的時候須要記錄在帳本上,即便老闆忘記了那也有據可查。
InnoDB是mysql的一個存儲引擎,大部分人對mysql都比較熟悉,這裏簡單介紹一下數據庫事務實現的一些基本原理,在本地事務中,服務和資源在事務的包裹下能夠看作是一體的:
咱們的本地事務由資源管理器進行管理:
而事務的ACID是經過InnoDB日誌和鎖來保證。事務的隔離性是經過數據庫鎖的機制實現的,持久性經過redo log(重作日誌)來實現,原子性和一致性經過Undo log來實現。UndoLog的原理很簡單,爲了知足事務的原子性,在操做任何數據以前,首先將數據備份到一個地方(這個存儲數據備份的地方稱爲UndoLog)。而後進行數據的修改。若是出現了錯誤或者用戶執行了ROLLBACK語句,系統能夠利用Undo Log中的備份將數據恢復到事務開始以前的狀態。
和Undo Log相反,RedoLog記錄的是新數據的備份。在事務提交前,只要將RedoLog持久化便可,不須要將數據持久化。當系統崩潰時,雖然數據沒有持久化,可是RedoLog已經持久化。系統能夠根據RedoLog的內容,將全部數據恢復到最新的狀態。
對具體實現過程有興趣的同窗能夠去自行搜索擴展。
分佈式事務就是指事務的參與者、支持事務的服務器、資源服務器以及事務管理器分別位於不一樣的分佈式系統的不一樣節點之上。簡單的說,就是一次大的操做由不一樣的小操做組成,這些小的操做分佈在不一樣的服務器上,且屬於不一樣的應用,分佈式事務須要保證這些小操做要麼所有成功,要麼所有失敗。本質上來講,分佈式事務就是爲了保證不一樣數據庫的數據一致性。
從上面本地事務來看,咱們能夠看爲兩塊,一個是service產生多個節點,另外一個是resource產生多個節點。
service多個節點
隨着互聯網快速發展,微服務,SOA等服務架構模式正在被大規模的使用,舉個簡單的例子,一個公司以內,用戶的資產可能分爲好多個部分,好比餘額,積分,優惠券等等。在公司內部有可能積分功能由一個微服務團隊維護,優惠券又是另外的團隊維護
這樣的話就沒法保證積分扣減了以後,優惠券可否扣減成功。
resource多個節點
一樣的,互聯網發展得太快了,咱們的Mysql通常來講裝千萬級的數據就得進行分庫分表,對於一個支付寶的轉帳業務來講,你給的朋友轉錢,有可能你的數據庫是在北京,而你的朋友的錢是存在上海,因此咱們依然沒法保證他們能同時成功。
從上面來看分佈式事務是隨着互聯網高速發展應運而生的,這是一個必然的咱們以前說過數據庫的ACID四大特性,已經沒法知足咱們分佈式事務,這個時候又有一些新的大佬提出一些新的理論:
CAP
CAP定理,又被叫做布魯爾定理。對於設計分佈式系統來講(不只僅是分佈式事務)的架構師來講,CAP就是你的入門理論。
C (一致性):對某個指定的客戶端來講,讀操做能返回最新的寫操做。對於數據分佈在不一樣節點上的數據上來講,若是在某個節點更新了數據,那麼在其餘節點若是都能讀取到這個最新的數據,那麼就稱爲強一致,若是有某個節點沒有讀取到,那就是分佈式不一致。
A (可用性):非故障的節點在合理的時間內返回合理的響應(不是錯誤和超時的響應)。可用性的兩個關鍵一個是合理的時間,一個是合理的響應。合理的時間指的是請求不能無限被阻塞,應該在合理的時間給出返回。合理的響應指的是系統應該明確返回結果而且結果是正確的,這裏的正確指的是好比應該返回50,而不是返回40。
P (分區容錯性):當出現網絡分區後,系統可以繼續工做。打個比方,這裏個集羣有多臺機器,有臺機器網絡出現了問題,可是這個集羣仍然能夠正常工做。
熟悉CAP的人都知道,三者不能共有,若是感興趣能夠搜索CAP的證實,在分佈式系統中,網絡沒法100%可靠,分區實際上是一個必然現象,若是咱們選擇了CA而放棄了P,那麼當發生分區現象時,爲了保證一致性,這個時候必須拒絕請求,可是A又不容許,因此分佈式系統理論上不可能選擇CA架構,只能選擇CP或者AP架構。
對於CP來講,放棄可用性,追求一致性和分區容錯性,咱們的zookeeper其實就是追求的強一致。
對於AP來講,放棄一致性(這裏說的一致性是強一致性),追求分區容錯性和可用性,這是不少分佈式系統設計時的選擇,後面的BASE也是根據AP來擴展。
順便一提,CAP理論中是忽略網絡延遲,也就是當事務提交時,從節點A複製到節點B,可是在現實中這個是明顯不可能的,因此總會有必定的時間是不一致。同時CAP中選擇兩個,好比你選擇了CP,並非叫你放棄A。由於P出現的機率實在是過小了,大部分的時間你仍然須要保證CA。就算分區出現了你也要爲後來的A作準備,好比經過一些日誌的手段,是其餘機器回覆至可用。
BASE
BASE 是 Basically Available(基本可用)、Soft state(軟狀態)和 Eventually consistent (最終一致性)三個短語的縮寫。是對CAP中AP的一個擴展
基本可用:分佈式系統在出現故障時,容許損失部分可用功能,保證核心功能可用。
軟狀態:容許系統中存在中間狀態,這個狀態不影響系統可用性,這裏指的是CAP中的不一致。
最終一致:最終一致是指通過一段時間後,全部節點數據都將會達到一致。
BASE解決了CAP中理論沒有網絡延遲,在BASE中用軟狀態和最終一致,保證了延遲後的一致性。BASE和 ACID 是相反的,它徹底不一樣於ACID的強一致性模型,而是經過犧牲強一致性來得到可用性,並容許數據在一段時間內是不一致的,但最終達到一致狀態。
有了上面的理論基礎後,這裏介紹開始介紹幾種常見的分佈式事務的解決方案。
說到這裏順便順便給你們推薦一個Java方面的中高級程度的交流學習羣:650385180,裏面會相互交流工做經驗和麪試心得以及面試題,還會分享一些資深架構師錄製的視頻錄像:有Spring,MyBatis,Netty源碼分析,高併發、高性能、分佈式、微服務架構的原理,JVM性能優化這些成爲架構師必備的知識體系。還能領取免費的學習資源,文章開頭的知識腦圖也是在羣裏獲取。相信對於已經工做和遇到技術瓶頸的碼友,在這個羣裏會有你須要的內容。
在說方案以前,首先你必定要明確你是否真的須要分佈式事務?
上面說過出現分佈式事務的兩個緣由,其中有個緣由是由於微服務過多。我見過太多團隊一我的維護幾個微服務,太多團隊過分設計,搞得全部人疲勞不堪,而微服務過多就會引出分佈式事務,這個時候我不會建議你去採用下面任何一種方案,而是請把須要事務的微服務聚合成一個單機服務,使用數據庫的本地事務。由於不論任何一種方案都會增長你係統的複雜度,這樣的成本實在是過高了,千萬不要由於追求某些設計,而引入沒必要要的成本和複雜度。
若是你肯定須要引入分佈式事務能夠看看下面幾種常見的方案。
2PC
說到2PC就不得不聊數據庫分佈式事務中的 XA Transactions。
在XA協議中分爲兩階段:
第一階段:事務管理器要求每一個涉及到事務的數據庫預提交(precommit)此操做,並反映是否能夠提交.
第二階段:事務協調器要求每一個數據庫提交數據,或者回滾數據。
優勢: 儘可能保證了數據的強一致,實現成本較低,在各大主流數據庫都有本身實現,對於MySQL是從5.5開始支持。
缺點:
單點問題:事務管理器在整個流程中扮演的角色很關鍵,若是其宕機,好比在第一階段已經完成,在第二階段正準備提交的時候事務管理器宕機,資源管理器就會一直阻塞,致使數據庫沒法使用。
同步阻塞:在準備就緒以後,資源管理器中的資源一直處於阻塞,直到提交完成,釋放資源。
數據不一致:兩階段提交協議雖然爲分佈式數據強一致性所設計,但仍然存在數據不一致性的可能,好比在第二階段中,假設協調者發出了事務commit的通知,可是由於網絡問題該通知僅被一部分參與者所收到並執行了commit操做,其他的參與者則由於沒有收到通知一直處於阻塞狀態,這時候就產生了數據的不一致性。
總的來講,XA協議比較簡單,成本較低,可是其單點問題,以及不能支持高併發(因爲同步阻塞)依然是其最大的弱點。
TCC
關於TCC(Try-Confirm-Cancel)的概念,最先是由Pat Helland於2007年發表的一篇名爲《Life beyond Distributed Transactions:an Apostate’s Opinion》的論文提出。
TCC事務機制相比於上面介紹的XA,解決了其幾個缺點:
1.解決了協調者單點,由主業務方發起並完成這個業務活動。業務活動管理器也變成多點,引入集羣。
2.同步阻塞:引入超時,超時後進行補償,而且不會鎖定整個資源,將資源轉換爲業務邏輯形式,粒度變小。
3.數據一致性,有了補償機制以後,由業務活動管理器控制一致性
對於TCC的解釋:
Try階段:嘗試執行,完成全部業務檢查(一致性),預留必須業務資源(準隔離性)
Confirm階段:確認執行真正執行業務,不做任何業務檢查,只使用Try階段預留的業務資源,Confirm操做知足冪等性。要求具有冪等設計,Confirm失敗後須要進行重試。
Cancel階段:取消執行,釋放Try階段預留的業務資源
Cancel操做知足冪等性Cancel階段的異常和Confirm階段異常處理方案基本上一致。
舉個簡單的例子若是你用100元買了一瓶水,
Try階段:你須要向你的錢包檢查是否夠100元並鎖住這100元,水也是同樣的。
若是有一個失敗,則進行cancel(釋放這100元和這一瓶水),若是cancel失敗不論什麼失敗都進行重試cancel,因此須要保持冪等。
若是都成功,則進行confirm,確認這100元扣,和這一瓶水被賣,若是confirm失敗不管什麼失敗則重試(會依靠活動日誌進行重試)
對於TCC來講適合一些:
強隔離性,嚴格一致性要求的活動業務。
執行時間較短的業務
本地消息表這個方案最初是ebay提出的 ebay的完整方案https://queue.acm.org/detail....。
此方案的核心是將須要分佈式處理的任務經過消息日誌的方式來異步執行。消息日誌能夠存儲到本地文本、數據庫或消息隊列,再經過業務規則自動或人工發起重試。人工重試更多的是應用於支付場景,經過對帳系統對過後問題的處理。
對於本地消息隊列來講核心是把大事務轉變爲小事務。仍是舉上面用100元去買一瓶水的例子。
1.當你扣錢的時候,你須要在你扣錢的服務器上新增長一個本地消息表,你須要把你扣錢和寫入減去水的庫存到本地消息表放入同一個事務(依靠數據庫本地事務保證一致性。
2.這個時候有個定時任務去輪詢這個本地事務表,把沒有發送的消息,扔給商品庫存服務器,叫他減去水的庫存,到達商品服務器以後這個時候得先寫入這個服務器的事務表,而後進行扣減,扣減成功後,更新事務表中的狀態。
3.商品服務器經過定時任務掃描消息表或者直接通知扣錢服務器,扣錢服務器本地消息表進行狀態更新。
4.針對一些異常狀況,定時掃描未成功處理的消息,進行從新發送,在商品服務器接到消息以後,首先判斷是不是重複的,若是已經接收,在判斷是否執行,若是執行在立刻又進行通知事務,若是未執行,須要從新執行須要由業務保證冪等,也就是不會多扣一瓶水。
本地消息隊列是BASE理論,是最終一致模型,適用於對一致性要求不高的。實現這個模型時須要注意重試的冪等。
在RocketMQ中實現了分佈式事務,實際上實際上是對本地消息表的一個封裝,將本地消息表移動到了MQ內部,下面簡單介紹一下MQ事務,
基本流程以下:
第一階段Prepared消息,會拿到消息的地址。
第二階段執行本地事務。
第三階段經過第一階段拿到的地址去訪問消息,並修改狀態。消息接受者就能使用這個消息。
若是確認消息失敗,在RocketMq Broker中提供了定時掃描沒有更新狀態的消息,若是有消息沒有獲得確認,會向消息發送者發送消息,來判斷是否提交,在rocketmq中是以listener的形式給發送者,用來處理。
若是消費超時,則須要一直重試,消息接收端須要保證冪等。若是消息消費失敗,這個就須要人工進行處理,由於這個機率較低,若是爲了這種小几率時間而設計這個複雜的流程反而得不償失
Saga是30年前一篇數據庫倫理提到的一個概念。其核心思想是將長事務拆分爲多個本地短事務,由Saga事務協調器協調,若是正常結束那就正常完成,若是某個步驟失敗,則根據相反順序一次調用補償操做。
Saga的組成:
每一個Saga由一系列sub-transaction Ti 組成
每一個Ti 都有對應的補償動做Ci,補償動做用於撤銷Ti形成的結果,這裏的每一個T,都是一個本地事務。
能夠看到,和TCC相比,Saga沒有「預留 try」動做,它的Ti就是直接提交到庫。
Saga的執行順序有兩種:
T1, T2, T3, ..., Tn
T1, T2, ..., Tj, Cj,..., C2, C1,其中0 < j < n
Saga定義了兩種恢復策略:
向後恢復,即上面提到的第二種執行順序,其中j是發生錯誤的sub-transaction,這種作法的效果是撤銷掉以前全部成功的sub-transation,使得整個Saga的執行結果撤銷。
向前恢復,適用於必需要成功的場景,執行順序是相似於這樣的:T1, T2, ..., Tj(失敗), Tj(重試),..., Tn,其中j是發生錯誤的sub-transaction。該狀況下不須要Ci。
這裏要注意的是,在saga模式中不能保證隔離性,由於沒有鎖住資源,其餘事務依然能夠覆蓋或者影響當前事務。
仍是拿100元買一瓶水的例子來講,這裏定義
T1=扣100元 T2=給用戶加一瓶水 T3=減庫存一瓶水
C1=加100元 C2=給用戶減一瓶水 C3=給庫存加一瓶水
咱們一次進行T1,T2,T3若是發生問題,就執行發生問題的C操做的反向。
上面說到的隔離性的問題會出如今,若是執行到T3這個時候須要執行回滾,可是這個用戶已經把水喝了(另一個事務),回滾的時候就會發現,沒法給用戶減一瓶水了。這就是事務之間沒有隔離性的問題
能夠看見saga模式沒有隔離性的影響仍是較大,能夠參照華爲的解決方案:從業務層面入手加入一 Session 以及鎖的機制來保證可以串行化操做資源。也能夠在業務層面經過預先凍結資金的方式隔離這部分資源, 最後在業務操做的過程當中能夠經過及時讀取當前狀態的方式獲取到最新的更新。
具體實例:能夠參考華爲的servicecomb
上面對解決方案只是一些簡單介紹,若是真正的想要落地,其實每種方案須要思考的地方都很是多,複雜度都比較大,因此最後再次提醒必定要判斷好是否使用分佈式事務。最後在總結一些問題,你們能夠下來本身從文章找尋答案:
ACID和CAP的 CA是同樣的嗎?分佈式事務經常使用的解決方案的優缺點是什麼?適用於什麼場景?分佈式事務出現的緣由?用來解決什麼痛點?