分布式

1. 分布式事物

不知道你是否遇到過(guò)這樣的情況爵赵,去小賣(mài)鋪買(mǎi)東西吝秕,付了錢(qián),但是店主因?yàn)樘幚砹艘恍┢渌驴栈茫尤煌浤愀读隋X(qián)烁峭,又叫你重新付。又或者在網(wǎng)上購(gòu)物明明已經(jīng)扣款秕铛,但是卻告訴我沒(méi)有發(fā)生交易则剃。這一系列情況都是因?yàn)闆](méi)有事務(wù)導(dǎo)致的。這說(shuō)明了事務(wù)在生活中的一些重要性如捅。有了事務(wù),你去小賣(mài)鋪買(mǎi)東西调煎,那就是一手交錢(qián)一手交貨镜遣。有了事務(wù),你去網(wǎng)上購(gòu)物,扣款即產(chǎn)生訂單交易悲关。

2. 事務(wù)的具體定義

什么是分布式事務(wù)

  1. 分布式事務(wù)指事務(wù)的參與者谎僻、支持事務(wù)的服務(wù)器、資源服務(wù)器以及事務(wù)管理器分別位于不同的分布式系統(tǒng)的不同節(jié)點(diǎn)之上寓辱。
    簡(jiǎn)單的說(shuō)艘绍,就是一次大的操作由不同的小操作組成,這些小的操作分布在不同的服務(wù)器上秫筏,且屬于不同的應(yīng)用诱鞠,分布式事務(wù)需要保證這些小操作要么全部成功,要么全部失敗这敬。

本質(zhì)上來(lái)說(shuō)航夺,分布式事務(wù)就是為了保證不同數(shù)據(jù)庫(kù)的數(shù)據(jù)一致性

  1. 事務(wù)提供一種機(jī)制將一個(gè)活動(dòng)涉及的所有操作納入到一個(gè)不可分割的執(zhí)行單元,組成事務(wù)的所有操作只有在所有操作均能正常執(zhí)行的情況下方能提交崔涂,只要其中任一操作執(zhí)行失敗阳掐,都將導(dǎo)致整個(gè)事務(wù)的回滾。

簡(jiǎn)單地說(shuō)冷蚂,事務(wù)提供一種“要么什么都不做缭保,要么做全套(All or Nothing)”機(jī)制。

3. ACID(數(shù)據(jù)庫(kù)事務(wù))

說(shuō)到數(shù)據(jù)庫(kù)事務(wù)就不得不說(shuō)蝙茶,數(shù)據(jù)庫(kù)事務(wù)中的四大特性 ACID:

A:原子性(Atomicity)艺骂,一個(gè)事務(wù)(transaction)中的所有操作,要么全部完成尸闸,要么全部不完成彻亲,不會(huì)結(jié)束在中間某個(gè)環(huán)節(jié)。

事務(wù)在執(zhí)行過(guò)程中發(fā)生錯(cuò)誤吮廉,會(huì)被回滾(Rollback)到事務(wù)開(kāi)始前的狀態(tài)苞尝,就像這個(gè)事務(wù)從來(lái)沒(méi)有執(zhí)行過(guò)一樣。

就像你買(mǎi)東西要么交錢(qián)收貨一起都執(zhí)行宦芦,要么發(fā)不出貨宙址,就退錢(qián)。

C:一致性(Consistency)调卑,事務(wù)的一致性指的是在一個(gè)事務(wù)執(zhí)行之前和執(zhí)行之后數(shù)據(jù)庫(kù)都必須處于一致性狀態(tài)抡砂。

如果事務(wù)成功地完成,那么系統(tǒng)中所有變化將正確地應(yīng)用恬涧,系統(tǒng)處于有效狀態(tài)注益。

如果在事務(wù)中出現(xiàn)錯(cuò)誤,那么系統(tǒng)中的所有變化將自動(dòng)地回滾溯捆,系統(tǒng)返回到原始狀態(tài)丑搔。

I:隔離性(Isolation),指的是在并發(fā)環(huán)境中,當(dāng)不同的事務(wù)同時(shí)操縱相同的數(shù)據(jù)時(shí)啤月,每個(gè)事務(wù)都有各自的完整數(shù)據(jù)空間煮仇。

由并發(fā)事務(wù)所做的修改必須與任何其他并發(fā)事務(wù)所做的修改隔離。事務(wù)查看數(shù)據(jù)更新時(shí)谎仲,數(shù)據(jù)所處的狀態(tài)要么是另一事務(wù)修改它之前的狀態(tài)浙垫,要么是另一事務(wù)修改它之后的狀態(tài),事務(wù)不會(huì)查看到中間狀態(tài)的數(shù)據(jù)郑诺。

打個(gè)比方夹姥,你買(mǎi)東西這個(gè)事情,是不影響其他人的间景。

D:持久性(Durability)佃声,指的是只要事務(wù)成功結(jié)束,它對(duì)數(shù)據(jù)庫(kù)所做的更新就必須永久保存下來(lái)倘要。

即使發(fā)生系統(tǒng)崩潰圾亏,重新啟動(dòng)數(shù)據(jù)庫(kù)系統(tǒng)后,數(shù)據(jù)庫(kù)還能恢復(fù)到事務(wù)成功結(jié)束時(shí)的狀態(tài)封拧。

打個(gè)比方志鹃,你買(mǎi)東西的時(shí)候需要記錄在賬本上,即使老板忘記了那也有據(jù)可查泽西。

4. 分布式事務(wù)的基礎(chǔ)

我們之前說(shuō)過(guò)數(shù)據(jù)庫(kù)的 ACID 四大特性曹铃,已經(jīng)無(wú)法滿足我們分布式事務(wù),這個(gè)時(shí)候又有一些新的大佬提出一些新的理論捧杉。
CAP

