一、分布式事務(wù)
什么事分布式事務(wù)
分布式事務(wù)就是指事務(wù)的資源分別位于不同的分布式系統(tǒng)的不同節(jié)點(diǎn)之上的事務(wù)。
分布式事務(wù)產(chǎn)生的原因
1. 數(shù)據(jù)庫分庫分表
在單庫單表場景下,當(dāng)業(yè)務(wù)數(shù)據(jù)量達(dá)到單庫單表的極限時(shí),就需要考慮分庫分表,將之前的單庫單表拆分成多庫多表。
分庫分表之后,原來在單個(gè)數(shù)據(jù)庫上的事務(wù)操作,可能就變成跨多個(gè)數(shù)據(jù)庫的操作,此時(shí)就需要使用分布式事務(wù)。
在這種場景下,事務(wù)的提交會變得相對復(fù)雜,因?yàn)槎鄠€(gè)節(jié)點(diǎn)(庫)的存在,可能存在部分節(jié)點(diǎn)提交失敗的情況,即事務(wù)的ACID特性需要在各個(gè)不同的數(shù)據(jù)庫實(shí)例中保證。比如更新db1庫的A表時(shí),必須同步更新db2庫的B表,兩個(gè)更新形成一個(gè)事務(wù),要么都成功,要么都失敗。
2. 業(yè)務(wù)服務(wù)化
業(yè)務(wù)服務(wù)化即業(yè)務(wù)按照面向服務(wù)(SOA)的架構(gòu)拆分整個(gè)網(wǎng)站系統(tǒng)。
比如互聯(lián)網(wǎng)金融網(wǎng)站SOA拆分,分離出交易系統(tǒng)、賬務(wù)系統(tǒng)、清算系統(tǒng)等,交易系統(tǒng)負(fù)責(zé)交易管理和記錄交易明細(xì),賬務(wù)系統(tǒng)負(fù)責(zé)維護(hù)用戶余額,所有的業(yè)務(wù)操作都以服務(wù)的方式對外發(fā)布。
一筆金融交易操作需要同時(shí)記錄交易明細(xì)和完成用戶余額的轉(zhuǎn)賬,此時(shí)需要分別調(diào)用交易系統(tǒng)的交易明細(xì)服務(wù)和賬務(wù)系統(tǒng)的用戶余額服務(wù),這種跨應(yīng)用、跨服務(wù)的操作需要使用分布式事務(wù)才能保證金融數(shù)據(jù)的一致性。
3. 解決方案
3.1 兩階段提交(2PC)
兩階段提交(Two-phase Commit,2PC),通過引入?yún)f(xié)調(diào)者(Coordinator)來協(xié)調(diào)參與者的行為,并最終決定這些參與者是否要真正執(zhí)行事務(wù)。
(一)準(zhǔn)備階段
協(xié)調(diào)者詢問參與者事務(wù)是否執(zhí)行成功,參與者發(fā)回事務(wù)執(zhí)行結(jié)果。

(二)提交階段
如果事務(wù)在每個(gè)參與者上都執(zhí)行成功,事務(wù)協(xié)調(diào)者發(fā)送通知讓參與者提交事務(wù);否則,協(xié)調(diào)者發(fā)送通知讓參與者回滾事務(wù)。
需要注意的是,在準(zhǔn)備階段,參與者執(zhí)行了事務(wù),但是還未提交。只有在提交階段接收到協(xié)調(diào)者發(fā)來的通知后,才進(jìn)行提交或者回滾。

