掃二維碼與項(xiàng)目經(jīng)理溝通
我們?cè)谖⑿派?4小時(shí)期待你的聲音
解答本文疑問(wèn)/技術(shù)咨詢(xún)/運(yùn)營(yíng)咨詢(xún)/技術(shù)建議/互聯(lián)網(wǎng)交流
作者:陳明羽 2019-10-21 08:51:41
架構(gòu)
分布式 一個(gè)復(fù)雜的系統(tǒng)往往都是從一個(gè)小而簡(jiǎn)的系統(tǒng)發(fā)展衍化而來(lái),為了滿(mǎn)足日益增長(zhǎng)的業(yè)務(wù)需求,不斷的增加系統(tǒng)的復(fù)雜度,從單體架構(gòu)逐步發(fā)展為分布式架構(gòu),而分布式系統(tǒng)架構(gòu)的設(shè)計(jì)主要關(guān)注:高性能,高可用,高拓展。

一個(gè)復(fù)雜的系統(tǒng)往往都是從一個(gè)小而簡(jiǎn)的系統(tǒng)發(fā)展衍化而來(lái),為了滿(mǎn)足日益增長(zhǎng)的業(yè)務(wù)需求,不斷的增加系統(tǒng)的復(fù)雜度,從單體架構(gòu)逐步發(fā)展為分布式架構(gòu),而分布式系統(tǒng)架構(gòu)的設(shè)計(jì)主要關(guān)注:高性能,高可用,高拓展。
圖片來(lái)自 Pexels
分布式事務(wù)
高可用是指系統(tǒng)無(wú)中斷的執(zhí)行功能的能力,代表了系統(tǒng)的可用程度,是進(jìn)行系統(tǒng)設(shè)計(jì)時(shí)必須要遵守的準(zhǔn)則之一。
而高可用的實(shí)現(xiàn)方案,無(wú)外乎就是冗余,就存儲(chǔ)的高可用而言,問(wèn)題不在于如何進(jìn)行數(shù)據(jù)備份,而在于如何規(guī)避數(shù)據(jù)不一致對(duì)業(yè)務(wù)造成的影響。
對(duì)于分布式系統(tǒng)而言,要保證分布式系統(tǒng)中的數(shù)據(jù)一致性就需要一種方案,可以保證數(shù)據(jù)在子系統(tǒng)中始終保持一致,避免業(yè)務(wù)出現(xiàn)問(wèn)題。
這種實(shí)現(xiàn)方案就叫做分布式事務(wù),要么一起成功,要么一起失敗,必須是一個(gè)整體性的事務(wù)。
理論基礎(chǔ)
在講解具體方案之前,有必要了解一下分布式中數(shù)據(jù)設(shè)計(jì)需要遵循的理論基礎(chǔ),CAP 理論和 BASE 理論,為后面的實(shí)踐鋪平道路。
CAP 理論
CAP,Consistency Availability Partition tolerance 的簡(jiǎn)寫(xiě):
因?yàn)榉植际较到y(tǒng)中系統(tǒng)肯定部署在多臺(tái)機(jī)器上,無(wú)法保證網(wǎng)絡(luò)做到 100% 的可靠,所以網(wǎng)絡(luò)分區(qū)一定存在,即 P 一定存在。
在出現(xiàn)網(wǎng)絡(luò)分區(qū)后,就出現(xiàn)了可用性和一致性的問(wèn)題,我們必須要在這兩者之間進(jìn)行取舍,因此就有了兩種架構(gòu):
①CP 架構(gòu)
當(dāng)網(wǎng)絡(luò)分區(qū)出現(xiàn)后,為了保證一致性,就必須拒接請(qǐng)求,否則無(wú)法保證一致性:
上面這種方式就違背了可用性的要求,只滿(mǎn)足一致性和分區(qū)容錯(cuò),即 CP,CAP 理論是忽略網(wǎng)絡(luò)延遲,從系統(tǒng) A 同步數(shù)據(jù)到系統(tǒng) B 的網(wǎng)絡(luò)延遲是忽略的。
CP 架構(gòu)保證了客戶(hù)端在獲取數(shù)據(jù)時(shí)一定是最近的寫(xiě)操作,或者獲取到異常信息,絕不會(huì)出現(xiàn)數(shù)據(jù)不一致的情況。
②AP 架構(gòu)
當(dāng)網(wǎng)絡(luò)分區(qū)出現(xiàn)后,為了保證可用性,系統(tǒng) B 可以返回舊值,保證系統(tǒng)的可用性:
上面這種方式就違背了一致性的要求,只滿(mǎn)足可用性和分區(qū)容錯(cuò),即 AP,AP 架構(gòu)保證了客戶(hù)端在獲取數(shù)據(jù)時(shí)無(wú)論返回的是最新值還是舊值,系統(tǒng)一定是可用的。
CAP 理論關(guān)注粒度是數(shù)據(jù),而不是整體系統(tǒng)設(shè)計(jì)的策略。
BASE 理論
BASE 理論指的是基本可用 Basically Available,軟狀態(tài) Soft State,最終一致性 Eventual Consistency,核心思想是即便無(wú)法做到強(qiáng)一致性,但應(yīng)該采用適合的方式保證最終一致性。
BASE,Basically Available Soft State Eventual Consistency 的簡(jiǎn)寫(xiě):
分布式事務(wù)協(xié)議
X/Open XA 協(xié)議
XA 是一個(gè)分布式事務(wù)協(xié)議,由 Tuxedo 提出。XA 規(guī)范主要定義了(全局)事務(wù)管理器(Transaction Manager)和(局部)資源管理器(Resource Manager)之間的接口。
XA 接口是雙向的系統(tǒng)接口,在事務(wù)管理器(Transaction Manager)以及一個(gè)或多個(gè)資源管理器(Resource Manager)之間形成通信橋梁。
XA 協(xié)議采用兩階段提交方式來(lái)管理分布式事務(wù)。XA 接口提供資源管理器與事務(wù)管理器之間進(jìn)行通信的標(biāo)準(zhǔn)接口。
2PC:二階段提交協(xié)議
二階段提交(Two-phase Commit),是指,為了使基于分布式系統(tǒng)架構(gòu)下的所有節(jié)點(diǎn)在進(jìn)行事務(wù)提交時(shí)保持一致性而設(shè)計(jì)的一種算法(Algorithm)。通常,二階段提交也被稱(chēng)為是一種協(xié)議(Protocol)。
在分布式系統(tǒng)中,每個(gè)節(jié)點(diǎn)雖然可以知曉自己的操作是成功或者失敗,卻無(wú)法知道其他節(jié)點(diǎn)的操作是成功或失敗。
當(dāng)一個(gè)事務(wù)跨越多個(gè)節(jié)點(diǎn)時(shí),為了保持事務(wù)的 ACID 特性,需要引入一個(gè)作為協(xié)調(diào)者的組件來(lái)統(tǒng)一掌控所有節(jié)點(diǎn)(稱(chēng)作參與者)的操作結(jié)果并最終指示這些節(jié)點(diǎn)是否要把操作結(jié)果進(jìn)行真正的提交(比如將更新后的數(shù)據(jù)寫(xiě)入磁盤(pán)等等)。
因此,二階段提交的算法思路可以概括為:參與者將操作成敗通知協(xié)調(diào)者,再由協(xié)調(diào)者根據(jù)所有參與者的反饋情報(bào)決定各參與者是否要提交操作還是中止操作。
二階段提交算法的成立基于以下假設(shè):
二階段提交分為兩階段:
投票階段 Prepares:
提交階段 Commit:
二階段提交優(yōu)點(diǎn):盡量保證了數(shù)據(jù)的強(qiáng)一致,但不是 100% 一致。
二階段提交缺點(diǎn):
而在這部分參與者接到提交事務(wù)請(qǐng)求之后就會(huì)執(zhí)行提交事務(wù)操作。但是其他部分未接收到提交事務(wù)請(qǐng)求的參與者則無(wú)法提交事務(wù)。從而導(dǎo)致分布式系統(tǒng)中的數(shù)據(jù)不一致。
二階段提交的問(wèn)題:如果協(xié)調(diào)者在第二階段發(fā)送提交請(qǐng)求之后掛掉,而唯一接受到這條消息的參與者執(zhí)行之后也掛掉了,即使協(xié)調(diào)者通過(guò)選舉協(xié)議產(chǎn)生了新的協(xié)調(diào)者并通知其他參與者進(jìn)行提交或回滾操作的話(huà),都可能會(huì)與這個(gè)已經(jīng)執(zhí)行的參與者執(zhí)行的操作不一樣。
當(dāng)這個(gè)掛掉的參與者恢復(fù)之后,就會(huì)產(chǎn)生數(shù)據(jù)不一致的問(wèn)題。
3PC:三階段提交協(xié)議
三階段提交(Three-phase commit),是為解決兩階段提交協(xié)議的缺點(diǎn)而設(shè)計(jì)的。與兩階段提交不同的是,三階段提交是“非阻塞”協(xié)議。
三階段提交在兩階段提交的第一階段與第二階段之間插入了一個(gè)準(zhǔn)備階段,使得原先在兩階段提交中,參與者在投票之后,由于協(xié)調(diào)者發(fā)生崩潰或錯(cuò)誤,而導(dǎo)致參與者處于無(wú)法知曉是否提交或者中止的“不確定狀態(tài)”所產(chǎn)生的可能相當(dāng)長(zhǎng)的延時(shí)的問(wèn)題得以解決。
三階段提交的三個(gè)階段:
①詢(xún)問(wèn)階段:CanCommit
協(xié)調(diào)者向參與者發(fā)送 Commit 請(qǐng)求,參與者如果可以提交就返回 Yes 響應(yīng),否則返回 No 響應(yīng)。
②準(zhǔn)備階段:PreCommit
協(xié)調(diào)者根據(jù)參與者在詢(xún)問(wèn)階段的響應(yīng)判斷是否執(zhí)行事務(wù)還是中斷事務(wù):
參與者執(zhí)行完操作之后返回 ACK 響應(yīng),同時(shí)開(kāi)始等待最終指令。
③提交階段:DoCommit
協(xié)調(diào)者根據(jù)參與者在準(zhǔn)備階段的響應(yīng)判斷是否執(zhí)行事務(wù)還是中斷事務(wù):
協(xié)調(diào)者收到所有參與者的 ACK 響應(yīng),完成事務(wù)。
解決二階段提交時(shí)的問(wèn)題:在三階段提交中,如果在第三階段協(xié)調(diào)者發(fā)送提交請(qǐng)求之后掛掉,并且唯一的接受的參與者執(zhí)行提交操作之后也掛掉了,這時(shí)協(xié)調(diào)者通過(guò)選舉協(xié)議產(chǎn)生了新的協(xié)調(diào)者。
在二階段提交時(shí)存在的問(wèn)題就是新的協(xié)調(diào)者不確定已經(jīng)執(zhí)行過(guò)事務(wù)的參與者是執(zhí)行的提交事務(wù)還是中斷事務(wù)。
但是在三階段提交時(shí),肯定得到了第二階段的再次確認(rèn),那么第二階段必然是已經(jīng)正確的執(zhí)行了事務(wù)操作,只等待提交事務(wù)了。
所以新的協(xié)調(diào)者可以從第二階段中分析出應(yīng)該執(zhí)行的操作,進(jìn)行提交或者中斷事務(wù)操作,這樣即使掛掉的參與者恢復(fù)過(guò)來(lái),數(shù)據(jù)也是一致的。
所以,三階段提交解決了二階段提交中存在的由于協(xié)調(diào)者和參與者同時(shí)掛掉可能導(dǎo)致的數(shù)據(jù)一致性問(wèn)題和單點(diǎn)故障問(wèn)題,并減少阻塞。
因?yàn)橐坏﹨⑴c者無(wú)法及時(shí)收到來(lái)自協(xié)調(diào)者的信息之后,他會(huì)默認(rèn)執(zhí)行提交事務(wù),而不會(huì)一直持有事務(wù)資源并處于阻塞狀態(tài)。
三階段提交的問(wèn)題:在提交階段如果發(fā)送的是中斷事務(wù)請(qǐng)求,但是由于網(wǎng)絡(luò)問(wèn)題,導(dǎo)致部分參與者沒(méi)有接到請(qǐng)求。
那么參與者會(huì)在等待超時(shí)之后執(zhí)行提交事務(wù)操作,這樣這些由于網(wǎng)絡(luò)問(wèn)題導(dǎo)致提交事務(wù)的參與者的數(shù)據(jù)就與接受到中斷事務(wù)請(qǐng)求的參與者存在數(shù)據(jù)不一致的問(wèn)題。
所以無(wú)論是 2PC 還是 3PC 都不能保證分布式系統(tǒng)中的數(shù)據(jù) 100% 一致。
解決方案
舉個(gè)栗子:在電商網(wǎng)站中,用戶(hù)對(duì)商品進(jìn)行下單,需要在訂單表中創(chuàng)建一條訂單數(shù)據(jù),同時(shí)需要在庫(kù)存表中修改當(dāng)前商品的剩余庫(kù)存數(shù)量。
兩步操作一個(gè)添加,一個(gè)修改,我們一定要保證這兩步操作一定同時(shí)操作成功或失敗,否則業(yè)務(wù)就會(huì)出現(xiàn)問(wèn)題。
建立時(shí):業(yè)務(wù)量不大,用戶(hù)少,系統(tǒng)只是一個(gè)單體架構(gòu),訂單表與庫(kù)存表都在一個(gè)數(shù)據(jù)庫(kù)中,這時(shí)可以使用 MySQL 的本地事務(wù)保證數(shù)據(jù)一致性。
發(fā)展期:業(yè)務(wù)發(fā)展迅速,用戶(hù)量變多,單數(shù)據(jù)已經(jīng)出現(xiàn)了性能瓶頸,按照業(yè)務(wù)緯度進(jìn)行分庫(kù),分為訂單庫(kù)和庫(kù)存庫(kù),由于跨庫(kù)跨機(jī)器,MySQL 的本地事務(wù)不能再保證訂單庫(kù)和庫(kù)存庫(kù)的數(shù)據(jù)一致性。
成熟期:業(yè)務(wù)拓展,單體架構(gòu)已經(jīng)滿(mǎn)足不了需求,進(jìn)而衍化成了分布式系統(tǒng),這時(shí)的訂單和庫(kù)存已經(jīng)拆分為了兩個(gè)子系統(tǒng)提供服務(wù),子系統(tǒng)間使用 RPC 進(jìn)行通信。
但是無(wú)論系統(tǒng)發(fā)展成什么樣,我們都要保證業(yè)務(wù)不出問(wèn)題,保證訂單和庫(kù)存的數(shù)據(jù)一致,這時(shí)候要思考下在服務(wù)之間我們應(yīng)如何保證數(shù)據(jù)一致。
強(qiáng)一致性分布式事務(wù)
單體架構(gòu)多數(shù)據(jù)源,在業(yè)務(wù)開(kāi)發(fā)中,肯定是先執(zhí)行對(duì)訂單庫(kù)的操作,但是不提交事務(wù),再執(zhí)行對(duì)庫(kù)存庫(kù)的操作,也不提交事務(wù),如果兩個(gè)操作都成功,在一起提交事務(wù),如果有一個(gè)操作失敗,則兩個(gè)都進(jìn)行回滾。
基于 2PC/XA 協(xié)議實(shí)現(xiàn)的 JTA:我們已經(jīng)知道了 2PC 和 XA 協(xié)議的原理,而 JTA 是 Java 規(guī)范,是 XA 在 Java 上的實(shí)現(xiàn)。
JTA(Java Transaction Manager):
JTA 主要的原理是二階段提交,當(dāng)整個(gè)業(yè)務(wù)完成了之后只是第一階段提交,在第二階段提交之前會(huì)檢查其他所有事務(wù)是否已經(jīng)提交。
如果前面出現(xiàn)了錯(cuò)誤或是沒(méi)有提交,那么第二階段就不會(huì)提交,而是直接回滾,這樣所有的事務(wù)都會(huì)做回滾操作?;?JTA 這種方案實(shí)現(xiàn)分布式事務(wù)的強(qiáng)一致性。
JTA 的特點(diǎn):
實(shí)現(xiàn)可以使用基于 JTA 實(shí)現(xiàn)的 Jar 包 Atomikos 例子可以自己百度一下。
正常架構(gòu)設(shè)計(jì)中是否應(yīng)該出現(xiàn)這種跨庫(kù)的操作,我覺(jué)得是不應(yīng)該的,如果按業(yè)務(wù)拆分將數(shù)據(jù)源進(jìn)行分庫(kù),我們應(yīng)該同時(shí)將服務(wù)也拆分出去才合適,應(yīng)遵循一個(gè)系統(tǒng)只操作一個(gè)數(shù)據(jù)源(主從沒(méi)關(guān)系),避免后續(xù)可能會(huì)出現(xiàn)的多個(gè)系統(tǒng)調(diào)用一個(gè)數(shù)據(jù)源的情況。
最終一致性分布式事務(wù)方案
JTA 方案適用于單體架構(gòu)多數(shù)據(jù)源時(shí)實(shí)現(xiàn)分布式事務(wù),但對(duì)于微服務(wù)間的分布式事務(wù)就無(wú)能為力了,我們需要使用其他的方案實(shí)現(xiàn)分布式事務(wù)。
①本地消息表
本地消息表的核心思想是將分布式事務(wù)拆分成本地事務(wù)進(jìn)行處理。
以本文中例子,在訂單系統(tǒng)新增一條消息表,將新增訂單和新增消息放到一個(gè)事務(wù)里完成,然后通過(guò)輪詢(xún)的方式去查詢(xún)消息表,將消息推送到 MQ,庫(kù)存系統(tǒng)去消費(fèi) MQ。
執(zhí)行流程:
訂單系統(tǒng)中的消息有可能由于業(yè)務(wù)問(wèn)題會(huì)一直重復(fù)發(fā)送,所以為了避免這種情況可以記錄一下發(fā)送次數(shù),當(dāng)達(dá)到次數(shù)限制之后報(bào)警,人工接入處理;庫(kù)存系統(tǒng)需要保證冪等,避免同一條消息被多次消費(fèi)造成數(shù)據(jù)一致。
本地消息表這種方案實(shí)現(xiàn)了最終一致性,需要在業(yè)務(wù)系統(tǒng)里增加消息表,業(yè)務(wù)邏輯中多一次插入的 DB 操作,所以性能會(huì)有損耗,而且最終一致性的間隔主要由定時(shí)任務(wù)的間隔時(shí)間決定。
②MQ 消息事務(wù)
消息事務(wù)的原理是將兩個(gè)事務(wù)通過(guò)消息中間件進(jìn)行異步解耦。
訂單系統(tǒng)執(zhí)行自己的本地事務(wù),并發(fā)送 MQ 消息,庫(kù)存系統(tǒng)接收消息,執(zhí)行自己的本地事務(wù)。
乍一看,好像跟本地消息表的實(shí)現(xiàn)方案類(lèi)似,只是省去了對(duì)本地消息表的操作和輪詢(xún)發(fā)送 MQ 的操作,但實(shí)際上兩種方案的實(shí)現(xiàn)是不一樣的。
消息事務(wù)一定要保證業(yè)務(wù)操作與消息發(fā)送的一致性,如果業(yè)務(wù)操作成功,這條消息也一定投遞成功。
消息事務(wù)依賴(lài)于消息中間件的事務(wù)消息,基于消息中間件的二階段提交實(shí)現(xiàn)的,RocketMQ 就支持事務(wù)消息。
執(zhí)行流程:
這種方案也是實(shí)現(xiàn)了最終一致性,對(duì)比本地消息表實(shí)現(xiàn)方案,不需要再建消息表,不再依賴(lài)本地?cái)?shù)據(jù)庫(kù)事務(wù)了,所以這種方案更適用于高并發(fā)的場(chǎng)景。
③最大努力通知
最大努力通知相比前兩種方案實(shí)現(xiàn)簡(jiǎn)單,適用于一些最終一致性要求較低的業(yè)務(wù),比如支付通知,短信通知這種業(yè)務(wù)。
以支付通知為例,業(yè)務(wù)系統(tǒng)調(diào)用支付平臺(tái)進(jìn)行支付,支付平臺(tái)進(jìn)行支付,進(jìn)行操作支付之后支付平臺(tái)會(huì)盡量去通知業(yè)務(wù)系統(tǒng)支付操作是否成功,但是會(huì)有一個(gè)最大通知次數(shù)。
如果超過(guò)這個(gè)次數(shù)后還是通知失敗,就不再通知,業(yè)務(wù)系統(tǒng)自行調(diào)用支付平臺(tái)提供一個(gè)查詢(xún)接口,供業(yè)務(wù)系統(tǒng)進(jìn)行查詢(xún)支付操作是否成功。
執(zhí)行流程:
這種方案也是實(shí)現(xiàn)了最終一致性。
④補(bǔ)償事務(wù) TCC
TCC,Try-Confirm-Cancel 的簡(jiǎn)稱(chēng),針對(duì)每個(gè)操作,都需要有一個(gè)其對(duì)應(yīng)的確認(rèn)和取消操作。
當(dāng)操作成功時(shí)調(diào)用確認(rèn)操作,當(dāng)操作失敗時(shí)調(diào)用取消操作,類(lèi)似于二階段提交,只不過(guò)是這里的提交和回滾是針對(duì)業(yè)務(wù)上的,所以基于 TCC 實(shí)現(xiàn)的分布式事務(wù)也可以看做是對(duì)業(yè)務(wù)的一種補(bǔ)償機(jī)制。
TCC 的三階段:
在 Try 階段,是對(duì)業(yè)務(wù)系統(tǒng)進(jìn)行檢查及資源預(yù)覽,比如訂單和存儲(chǔ)操作,需要檢查庫(kù)存剩余數(shù)量是否夠用,并進(jìn)行預(yù)留,預(yù)留操作的話(huà)就是新建一個(gè)可用庫(kù)存數(shù)量字段,Try 階段操作是對(duì)這個(gè)可用庫(kù)存數(shù)量進(jìn)行操作。
比如下一個(gè)訂單減一個(gè)庫(kù)存:
執(zhí)行流程:
基于 TCC 實(shí)現(xiàn)分布式事務(wù),代碼邏輯相對(duì)復(fù)雜一些,需要將原來(lái)的接口的邏輯拆分為:Try,Confirm ,Cancel 三個(gè)接口的邏輯。
基于 TCC 實(shí)現(xiàn)的分布式事務(wù)框架:
讀完之后應(yīng)該對(duì)分布式事務(wù)有了一個(gè)大致的了解,在實(shí)際生產(chǎn)中我們要盡量避免使用分布式事務(wù),能轉(zhuǎn)化為本地事務(wù)就用本地事務(wù),如果必須使用分布式事務(wù),還需要從業(yè)務(wù)角度多思考使用哪種方案更適合,總之行動(dòng)之前多思考。

我們?cè)谖⑿派?4小時(shí)期待你的聲音
解答本文疑問(wèn)/技術(shù)咨詢(xún)/運(yùn)營(yíng)咨詢(xún)/技術(shù)建議/互聯(lián)網(wǎng)交流