CAP 定理陕见,又被叫作布魯爾定理。對(duì)于設(shè)計(jì)分布式系統(tǒng)(不僅僅是分布式事務(wù))的架構(gòu)師來(lái)說(shuō)味抖,CAP 就是你的入門(mén)理論评甜。

C (一致性):對(duì)某個(gè)指定的客戶端來(lái)說(shuō),讀操作能返回最新的寫(xiě)操作仔涩。

對(duì)于數(shù)據(jù)分布在不同節(jié)點(diǎn)上的數(shù)據(jù)來(lái)說(shuō)忍坷,如果在某個(gè)節(jié)點(diǎn)更新了數(shù)據(jù),那么在其他節(jié)點(diǎn)如果都能讀取到這個(gè)最新的數(shù)據(jù)熔脂,那么就稱(chēng)為強(qiáng)一致佩研,如果有某個(gè)節(jié)點(diǎn)沒(méi)有讀取到,那就是分布式不一致霞揉。

A (可用性):非故障的節(jié)點(diǎn)在合理的時(shí)間內(nèi)返回合理的響應(yīng)(不是錯(cuò)誤和超時(shí)的響應(yīng))酝惧≌焉欤可用性的兩個(gè)關(guān)鍵一個(gè)是合理的時(shí)間丐黄,一個(gè)是合理的響應(yīng)。

合理的時(shí)間指的是請(qǐng)求不能無(wú)限被阻塞些侍,應(yīng)該在合理的時(shí)間給出返回。合理的響應(yīng)指的是系統(tǒng)應(yīng)該明確返回結(jié)果并且結(jié)果是正確的政模,這里的正確指的是比如應(yīng)該返回 50,而不是返回 40蚂会。

P (分區(qū)容錯(cuò)性):當(dāng)出現(xiàn)網(wǎng)絡(luò)分區(qū)后淋样,系統(tǒng)能夠繼續(xù)工作。打個(gè)比方胁住,這里集群有多臺(tái)機(jī)器趁猴,有臺(tái)機(jī)器網(wǎng)絡(luò)出現(xiàn)了問(wèn)題,但是這個(gè)集群仍然可以正常工作彪见。

熟悉 CAP 的人都知道儡司,三者不能共有,如果感興趣可以搜索 CAP 的證明余指,在分布式系統(tǒng)中捕犬,網(wǎng)絡(luò)無(wú)法 100% 可靠,分區(qū)其實(shí)是一個(gè)必然現(xiàn)象酵镜。

如果我們選擇了 CA 而放棄了 P碉碉,那么當(dāng)發(fā)生分區(qū)現(xiàn)象時(shí),為了保證一致性淮韭,這個(gè)時(shí)候必須拒絕請(qǐng)求垢粮,但是 A 又不允許,所以分布式系統(tǒng)理論上不可能選擇 CA 架構(gòu)靠粪,只能選擇 CP 或者 AP 架構(gòu)蜡吧。

對(duì)于 CP 來(lái)說(shuō),放棄可用性占键,追求一致性和分區(qū)容錯(cuò)性昔善,我們的 ZooKeeper 其實(shí)就是追求的強(qiáng)一致。

對(duì)于 AP 來(lái)說(shuō)捞慌,放棄一致性(這里說(shuō)的一致性是強(qiáng)一致性)耀鸦,追求分區(qū)容錯(cuò)性和可用性,這是很多分布式系統(tǒng)設(shè)計(jì)時(shí)的選擇啸澡,后面的 BASE 也是根據(jù) AP 來(lái)擴(kuò)展袖订。

順便一提,CAP 理論中是忽略網(wǎng)絡(luò)延遲嗅虏,也就是當(dāng)事務(wù)提交時(shí)洛姑,從節(jié)點(diǎn) A 復(fù)制到節(jié)點(diǎn) B 沒(méi)有延遲,但是在現(xiàn)實(shí)中這個(gè)是明顯不可能的皮服,所以總會(huì)有一定的時(shí)間是不一致楞艾。

同時(shí) CAP 中選擇兩個(gè)参咙,比如你選擇了 CP,并不是叫你放棄 A硫眯。因?yàn)?P 出現(xiàn)的概率實(shí)在是太小了蕴侧,大部分的時(shí)間你仍然需要保證 CA。

就算分區(qū)出現(xiàn)了你也要為后來(lái)的 A 做準(zhǔn)備两入,比如通過(guò)一些日志的手段净宵,是其他機(jī)器回復(fù)至可用。

5. 2PC

說(shuō)到 2PC 就不得不聊數(shù)據(jù)庫(kù)分布式事務(wù)中的 XA Transactions裹纳。

在 XA 協(xié)議中分為兩階段:

事務(wù)管理器要求每個(gè)涉及到事務(wù)的數(shù)據(jù)庫(kù)預(yù)提交(precommit)此操作择葡,并反映是否可以提交。

事務(wù)協(xié)調(diào)器要求每個(gè)數(shù)據(jù)庫(kù)提交數(shù)據(jù)剃氧,或者回滾數(shù)據(jù)敏储。

優(yōu)點(diǎn):

盡量保證了數(shù)據(jù)的強(qiáng)一致,實(shí)現(xiàn)成本較低朋鞍,在各大主流數(shù)據(jù)庫(kù)都有自己實(shí)現(xiàn)已添,對(duì)于 MySQL 是從 5.5 開(kāi)始支持。

缺點(diǎn)

單點(diǎn)問(wèn)題:事務(wù)管理器在整個(gè)流程中扮演的角色很關(guān)鍵番舆,如果其宕機(jī)酝碳,比如在第一階段已經(jīng)完成,在第二階段正準(zhǔn)備提交的時(shí)候事務(wù)管理器宕機(jī)恨狈,資源管理器就會(huì)一直阻塞疏哗,導(dǎo)致數(shù)據(jù)庫(kù)無(wú)法使用。