存在的問題:
- 同步阻塞 所有事務(wù)參與者在等待其它參與者響應(yīng)的時(shí)候都處于同步阻塞狀態(tài),無法進(jìn)行其它操作。
- 單點(diǎn)問題 協(xié)調(diào)者在 2PC 中起到非常大的作用,發(fā)生故障將會造成很大影響。特別是在階段二發(fā)生故障,所有參與者會一直等待狀態(tài),無法完成其它操作。
- 數(shù)據(jù)不一致 在階段二,如果協(xié)調(diào)者只發(fā)送了部分 Commit 消息,此時(shí)網(wǎng)絡(luò)發(fā)生異常,那么只有部分參與者接收到 Commit 消息,也就是說只有部分參與者提交了事務(wù),使得系統(tǒng)數(shù)據(jù)不一致。
- 太過保守 任意一個(gè)節(jié)點(diǎn)失敗就會導(dǎo)致整個(gè)事務(wù)失敗,沒有完善的容錯(cuò)機(jī)制。
3.2 2PC應(yīng)用之XA
XA需要兩階段提交: prepare 和 commit.
- 第一階段為 準(zhǔn)備(prepare)階段。即所有的參與者準(zhǔn)備執(zhí)行事務(wù)并鎖住需要的資源。參與者ready時(shí),向transaction manager報(bào)告已準(zhǔn)備就緒。
- 第二階段為提交階段(commit)。當(dāng)transaction manager確認(rèn)所有參與者都ready后,向所有參與者發(fā)送commit命令。
因?yàn)閄A 事務(wù)是基于兩階段提交協(xié)議的,所以需要有一個(gè)事務(wù)協(xié)調(diào)者(transaction manager)來保證所有的事務(wù)參與者都完成了準(zhǔn)備工作(第一階段)。如果事務(wù)協(xié)調(diào)者(transaction manager)收到所有參與者都準(zhǔn)備好的消息,就會通知所有的事務(wù)都可以提交了(第二階段)。MySQL 在這個(gè)XA事務(wù)中扮演的是參與者的角色,而不是事務(wù)協(xié)調(diào)者(transaction manager)。
XA 事務(wù)的缺點(diǎn)是性能不好,且無法滿足高并發(fā)場景。一個(gè)數(shù)據(jù)庫的事務(wù)和多個(gè)數(shù)據(jù)庫間的XA 事務(wù)性能會相差很多。因此,要盡量避免XA 事務(wù),如可以將數(shù)據(jù)寫入本地,用高性能的消息系統(tǒng)分發(fā)數(shù)據(jù),或使用數(shù)據(jù)庫復(fù)制等技術(shù)。只有在其他辦法都無法實(shí)現(xiàn)業(yè)務(wù)需求,且性能不是瓶頸時(shí)才使用XA。
3.3 2PC應(yīng)用之TCC
TCC 其實(shí)就是采用的補(bǔ)償機(jī)制,其核心思想是:針對每個(gè)操作,都要注冊一個(gè)與其對應(yīng)的確認(rèn)和補(bǔ)償(撤銷)操作。它分為三個(gè)階段:
- Try 階段主要是對業(yè)務(wù)系統(tǒng)做檢測及資源預(yù)留
- Confirm 階段主要是對業(yè)務(wù)系統(tǒng)做確認(rèn)提交,Try階段執(zhí)行成功并開始執(zhí)行 Confirm階段時(shí),默認(rèn) Confirm階段是不會出錯(cuò)的。即:只要Try成功,Confirm一定成功。
- Cancel 階段主要是在業(yè)務(wù)執(zhí)行錯(cuò)誤,需要回滾的狀態(tài)下執(zhí)行的業(yè)務(wù)取消,預(yù)留資源釋放。
特點(diǎn):不與具體的服務(wù)框架耦合,位于業(yè)務(wù)服務(wù)層,而不是資源層,可以靈活的選擇業(yè)務(wù)資源的鎖定粒度。TCC里對每個(gè)服務(wù)資源操作的是本地事務(wù),數(shù)據(jù)被鎖住的時(shí)間短,可擴(kuò)展性好,可以說是為獨(dú)立部署的SOA服務(wù)而設(shè)計(jì)的。強(qiáng)隔離性,嚴(yán)格一致性要求的業(yè)務(wù)活動(dòng)。適用于執(zhí)行時(shí)間較短的業(yè)務(wù),比如處理賬戶或者收費(fèi)等等。
TCC事務(wù)的優(yōu)缺點(diǎn):
- 優(yōu)點(diǎn):XA兩階段提交資源層面的,而TCC實(shí)際上把資源層面二階段提交上提到了業(yè)務(wù)層面來實(shí)現(xiàn)。有效了的避免了XA兩階段提交占用資源鎖時(shí)間過長導(dǎo)致的性能地下問題。
- 缺點(diǎn):主業(yè)務(wù)服務(wù)和從業(yè)務(wù)服務(wù)都需要進(jìn)行改造,從業(yè)務(wù)方改造成本更高。例如購票系統(tǒng),原來只需要提供一個(gè)購買接口,現(xiàn)在需要改造成try、confirm、canel3個(gè)接口,開發(fā)成本高。
二、分布式鎖
為什么使用分布式鎖
在單機(jī)場景下,可以使用 Java 提供的內(nèi)置鎖來實(shí)現(xiàn)進(jìn)程同步。但是在分布式場景下,需要同步的進(jìn)程可能位于不同的節(jié)點(diǎn)上,那么就需要使用分布式鎖。
分布式鎖應(yīng)該具備哪些條件
在分布式系統(tǒng)環(huán)境下,一個(gè)方法在同一時(shí)間只能被一個(gè)機(jī)器的一個(gè)線程執(zhí)行
高可用的獲取鎖與釋放鎖
高性能的獲取鎖與釋放鎖
具備可重入特性(可理解為重新進(jìn)入,由多于一個(gè)任務(wù)并發(fā)使用,而不必?fù)?dān)心數(shù)據(jù)錯(cuò)誤)
具備鎖失效機(jī)制,防止死鎖
具備非阻塞鎖特性,即沒有獲取到鎖將直接返回獲取鎖失敗
分布式鎖實(shí)現(xiàn)
1. 數(shù)據(jù)庫的唯一索引
當(dāng)想要獲得鎖時(shí),就向表中插入一條記錄,釋放鎖時(shí)就刪除這條記錄。唯一索引可以保證該記錄只被插入一次,那么就可以用這個(gè)記錄是否存在來判斷是否存于鎖定狀態(tài)。
存在以下幾個(gè)問題:
- 鎖沒有失效時(shí)間,解鎖失敗的話其它進(jìn)程無法再獲得鎖。
- 只能是非阻塞鎖,插入失敗直接就報(bào)錯(cuò)了,無法重試。
- 不可重入,已經(jīng)獲得鎖的進(jìn)程也必須重新獲取鎖。
2. 基于 REDIS 的 SETNX 實(shí)現(xiàn)分布式鎖
setnx 只有在 key 不存在的情況下,才能 set 成功,可以通過 key 判斷是否處于鎖定狀態(tài)。
3. Redis 的 RedLock 算法
為了取到鎖,客戶端應(yīng)該執(zhí)行以下操作:
- 獲取當(dāng)前Unix時(shí)間,以毫秒為單位。
- 依次嘗試從N個(gè)實(shí)例,使用相同的key和具有唯一性的value(例如UUID)獲取鎖。當(dāng)向Redis請求獲取鎖時(shí),客戶端應(yīng)該設(shè)置一個(gè)網(wǎng)絡(luò)連接和響應(yīng)超時(shí)時(shí)間,這個(gè)超時(shí)時(shí)間應(yīng)該小于鎖的失效時(shí)間。如果服務(wù)器端沒有在規(guī)定時(shí)間內(nèi)響應(yīng),客戶端應(yīng)該盡快嘗試去另外一個(gè)Redis實(shí)例請求獲取鎖。
- 客戶端使用當(dāng)前時(shí)間減去開始獲取鎖時(shí)間(步驟1記錄的時(shí)間)就得到獲取鎖使用的時(shí)間。當(dāng)且僅當(dāng)從大多數(shù)(N/2+1)的Redis節(jié)點(diǎn)都取到鎖,并且使用的時(shí)間小于鎖失效時(shí)間時(shí),鎖才算獲取成功。
- 如果獲取鎖失敗,客戶端應(yīng)該在所有的Redis實(shí)例上進(jìn)行解鎖。
優(yōu)點(diǎn):使用 Redlock 算法,可以保證在掛掉最多 2 個(gè)節(jié)點(diǎn)的時(shí)候,分布式鎖服務(wù)仍然能工作,這相比之前的數(shù)據(jù)庫鎖和緩存鎖大大提高了可用性,由于 redis 的高效性能,分布式緩存鎖性能并不比數(shù)據(jù)庫鎖差。
缺點(diǎn):失效時(shí)間設(shè)置多長時(shí)間為好?如何設(shè)置的失效時(shí)間太短,方法沒等執(zhí)行完,鎖就自動(dòng)釋放了,那么就會產(chǎn)生并發(fā)問題。如果設(shè)置的時(shí)間太長,其他獲取鎖的線程就可能要平白的多等一段時(shí)間。
4. Zookeeper
利用 Zookeeper 的順序臨時(shí)節(jié)點(diǎn),來實(shí)現(xiàn)分布式鎖和等待隊(duì)列。
缺點(diǎn):所有取鎖失敗的進(jìn)程都監(jiān)聽父節(jié)點(diǎn),很容易發(fā)生羊群效應(yīng),即當(dāng)釋放鎖后所有等待進(jìn)程一起來創(chuàng)建節(jié)點(diǎn),并發(fā)量很大。
參考: