數(shù)據(jù)庫事務的概念
在講述分布式事務的概念之前借宵,我們先來回顧下事務相關(guān)的一些概念幌衣。
事務的基本概念:
就是一個程序執(zhí)行單元,里面的操作要么全部執(zhí)行成功壤玫,要么全部執(zhí)行失敗豁护,不允許只成功一半另外一半執(zhí)行失敗的事情發(fā)生。例如一段事務代碼做了兩次數(shù)據(jù)庫更新操作欲间,那么這兩次數(shù)據(jù)庫操作要么全部執(zhí)行成功楚里,要么全部回滾。
事務的基本特性:
我們知道事務有4個非常重要的特性猎贴,即我們常說的(ACID)班缎。
Atomicity(原子性):
是說事務是一個不可分割的整體,所有操作要么全做她渴,要么全不做达址;只要事務中有一個操作出錯,回滾到事務開始前的狀態(tài)的話趁耗,那么之前已經(jīng)執(zhí)行的所有操作都是無效的沉唠,都應該回滾到開始前的狀態(tài)。
Consistency(一致性):
是說事務執(zhí)行前后苛败,數(shù)據(jù)從一個狀態(tài)到另一個狀態(tài)必須是一致的满葛,比如A向B轉(zhuǎn)賬( A、B的總金額就是一個一致性狀態(tài))罢屈,不可能出現(xiàn)A扣了錢嘀韧,B卻沒收到的情況發(fā)生。
Isolation(隔離性):
多個并發(fā)事務之間相互隔離儡遮,不能互相干擾乳蛾。關(guān)于事務的隔離性,可能不是特別好理解鄙币,這里的并發(fā)事務是指兩個事務操作了同一份數(shù)據(jù)的情況肃叶;而對于并發(fā)事務操作同一份數(shù)據(jù)的隔離性問題,則是要求不能出現(xiàn)臟讀十嘿、幻讀的情況因惭,即事務A不能讀取事務B還沒有提交的數(shù)據(jù),或者在事務A讀取數(shù)據(jù)進行更新操作時绩衷,不允許事務B率先更新掉這條數(shù)據(jù)蹦魔。而為了解決這個問題,常用的手段就是加鎖了咳燕,對于數(shù)據(jù)庫來說就是通過數(shù)據(jù)庫的相關(guān)鎖機制來保證勿决。
Durablity(持久性):
事務完成后,對數(shù)據(jù)庫的更改是永久保存的招盲,不能回滾低缩。
事務的隔離級別
在MySQL中,事務有四種隔離級別:
- Read Uncommitted:讀未提交(不能解決任何并發(fā)問題)曹货,一個事務可以讀取另一個未提交事務的數(shù)據(jù)咆繁。會出現(xiàn)臟讀、幻讀顶籽、不可重復讀問題
- Read Committed:讀已提交(解決臟讀問題)玩般,一個事務只能讀取另一個已提交的事務中的數(shù)據(jù)。會出現(xiàn)幻讀礼饱、不可重復讀問題
- Repeatable Read:可重復讀(解決臟讀坏为、不可重復讀問題),在事務中鎖定所讀取的數(shù)據(jù)镊绪,使得其他并發(fā)的事務不能修改這些數(shù)據(jù)久脯。但會出現(xiàn)幻讀
- Serializable:串行化(可以避免臟讀、不可重復讀與幻讀)镰吆,是最高的事務隔離級別帘撰,但是這種事務隔離級別效率低下,比較耗數(shù)據(jù)庫性能万皿,一般不使用摧找。
臟讀:A,B兩個事務牢硅,A開啟事務修改數(shù)據(jù)蹬耘,B 也開啟一個事務剛好查詢能看到A改的數(shù)據(jù)并使用,但是A還沒有改完减余,發(fā)現(xiàn)錯誤并回滾了综苔,此時B就是臟讀。
幻讀:在一個事務中,前后兩次相同的查詢操作返回的結(jié)果集不一致如筛,第二次查詢發(fā)現(xiàn)了新插入或者已刪除的行堡牡,導致出現(xiàn)了“幻覺”,即似乎出現(xiàn)了新的行或者少了某些行杨刨。
不可重復讀:在事務中晤柄,某個查詢操作在多次執(zhí)行之間返回了不同的結(jié)果集,即兩次相同的查詢卻得到了不同的結(jié)果妖胀。這種情況通常發(fā)生在一個事務中執(zhí)行了兩次讀取操作之間有其他事務修改了數(shù)據(jù)的情況下芥颈。
什么是分布式事務
以上內(nèi)容我們回顧了下事務的基本概念,那么分布式事務又是個什么概念呢赚抡?它與數(shù)據(jù)庫事務之間又有什么區(qū)別呢爬坑?
其實分布式事務從實質(zhì)上看與數(shù)據(jù)庫事務的概念是一致的,既然是事務也就需要滿足事務的基本特性(ACID)涂臣,只是分布式事務相對于本地事務而言其表現(xiàn)形式有很大的不同盾计。舉個例子,在一個JVM進程中如果需要同時操作數(shù)據(jù)庫的多條記錄肉康,而這些操作需要在一個事務中闯估,那么我們可以通過數(shù)據(jù)庫提供的事務機制(一般是數(shù)據(jù)庫鎖)來實現(xiàn)。
而隨著這個JVM進程(應用)被拆分成了微服務架構(gòu)吼和,原本一個本地邏輯執(zhí)行單元被拆分到了多個獨立的微服務中涨薪,這些微服務又分別操作不同的數(shù)據(jù)庫和表,服務之間通過網(wǎng)絡調(diào)用炫乓。
舉個例子:服務A收到一筆購物下單請求后刚夺,需要調(diào)用服務B去支付,支付成功則處理購物訂單為待發(fā)貨狀態(tài)末捣,否則就需要將購物訂單處理為失敗狀態(tài)侠姑。(如圖所示)
在上面這個例子中會不會出現(xiàn)服務B支付成功了,但是由于網(wǎng)絡調(diào)用的問題沒有通知到服務A箩做,導致用戶付了錢莽红,但是購物訂單無法顯示支付成功的狀態(tài)呢?
答案是 這種情況是普遍存在的邦邦,因為服務B在處理成功后需要向服務A發(fā)送網(wǎng)絡請求安吁,而這個過程是極有可能失敗的。那么如何確比枷剑“服務A->服務B”這個過程能夠組成一個事務鬼店,要么全部成功、要么全部失敗呢黔龟?而這就是典型的需要通過分布式事務解決的問題妇智。
分布式事務是為了解決微服務架構(gòu)(形式都是分布式系統(tǒng))中不同節(jié)點之間的數(shù)據(jù)一致性問題滥玷。這個一致性問題本質(zhì)上解決的也是傳統(tǒng)事務需要解決的問題,即一個請求在多個微服務調(diào)用鏈中巍棱,所有服務的數(shù)據(jù)處理要么全部成功惑畴,要么全部回滾。當然分布式事務問題的形式可能與傳統(tǒng)事務會有比較大的差異拉盾,但是問題本質(zhì)是一致的桨菜,都是要求解決數(shù)據(jù)的一致性問題豁状。
而分布式事務的實現(xiàn)方式有很多種捉偏,最具有代表性的是由Oracle Tuxedo系統(tǒng)提出的 XA分布式事務協(xié)議。XA協(xié)議包括兩階段提交(2PC)和三階段提交(3PC)兩種實現(xiàn)泻红,接下來我們分別來介紹下這兩種實現(xiàn)方式的原理夭禽。
兩階段提交(2PC)
兩階段提交又稱2PC(two-phase commit protocol),2pc是一個非常經(jīng)典的強一致、中心化的原子提交協(xié)議谊路。這里所說的中心化是指協(xié)議中有兩類節(jié)點:一個是中心化協(xié)調(diào)者節(jié)點(coordinator)和N個參與者節(jié)點(partcipant)讹躯。
下面我們就以一個盡量貼近實際業(yè)務場景的操作來舉例:"假設在一個分布式架構(gòu)的系統(tǒng)中事務的發(fā)起者通過分布式事務協(xié)調(diào)者(如RocketMQ,在早期RocketMQ版本不提供事務消息特性時缠劝,有些公司會自己研發(fā)一個基于MQ的可靠消息服務來實現(xiàn)一定的分布式事務的特性)分別向應用服務A潮梯、應用服務B發(fā)起處理請求,二者在處理的過程中會分別操作自身服務的數(shù)據(jù)庫惨恭,現(xiàn)在要求應用服務A秉馏、應用服務B的數(shù)據(jù)處理操作要在一個事務里"?
在上面這個例子中如果采用兩階段提交來實現(xiàn)分布式事務,那么其運行原理應該是個什么樣的呢脱羡?(如下):
第一階段:請求/表決階段
既然稱為兩階段提交萝究,說明在這個過程中是大致存在兩個階段的處理流程。第一個階段如上圖所示锉罐,這個階段被稱之為請求/表決階段帆竹。是個什么意思呢?
就是在分布式事務的發(fā)起方在向分布式事務協(xié)調(diào)者(Coordinator)發(fā)送請求時脓规,Coordinator首先會分別向參與者(Partcipant)節(jié)點A栽连、參與這節(jié)點(Partcipant)節(jié)點B分別發(fā)送 事務預處理請求,稱之為Prepare侨舆,有些資料也叫"Vote Request"秒紧。
說的直白點就是問一下這些參與節(jié)點"這件事你們能不能處理成功了",此時這些參與者節(jié)點一般來說就會打開本地數(shù)據(jù)庫事務态罪,然后開始執(zhí)行數(shù)據(jù)庫本地事務噩茄,但在執(zhí)行完成后并不會立馬提交數(shù)據(jù)庫本地事務,而是先向Coordinator報告說:“我這邊可以處理了/我這邊不能處理”复颈。
如果所有的參與這節(jié)點都向協(xié)調(diào)者做了“Vote Commit”的反饋的話绩聘,那么此時流程就會進入第二個階段了沥割。
第二階段:提交/執(zhí)行階段(正常流程)
如果所有參與者節(jié)點都向協(xié)調(diào)者報告說“我這邊可以處理”,那么此時協(xié)調(diào)者就會向所有參與者節(jié)點發(fā)送“全局提交確認通知(global_commit)”凿菩,即你們都可以進行本地事務提交了机杜,此時參與者節(jié)點就會完成自身本地數(shù)據(jù)庫事務的提交,并最終將提交結(jié)果回復“ack”消息給Coordinator衅谷,然后Coordinator就會向調(diào)用方返回分布式事務處理完成的結(jié)果椒拗。
第二階段:提交/執(zhí)行階段(異常流程)
相反,在第二階段除了所有的參與者節(jié)點都反饋“我這邊可以處理了”的情況外获黔,也會有節(jié)點反饋說“我這邊不能處理”的情況發(fā)生蚀苛,此時參與者節(jié)點就會向協(xié)調(diào)者節(jié)點反饋“Vote_Abort”的消息。此時分布式事務協(xié)調(diào)者節(jié)點就會向所有的參與者節(jié)點發(fā)起事務回滾的消息(“global_rollback”)玷氏,此時各個參與者節(jié)點就會回滾本地事務堵未,釋放資源,并且向協(xié)調(diào)者節(jié)點發(fā)送“ack”確認消息盏触,協(xié)調(diào)者節(jié)點就會向調(diào)用方返回分布式事務處理失敗的結(jié)果渗蟹。
以上就是兩階段提交的基本過程了,那么按照這個兩階段提交協(xié)議赞辩,分布式系統(tǒng)的數(shù)據(jù)一致性問題就能得到滿足嗎雌芽?
實際上分布式事務是一件非常復雜的事情,兩階段提交只是通過增加了事務協(xié)調(diào)者(Coordinator)的角色來通過2個階段的處理流程來解決分布式系統(tǒng)中一個事務需要跨多個服務節(jié)點的數(shù)據(jù)一致性問題辨嗽。但是從異常情況上考慮世落,這個流程也并不是那么的無懈可擊。
假設如果在第二個階段中Coordinator在接收到Partcipant的"Vote_Request"后掛掉了或者網(wǎng)絡出現(xiàn)了異常召庞,那么此時Partcipant節(jié)點就會一直處于本地事務掛起的狀態(tài)岛心,從而長時間地占用資源。當然這種情況只會出現(xiàn)在極端情況下篮灼,然而作為一套健壯的軟件系統(tǒng)而言忘古,異常Case的處理才是真正考驗方案正確性的地方。
以下幾點是XA-兩階段提交協(xié)議中會遇到的一些問題:
-
性能問題
從流程上我們可以看得出诅诱,其最大缺點就在于它的執(zhí)行過程中間髓堪,節(jié)點都處于阻塞狀態(tài)。各個操作數(shù)據(jù)庫的節(jié)點此時都占用著數(shù)據(jù)庫資源娘荡,只有當所有節(jié)點準備完畢干旁,事務協(xié)調(diào)者才會通知進行全局提交,參與者進行本地事務提交后才會釋放資源炮沐。這樣的過程會比較漫長争群,對性能影響比較大。
-
協(xié)調(diào)者單點故障問題
事務協(xié)調(diào)者是整個XA模型的核心大年,一旦事務協(xié)調(diào)者節(jié)點掛掉换薄,會導致參與者收不到提交或回滾的通知玉雾,從而導致參與者節(jié)點始終處于事務無法完成的中間狀態(tài)。
-
丟失消息導致的數(shù)據(jù)不一致問題
在第二個階段轻要,如果發(fā)生局部網(wǎng)絡問題复旬,一部分事務參與者收到了提交消息,另一部分事務參與者沒收到提交消息冲泥,那么就會導致節(jié)點間數(shù)據(jù)的不一致問題驹碍。
既然兩階段提交有以上問題,那么有沒有其他的方案來解決呢凡恍?
三階段提交(3PC)
三階段提交又稱3PC志秃,其在兩階段提交的基礎(chǔ)上增加了 CanCommit階段,并引入了 超時機制咳焚。一旦事務參與者遲遲沒有收到協(xié)調(diào)者的Commit請求洽损,就會自動進行本地commit庞溜,這樣相對有效地解決了協(xié)調(diào)者單點故障的問題革半。
但是性能問題和不一致問題仍然沒有根本解決。下面我們還是一起看下三階段流程的是什么樣的流码?
第一階段:CanCommit階段
這個階段類似于2PC中的第二個階段中的Ready階段又官,是一種事務詢問操作,事務的協(xié)調(diào)者向所有參與者詢問“你們是否可以完成本次事務漫试?”六敬,如果參與者節(jié)點認為自身可以完成事務就返回“YES”,否則“NO”驾荣。而在實際的場景中參與者節(jié)點會對自身邏輯進行事務嘗試外构,其實說白了就是檢查下自身狀態(tài)的健康性,看有沒有能力進行事務操作播掷。
第二階段:PreCommit階段
在階段一中审编,如果所有的參與者都返回Yes的話,那么就會進入PreCommit階段進行事務預提交歧匈。此時分布式事務協(xié)調(diào)者會向所有的參與者節(jié)點發(fā)送PreCommit請求垒酬,參與者收到后開始執(zhí)行事務操作,并將Undo和Redo信息記錄到事務日志中件炉。參與者執(zhí)行完事務操作后(此時屬于未提交事務的狀態(tài))勘究,就會向協(xié)調(diào)者反饋“Ack”表示我已經(jīng)準備好提交了,并等待協(xié)調(diào)者的下一步指令斟冕。
如果階段一中有任何一個參與者節(jié)點返回的結(jié)果是No響應口糕,或者協(xié)調(diào)者在等待參與者節(jié)點反饋的過程中因掛掉而超時(2PC中只有協(xié)調(diào)者可以超時,參與者沒有超時機制)磕蛇。整個分布式事務就會中斷景描,協(xié)調(diào)者就會向所有的參與者發(fā)送“abort”請求券时。
第三階段:DoCommit階段
在階段二中如果所有的參與者節(jié)點都可以進行PreCommit提交,那么協(xié)調(diào)者就會從“預提交狀態(tài)” 轉(zhuǎn)變?yōu)?“提交狀態(tài)”伏伯。然后向所有的參與者節(jié)點發(fā)送"doCommit"請求橘洞,參與者節(jié)點在收到提交請求后就會各自執(zhí)行事務提交操作,并向協(xié)調(diào)者節(jié)點反饋“Ack”消息说搅,協(xié)調(diào)者收到所有參與者的Ack消息后完成事務炸枣。
看到這里,你是不是會疑惑"3PC相對于2PC而言到底優(yōu)化了什么地方呢?"
相比較2PC而言弄唧,3PC對于協(xié)調(diào)者(Coordinator)和參與者(Partcipant)都設置了超時時間适肠,而2PC只有協(xié)調(diào)者才擁有超時機制。這解決了一個什么問題呢候引?這個優(yōu)化點侯养,主要是避免了參與者在長時間無法與協(xié)調(diào)者節(jié)點通訊(協(xié)調(diào)者掛掉了)的情況下,無法釋放資源的問題澄干,因為參與者自身擁有超時機制會在超時后逛揩,自動進行本地commit從而進行釋放資源。而這種機制也側(cè)面降低了整個事務的阻塞時間和范圍麸俘。
另外辩稽,通過CanCommit、PreCommit从媚、DoCommit三個階段的設計逞泄,相較于2PC而言,多設置了一個緩沖階段保證了在最后提交階段之前各參與節(jié)點的狀態(tài)是一致的拜效。
以上就是3PC相對于2PC的一個提高(相對緩解了2PC中的前兩個問題)喷众,但是3PC依然沒有完全解決數(shù)據(jù)不一致的問題。假如在 DoCommit 過程紧憾,參與者A無法接收協(xié)調(diào)者的通信到千,那么參與者A會自動提交,但是提交失敗了稻励,其他參與者成功了父阻,此時數(shù)據(jù)就會不一致。