同步阻塞:在準(zhǔn)備就緒之后禾怠,資源管理器中的資源一直處于阻塞返奉,直到提交完成,釋放資源吗氏。

數(shù)據(jù)不一致:兩階段提交協(xié)議雖然為分布式數(shù)據(jù)強(qiáng)一致性所設(shè)計(jì)芽偏,但仍然存在數(shù)據(jù)不一致性的可能。

比如在第二階段中弦讽,假設(shè)協(xié)調(diào)者發(fā)出了事務(wù) Commit 的通知污尉,但是因?yàn)榫W(wǎng)絡(luò)問(wèn)題該通知僅被一部分參與者所收到并執(zhí)行了 Commit 操作,其余的參與者則因?yàn)闆](méi)有收到通知一直處于阻塞狀態(tài)往产,這時(shí)候就產(chǎn)生了數(shù)據(jù)的不一致性被碗。

總的來(lái)說(shuō),XA 協(xié)議比較簡(jiǎn)單仿村,成本較低锐朴,但是其單點(diǎn)問(wèn)題,以及不能支持高并發(fā)(由于同步阻塞)依然是其最大的弱點(diǎn)蔼囊。

6. 3pc

3.1 簡(jiǎn)述

三階段提交(Three-phase commit)焚志,是二階段提交(2PC)的改進(jìn)版本衣迷。

與兩階段提交不同的是,三階段提交有兩個(gè)改動(dòng)點(diǎn)酱酬。

  • 引入超時(shí)機(jī)制壶谒。同時(shí)在協(xié)調(diào)者和參與者中都引入超時(shí)機(jī)制。

  • 在第一階段和第二階段中插入一個(gè)準(zhǔn)備階段膳沽。保證了在最后提交階段之前各參與節(jié)點(diǎn)的狀態(tài)是一致的佃迄。

也就是說(shuō),除了引入超時(shí)機(jī)制之外贵少,3PC把2PC的準(zhǔn)備階段再次一分為二,這樣三階段提交就有CanCommit堆缘、PreCommit滔灶、DoCommit三個(gè)階段。

3.2 CanCommit階段

3PC的CanCommit階段其實(shí)和2PC的準(zhǔn)備階段很像吼肥。協(xié)調(diào)者向參與者發(fā)送commit請(qǐng)求录平,參與者如果可以提交就返回Yes響應(yīng),否則返回No響應(yīng)缀皱。

  • 事務(wù)詢問(wèn) 協(xié)調(diào)者向參與者發(fā)送CanCommit請(qǐng)求斗这。詢問(wèn)是否可以執(zhí)行事務(wù)提交操作。然后開(kāi)始等待參與者的響應(yīng)啤斗。

  • 響應(yīng)反饋 參與者接到CanCommit請(qǐng)求之后表箭,正常情況下,如果其自身認(rèn)為可以順利執(zhí)行事務(wù)钮莲,則返回Yes響應(yīng)免钻,并進(jìn)入預(yù)備狀態(tài)。否則反饋No

3.3 PreCommit階段

協(xié)調(diào)者根據(jù)參與者的反應(yīng)情況來(lái)決定是否可以記性事務(wù)的PreCommit操作崔拥。根據(jù)響應(yīng)情況极舔,有以下兩種可能。

假如協(xié)調(diào)者從所有的參與者獲得的反饋都是Yes響應(yīng)链瓦,那么就會(huì)執(zhí)行事務(wù)的預(yù)執(zhí)行拆魏。

  • 發(fā)送預(yù)提交請(qǐng)求 協(xié)調(diào)者向參與者發(fā)送PreCommit請(qǐng)求,并進(jìn)入Prepared階段慈俯。

  • 事務(wù)預(yù)提交 參與者接收到PreCommit請(qǐng)求后渤刃,會(huì)執(zhí)行事務(wù)操作,并將undo和redo信息記錄到事務(wù)日志中肥卡。

  • 響應(yīng)反饋 如果參與者成功的執(zhí)行了事務(wù)操作溪掀,則返回ACK響應(yīng),同時(shí)開(kāi)始等待最終指令步鉴。

假如有任何一個(gè)參與者向協(xié)調(diào)者發(fā)送了No響應(yīng)揪胃,或者等待超時(shí)之后璃哟,協(xié)調(diào)者都沒(méi)有接到參與者的響應(yīng),那么就執(zhí)行事務(wù)的中斷喊递。

  • 發(fā)送中斷請(qǐng)求 協(xié)調(diào)者向所有參與者發(fā)送abort請(qǐng)求随闪。

  • 中斷事務(wù) 參與者收到來(lái)自協(xié)調(diào)者的abort請(qǐng)求之后(或超時(shí)之后,仍未收到協(xié)調(diào)者的請(qǐng)求)骚勘,執(zhí)行事務(wù)的中斷铐伴。

3.4 doCommit階段

該階段進(jìn)行真正的事務(wù)提交,也可以分為以下兩種情況俏讹。

執(zhí)行提交
  • 發(fā)送提交請(qǐng)求 協(xié)調(diào)接收到參與者發(fā)送的ACK響應(yīng)当宴,那么他將從預(yù)提交狀態(tài)進(jìn)入到提交狀態(tài)。并向所有參與者發(fā)送doCommit請(qǐng)求泽疆。

  • 事務(wù)提交 參與者接收到doCommit請(qǐng)求之后户矢,執(zhí)行正式的事務(wù)提交。并在完成事務(wù)提交之后釋放所有事務(wù)資源殉疼。

  • 響應(yīng)反饋 事務(wù)提交完之后梯浪,向協(xié)調(diào)者發(fā)送Ack響應(yīng)。

  • 完成事務(wù) 協(xié)調(diào)者接收到所有參與者的ack響應(yīng)之后瓢娜,完成事務(wù)挂洛。

中斷事務(wù)

協(xié)調(diào)者沒(méi)有接收到參與者發(fā)送的ACK響應(yīng)(可能是接受者發(fā)送的不是ACK響應(yīng),也可能響應(yīng)超時(shí))眠砾,那么就會(huì)執(zhí)行中斷事務(wù)虏劲。

  • 發(fā)送中斷請(qǐng)求 協(xié)調(diào)者向所有參與者發(fā)送abort請(qǐng)求

  • 事務(wù)回滾 參與者接收到abort請(qǐng)求之后,利用其在階段二記錄的undo信息來(lái)執(zhí)行事務(wù)的回滾操作荠藤,并在完成回滾之后釋放所有的事務(wù)資源伙单。

  • 反饋結(jié)果 參與者完成事務(wù)回滾之后,向協(xié)調(diào)者發(fā)送ACK消息

  • 中斷事務(wù) 協(xié)調(diào)者接收到參與者反饋的ACK消息之后哈肖,執(zhí)行事務(wù)的中斷吻育。

7. 2pc和3pc的區(qū)別

相對(duì)于2PC,3PC主要解決的單點(diǎn)故障問(wèn)題淤井,并減少阻塞布疼,因?yàn)橐坏﹨⑴c者無(wú)法及時(shí)收到來(lái)自協(xié)調(diào)者的信息之后,他會(huì)默認(rèn)執(zhí)行commit币狠。而不會(huì)一直持有事務(wù)資源并處于阻塞狀態(tài)游两。但是這種機(jī)制也會(huì)導(dǎo)致數(shù)據(jù)一致性問(wèn)題,因?yàn)殇雒啵捎诰W(wǎng)絡(luò)原因贱案,協(xié)調(diào)者發(fā)送的abort響應(yīng)沒(méi)有及時(shí)被參與者接收到,那么參與者在等待超時(shí)之后執(zhí)行了commit操作止吐。這樣就和其他接到abort命令并執(zhí)行回滾的參與者之間存在數(shù)據(jù)不一致的情況宝踪。

在2PC中一個(gè)參與者的狀態(tài)只有它自己和協(xié)調(diào)者知曉侨糟,假如協(xié)調(diào)者提議后自身宕機(jī),在協(xié)調(diào)者備份啟用前一個(gè)參與者又宕機(jī)瘩燥,其他參與者就會(huì)進(jìn)入既不能回滾秕重、又不能強(qiáng)制commit的阻塞狀態(tài),直到參與者宕機(jī)恢復(fù)厉膀。

參與者如果在不同階段宕機(jī)溶耘,我們來(lái)看看3PC如何應(yīng)對(duì):

  • 階段1: 協(xié)調(diào)者或協(xié)調(diào)者備份未收到宕機(jī)參與者的vote,直接中止事務(wù)服鹅;宕機(jī)的參與者恢復(fù)后凳兵,讀取logging發(fā)現(xiàn)未發(fā)出贊成vote,自行中止該次事務(wù)

  • 階段2: 協(xié)調(diào)者未收到宕機(jī)參與者的precommit ACK企软,但因?yàn)橹耙呀?jīng)收到了宕機(jī)參與者的贊成反饋(不然也不會(huì)進(jìn)入到階段2)留荔,協(xié)調(diào)者進(jìn)行commit;協(xié)調(diào)者備份可以通過(guò)問(wèn)詢其他參與者獲得這些信息澜倦,過(guò)程同理;宕機(jī)的參與者恢復(fù)后發(fā)現(xiàn)收到precommit或已經(jīng)發(fā)出贊成vote杰妓,則自行commit該次事務(wù)

  • 階段3: 即便協(xié)調(diào)者或協(xié)調(diào)者備份未收到宕機(jī)參與者t的commit ACK藻治,也結(jié)束該次事務(wù);宕機(jī)的參與者恢復(fù)后發(fā)現(xiàn)收到commit或者precommit巷挥,也將自行commit該次事務(wù)

8. BASE

BASE 是 Basically Available(基本可用)桩卵、Soft state(軟狀態(tài))和 Eventually consistent (最終一致性)三個(gè)短語(yǔ)的縮寫(xiě),是對(duì) CAP 中 AP 的一個(gè)擴(kuò)展倍宾。

基本可用:分布式系統(tǒng)在出現(xiàn)故障時(shí)雏节,允許損失部分可用功能,保證核心功能可用高职。

軟狀態(tài):允許系統(tǒng)中存在中間狀態(tài)钩乍,這個(gè)狀態(tài)不影響系統(tǒng)可用性,這里指的是 CAP 中的不一致怔锌。

最終一致:最終一致是指經(jīng)過(guò)一段時(shí)間后寥粹,所有節(jié)點(diǎn)數(shù)據(jù)都將會(huì)達(dá)到一致。

BASE 解決了 CAP 中理論沒(méi)有網(wǎng)絡(luò)延遲埃元,在 BASE 中用軟狀態(tài)和最終一致涝涤,保證了延遲后的一致性。

BASE 和 ACID 是相反的岛杀,它完全不同于 ACID 的強(qiáng)一致性模型阔拳,而是通過(guò)犧牲強(qiáng)一致性來(lái)獲得可用性,并允許數(shù)據(jù)在一段時(shí)間內(nèi)是不一致的类嗤,但最終達(dá)到一致?tīng)顟B(tài)糊肠。

分布式事務(wù)解決方案

有了上面的理論基礎(chǔ)后辨宠,這里開(kāi)始介紹幾種常見(jiàn)的分布式事務(wù)的解決方案。

9. TCC(補(bǔ)償事務(wù))

關(guān)于 TCC(Try-Confirm-Cancel)的概念罪针,最早是由 Pat Helland 于 2007 年發(fā)表的一篇名為《Life beyond Distributed Transactions:an Apostate’s Opinion》的論文提出彭羹。

TCC 事務(wù)機(jī)制相比于上面介紹的 XA,解決了如下幾個(gè)缺點(diǎn):

解決了協(xié)調(diào)者單點(diǎn)泪酱,由主業(yè)務(wù)方發(fā)起并完成這個(gè)業(yè)務(wù)活動(dòng)派殷。業(yè)務(wù)活動(dòng)管理器也變成多點(diǎn),引入集群墓阀。

同步阻塞:引入超時(shí)毡惜,超時(shí)后進(jìn)行補(bǔ)償,并且不會(huì)鎖定整個(gè)資源斯撮,將資源轉(zhuǎn)換為業(yè)務(wù)邏輯形式经伙,粒度變小。

數(shù)據(jù)一致性勿锅,有了補(bǔ)償機(jī)制之后帕膜,由業(yè)務(wù)活動(dòng)管理器控制一致性。

對(duì)于 TCC 的解釋?zhuān)?/p>

Try 階段:嘗試執(zhí)行溢十,完成所有業(yè)務(wù)檢查(一致性)垮刹,預(yù)留必需業(yè)務(wù)資源(準(zhǔn)隔離性)。

Confirm 階段:確認(rèn)真正執(zhí)行業(yè)務(wù)张弛,不作任何業(yè)務(wù)檢查荒典,只使用 Try 階段預(yù)留的業(yè)務(wù)資源,Confirm 操作滿足冪等性吞鸭。要求具備冪等設(shè)計(jì)寺董,Confirm 失敗后需要進(jìn)行重試。

Cancel 階段:取消執(zhí)行刻剥,釋放 Try 階段預(yù)留的業(yè)務(wù)資源遮咖,Cancel 操作滿足冪等性。Cancel 階段的異常和 Confirm 階段異常處理方案基本上一致造虏。

舉個(gè)簡(jiǎn)單的例子:如果你用 100 元買(mǎi)了一瓶水盯滚, Try 階段:你需要向你的錢(qián)包檢查是否夠 100 元并鎖住這 100 元,水也是一樣的酗电。

如果有一個(gè)失敗魄藕,則進(jìn)行 Cancel(釋放這 100 元和這一瓶水),如果 Cancel 失敗不論什么失敗都進(jìn)行重試 Cancel撵术,所以需要保持冪等背率。

如果都成功,則進(jìn)行 Confirm,確認(rèn)這 100 元被扣寝姿,和這一瓶水被賣(mài)交排,如果 Confirm 失敗無(wú)論什么失敗則重試(會(huì)依靠活動(dòng)日志進(jìn)行重試)。

對(duì)于 TCC 來(lái)說(shuō)適合一些:

強(qiáng)隔離性饵筑,嚴(yán)格一致性要求的活動(dòng)業(yè)務(wù)埃篓。

執(zhí)行時(shí)間較短的業(yè)務(wù)

10. 本地消息表

對(duì)于本地消息隊(duì)列來(lái)說(shuō)核心是把大事務(wù)轉(zhuǎn)變?yōu)樾∈聞?wù)。還是舉上面用 100 元去買(mǎi)一瓶水的例子根资。

  1. 當(dāng)你扣錢(qián)的時(shí)候架专,你需要在你扣錢(qián)的服務(wù)器上新增加一個(gè)本地消息表,你需要把你扣錢(qián)和減去水的庫(kù)存寫(xiě)入到本地消息表玄帕,放入同一個(gè)事務(wù)(依靠數(shù)據(jù)庫(kù)本地事務(wù)保證一致性)部脚。

  2. 這個(gè)時(shí)候有個(gè)定時(shí)任務(wù)去輪詢這個(gè)本地事務(wù)表,把沒(méi)有發(fā)送的消息裤纹,扔給商品庫(kù)存服務(wù)器委刘,叫它減去水的庫(kù)存,到達(dá)商品服務(wù)器之后鹰椒,這時(shí)得先寫(xiě)入這個(gè)服務(wù)器的事務(wù)表锡移,然后進(jìn)行扣減,扣減成功后漆际,更新事務(wù)表中的狀態(tài)罩抗。

  3. 商品服務(wù)器通過(guò)定時(shí)任務(wù)掃描消息表或者直接通知扣錢(qián)服務(wù)器,扣錢(qián)服務(wù)器在本地消息表進(jìn)行狀態(tài)更新灿椅。

  4. 針對(duì)一些異常情況,定時(shí)掃描未成功處理的消息钞支,進(jìn)行重新發(fā)送茫蛹,在商品服務(wù)器接到消息之后,首先判斷是否是重復(fù)的烁挟。

如果已經(jīng)接收婴洼,再判斷是否執(zhí)行,如果執(zhí)行在馬上又進(jìn)行通知事務(wù)撼嗓;如果未執(zhí)行柬采,需要重新執(zhí)行由業(yè)務(wù)保證冪等,也就是不會(huì)多扣一瓶水且警。

本地消息隊(duì)列是 BASE 理論粉捻,是最終一致模型,適用于對(duì)一致性要求不高的情況斑芜。實(shí)現(xiàn)這個(gè)模型時(shí)需要注意重試的冪等肩刃。

11. MQ 事務(wù)

在 RocketMQ 中實(shí)現(xiàn)了分布式事務(wù),實(shí)際上是對(duì)本地消息表的一個(gè)封裝,將本地消息表移動(dòng)到了 MQ 內(nèi)部盈包。
基本流程如下:

第一階段 Prepared 消息沸呐,會(huì)拿到消息的地址。

第二階段執(zhí)行本地事務(wù)呢燥。

第三階段通過(guò)第一階段拿到的地址去訪問(wèn)消息崭添,并修改狀態(tài)。消息接受者就能使用這個(gè)消息叛氨。

如果確認(rèn)消息失敗呼渣,在 RocketMQ Broker 中提供了定時(shí)掃描沒(méi)有更新?tīng)顟B(tài)的消息。

如果有消息沒(méi)有得到確認(rèn)力试,會(huì)向消息發(fā)送者發(fā)送消息徙邻,來(lái)判斷是否提交,在 RocketMQ 中是以 Listener 的形式給發(fā)送者畸裳,用來(lái)處理缰犁。

如果消費(fèi)超時(shí),則需要一直重試怖糊,消息接收端需要保證冪等帅容。如果消息消費(fèi)失敗,這時(shí)就需要人工進(jìn)行處理伍伤,因?yàn)檫@個(gè)概率較低并徘,如果為了這種小概率時(shí)間而設(shè)計(jì)這個(gè)復(fù)雜的流程反而得不償失。

生產(chǎn)者和消費(fèi)者的理解

在實(shí)際的軟件開(kāi)發(fā)過(guò)程中扰魂,經(jīng)常會(huì)碰到如下場(chǎng)景:某個(gè)模塊負(fù)責(zé)產(chǎn)生數(shù)據(jù)麦乞,這些數(shù)據(jù)由另一個(gè)模塊來(lái)負(fù)責(zé)處理(此處的模塊是廣義的,可以是類(lèi)劝评、函數(shù)姐直、線程、進(jìn)程等)蒋畜。產(chǎn)生數(shù)據(jù)的模塊声畏,就形象地稱(chēng)為生產(chǎn)者;而處理數(shù)據(jù)的模塊姻成,就稱(chēng)為消費(fèi)者插龄。

單單抽象出生產(chǎn)者和消費(fèi)者,還夠不上是生產(chǎn)者/消費(fèi)者模式科展。該模式還需要有一個(gè)緩沖區(qū)處于生產(chǎn)者和消費(fèi)者之間均牢,作為一個(gè)中介。生產(chǎn)者把數(shù)據(jù)放入緩沖區(qū)才睹,而消費(fèi)者從緩沖區(qū)取出數(shù)據(jù)
優(yōu)點(diǎn)

  • 解耦膨处,即降低生產(chǎn)者和消費(fèi)者之間的依賴關(guān)系见秤。

    比如我們?cè)谛薷南M(fèi)者功能時(shí),不需要考慮生產(chǎn)者模塊的代碼真椿,同理對(duì)于生產(chǎn)者

  • 支持并發(fā)鹃答,即生產(chǎn)者和消費(fèi)者可以是兩個(gè)獨(dú)立的并發(fā)主體,互不干擾的運(yùn)行突硝。

    在生產(chǎn)者和消費(fèi)者之間测摔,因?yàn)槭峭ㄟ^(guò)隊(duì)列通訊,互不干擾解恰,所以可以起兩個(gè)線程锋八,各自完成自己的工作

  • 支持忙閑不均,如果制造數(shù)據(jù)的速度時(shí)快時(shí)慢护盈,緩沖區(qū)可以對(duì)其進(jìn)行適當(dāng)緩沖挟纱。當(dāng)數(shù)據(jù)制造快的時(shí)候,消費(fèi)者來(lái)不及處理腐宋,未處理的數(shù)據(jù)可以暫時(shí)存在緩沖區(qū)中紊服。等生產(chǎn)者的制造速度慢下來(lái),消費(fèi)者再慢慢處理掉胸竞。

12. 分布式緩存設(shè)計(jì)

目前常見(jiàn)的緩存方案都是分層緩存欺嗤,通常可以分為以下幾層:

  • NG 本地緩存卫枝,命中的話直接返回煎饼。

  • NG 沒(méi)有命中時(shí)則需要查詢分布式緩存,如 Redis 校赤。

  • 如果分布式緩存沒(méi)有命中則需要回源到 Tomcat 在本地堆進(jìn)行查詢吆玖,命中之后異步寫(xiě)回 Redis 。

  • 以上都沒(méi)有命中那就只有從 DB 或者是數(shù)據(jù)源進(jìn)行查詢马篮,并寫(xiě)回到 Redis 中沾乘。

13. 緩存更新的原子性

在寫(xiě)回 Redis 的時(shí)候如果是 Tomcat 集群,多個(gè)進(jìn)程同時(shí)寫(xiě)那很有可能出現(xiàn)臟數(shù)據(jù)积蔚,這時(shí)就會(huì)出現(xiàn)更新原子性的問(wèn)題。

可以有以下解決方案:

  • 可以將多個(gè) Tomcat 中的數(shù)據(jù)寫(xiě)入到 MQ 隊(duì)列中烦周,由消費(fèi)者進(jìn)行單線程更新緩存尽爆。

  • 利用分布式鎖,只有獲取到鎖進(jìn)程才能寫(xiě)數(shù)據(jù)读慎。

14. 如何寫(xiě)緩存

寫(xiě)緩存時(shí)也要注意漱贱,通常來(lái)說(shuō)分為以下幾步:

  • 開(kāi)啟事務(wù)。

  • 寫(xiě)入 DB 夭委。

  • 提交事務(wù)幅狮。

  • 寫(xiě)入緩存。

這里可能會(huì)存在數(shù)據(jù)庫(kù)寫(xiě)入成功但是緩存寫(xiě)入失敗的情況,但是也不建議將寫(xiě)入緩存加入到事務(wù)中崇摄。 因?yàn)閷?xiě)緩存的時(shí)候可能會(huì)因?yàn)榫W(wǎng)絡(luò)原因耗時(shí)較長(zhǎng)擎值,這樣會(huì)阻塞數(shù)據(jù)庫(kù)事務(wù)。 如果對(duì)一致性要求不高并且數(shù)據(jù)量也不大的情況下逐抑,可以單獨(dú)起一個(gè)服務(wù)來(lái)做 DB 和緩存之間的數(shù)據(jù)同步操作鸠儿。

更新緩存時(shí)也建議做增量更新。

15. 負(fù)載策略

緩存負(fù)載策略一般有以下兩種:

  • 輪詢機(jī)制厕氨。

  • 一致哈希算法进每。

輪詢的優(yōu)點(diǎn)是負(fù)載到各個(gè)服務(wù)器的請(qǐng)求是均勻的,但是如果進(jìn)行擴(kuò)容則緩存命中率會(huì)下降命斧。

一致哈希的優(yōu)點(diǎn)是相同的請(qǐng)求會(huì)負(fù)載到同一臺(tái)服務(wù)器上田晚,命中率不會(huì)隨著擴(kuò)容而降低,但是當(dāng)大流量過(guò)來(lái)時(shí)有可能把服務(wù)器拖垮国葬。

所以建議兩種方案都采用: 首先采用一致哈希算法贤徒,當(dāng)流量達(dá)到一定的閾值的時(shí)候則切換為輪詢,這樣既能保證緩存命中率胃惜,也能提高系統(tǒng)的可用性泞莉。

16. 基于redis的分布式鎖

分布式鎖在分布式應(yīng)用中應(yīng)用廣泛,想要搞懂一個(gè)新事物首先得了解它的由來(lái)船殉,這樣才能更加的理解甚至可以舉一反三鲫趁。

首先談到分布式鎖自然也就聯(lián)想到分布式應(yīng)用。

在我們將應(yīng)用拆分為分布式應(yīng)用之前的單機(jī)系統(tǒng)中利虫,對(duì)一些并發(fā)場(chǎng)景讀取公共資源時(shí)如扣庫(kù)存挨厚,賣(mài)車(chē)票之類(lèi)的需求可以簡(jiǎn)單的使用同步或者是加鎖就可以實(shí)現(xiàn)。

但是應(yīng)用分布式了之后系統(tǒng)由以前的單進(jìn)程多線程的程序變?yōu)榱硕噙M(jìn)程多線程糠惫,這時(shí)使用以上的解決方案明顯就不夠了疫剃。

因此業(yè)界常用的解決方案通常是借助于一個(gè)第三方組件并利用它自身的排他性來(lái)達(dá)到多進(jìn)程的互斥。如:

  • 基于 DB 的唯一索引硼讽。

  • 基于 ZK 的臨時(shí)有序節(jié)點(diǎn)巢价。

  • 基于 Redis 的 NX EX 參數(shù)。

這里主要基于 Redis 進(jìn)行討論固阁。

17. 實(shí)現(xiàn)

既然是選用了 Redis壤躲,那么它就得具有排他性才行。同時(shí)它最好也有鎖的一些基本特性:

  • 高性能(加备燃、解鎖時(shí)高性能)

  • 可以使用阻塞鎖與非阻塞鎖碉克。

  • 不能出現(xiàn)死鎖。

  • 可用性(不能出現(xiàn)節(jié)點(diǎn) down 掉后加鎖失敗)并齐。

這里利用 Redis set key 時(shí)的一個(gè) NX 參數(shù)可以保證在這個(gè) key 不存在的情況下寫(xiě)入成功漏麦。并且再加上 EX 參數(shù)可以讓該 key 在超時(shí)之后自動(dòng)刪除客税。

所以利用以上兩個(gè)特性可以保證在同一時(shí)刻只會(huì)有一個(gè)進(jìn)程獲得鎖,并且不會(huì)出現(xiàn)死鎖(最壞的情況就是超時(shí)自動(dòng)刪除 key)撕贞。

18. 加鎖

實(shí)現(xiàn)代碼如下:

private static final String SET_IF_NOT_EXIST = "NX";

private static final String SET_WITH_EXPIRE_TIME = "PX";

public boolean tryLock(String key, String request) {

String result = this.jedis.set(LOCK_PREFIX + key, request, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, 10 * TIME);

if (LOCK_MSG.equals(result)){

return true ;

}else {

return false ;

}

}

注意這里使用的 jedis 的

String set(String key, String value, String nxxx, String expx, long time);

該命令可以保證 NX EX 的原子性更耻。

一定不要把兩個(gè)命令(NX EX)分開(kāi)執(zhí)行,如果在 NX 之后程序出現(xiàn)問(wèn)題就有可能產(chǎn)生死鎖麻掸。

19. 阻塞鎖

同時(shí)也可以實(shí)現(xiàn)一個(gè)阻塞鎖:

//一直阻塞

public void lock(String key, String request) throws InterruptedException {

for (;;){

String result = this.jedis.set(LOCK_PREFIX + key, request, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, 10 * TIME);

if (LOCK_MSG.equals(result)){

break ;

}

//防止一直消耗 CPU

Thread.sleep(DEFAULT_SLEEP_TIME) ;

}

}

//自定義阻塞時(shí)間

public boolean lock(String key, String request,int blockTime) throws InterruptedException {

while (blockTime >= 0){

String result = this.jedis.set(LOCK_PREFIX + key, request, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, 10 * TIME);

if (LOCK_MSG.equals(result)){

return true ;

}

blockTime -= DEFAULT_SLEEP_TIME ;

Thread.sleep(DEFAULT_SLEEP_TIME) ;

}

return false ;

}

20. 解鎖

解鎖也很簡(jiǎn)單酥夭,其實(shí)就是把這個(gè) key 刪掉就萬(wàn)事大吉了,比如使用 del key 命令脊奋。

但現(xiàn)實(shí)往往沒(méi)有那么 easy熬北。

如果進(jìn)程 A 獲取了鎖設(shè)置了超時(shí)時(shí)間,但是由于執(zhí)行周期較長(zhǎng)導(dǎo)致到了超時(shí)時(shí)間之后鎖就自動(dòng)釋放了诚隙。這時(shí)進(jìn)程 B 獲取了該鎖執(zhí)行很快就釋放鎖讶隐。這樣就會(huì)出現(xiàn)進(jìn)程 B 將進(jìn)程 A 的鎖釋放了。

所以最好的方式是在每次解鎖時(shí)都需要判斷鎖是否是自己的久又。

這時(shí)就需要結(jié)合加鎖機(jī)制一起實(shí)現(xiàn)了巫延。

加鎖時(shí)需要傳遞一個(gè)參數(shù),將該參數(shù)作為這個(gè) key 的 value地消,這樣每次解鎖時(shí)判斷 value 是否相等即可炉峰。

所以解鎖代碼就不能是簡(jiǎn)單的 del了。

public boolean unlock(String key,String request){

//lua script

String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";

Object result = null ;

if (jedis instanceof Jedis){

result = ((Jedis)this.jedis).eval(script, Collections.singletonList(LOCK_PREFIX + key), Collections.singletonList(request));

}else if (jedis instanceof JedisCluster){

result = ((JedisCluster)this.jedis).eval(script, Collections.singletonList(LOCK_PREFIX + key), Collections.singletonList(request));

}else {

//throw new RuntimeException("instance is error") ;

return false ;

}

if (UNLOCK_MSG.equals(result)){

return true ;

}else {

return false ;

}

}

這里使用了一個(gè) lua 腳本來(lái)判斷 value 是否相等脉执,相等才執(zhí)行 del 命令疼阔。

使用 lua 也可以保證這里兩個(gè)操作的原子性。

因此上文提到的四個(gè)基本特性也能滿足了:

  • 使用 Redis 可以保證性能半夷。

  • 阻塞鎖與非阻塞鎖見(jiàn)上文婆廊。

  • 利用超時(shí)機(jī)制解決了死鎖。

  • Redis 支持集群部署提高了可用性巫橄。

21. 總結(jié)

至此一個(gè)基于 Redis 的分布式鎖完成淘邻,但是依然有些問(wèn)題。

  • 如在 key 超時(shí)之后業(yè)務(wù)并沒(méi)有執(zhí)行完畢但卻自動(dòng)釋放鎖了湘换,這樣就會(huì)導(dǎo)致并發(fā)問(wèn)題宾舅。

  • 就算 Redis 是集群部署的,如果每個(gè)節(jié)點(diǎn)都只是 master 沒(méi)有 slave彩倚,那么 master 宕機(jī)時(shí)該節(jié)點(diǎn)上的所有 key 在那一時(shí)刻都相當(dāng)于是釋放鎖了筹我,這樣也會(huì)出現(xiàn)并發(fā)問(wèn)題。就算是有 slave 節(jié)點(diǎn)署恍,但如果在數(shù)據(jù)同步到 salve 之前 master 宕機(jī)也是會(huì)出現(xiàn)上面的問(wèn)題崎溃。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末蜻直,一起剝皮案震驚了整個(gè)濱河市盯质,隨后出現(xiàn)的幾起案子袁串,更是在濱河造成了極大的恐慌,老刑警劉巖呼巷,帶你破解...
    沈念sama閱讀 217,277評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件囱修,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡王悍,警方通過(guò)查閱死者的電腦和手機(jī)破镰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)压储,“玉大人鲜漩,你說(shuō)我怎么就攤上這事〖铮” “怎么了孕似?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,624評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)刮刑。 經(jīng)常有香客問(wèn)我喉祭,道長(zhǎng),這世上最難降的妖魔是什么雷绢? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,356評(píng)論 1 293
  • 正文 為了忘掉前任泛烙,我火速辦了婚禮,結(jié)果婚禮上翘紊,老公的妹妹穿的比我還像新娘蔽氨。我一直安慰自己,他們只是感情好霞溪,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布孵滞。 她就那樣靜靜地躺著,像睡著了一般鸯匹。 火紅的嫁衣襯著肌膚如雪坊饶。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,292評(píng)論 1 301
  • 那天殴蓬,我揣著相機(jī)與錄音匿级,去河邊找鬼。 笑死染厅,一個(gè)胖子當(dāng)著我的面吹牛痘绎,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播肖粮,決...
    沈念sama閱讀 40,135評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼孤页,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了涩馆?” 一聲冷哼從身側(cè)響起行施,我...
    開(kāi)封第一講書(shū)人閱讀 38,992評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤允坚,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后蛾号,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體稠项,經(jīng)...
    沈念sama閱讀 45,429評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評(píng)論 3 334
  • 正文 我和宋清朗相戀三年鲜结,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了展运。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,785評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡精刷,死狀恐怖拗胜,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情怒允,我是刑警寧澤挤土,帶...
    沈念sama閱讀 35,492評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站误算,受9級(jí)特大地震影響仰美,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜儿礼,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評(píng)論 3 328
  • 文/蒙蒙 一咖杂、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蚊夫,春花似錦诉字、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,723評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至琅轧,卻和暖如春伍绳,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背乍桂。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,858評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工冲杀, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人睹酌。 一個(gè)月前我還...
    沈念sama閱讀 47,891評(píng)論 2 370
  • 正文 我出身青樓权谁,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親憋沿。 傳聞我的和親對(duì)象是個(gè)殘疾皇子旺芽,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容