系統(tǒng)分布式情況下最終一致性方案梳理
前言
目前的應(yīng)用系統(tǒng),不管是企業(yè)級(jí)應(yīng)用還是互聯(lián)網(wǎng)應(yīng)用,最終數(shù)據(jù)的一致性是每個(gè)應(yīng)用系統(tǒng)都要面臨的問(wèn)題,隨著分布式的逐漸普及,數(shù)據(jù)一致性更加艱難,但是也很難有銀彈的解決方案,也并不是引入特定的中間件或者特定的開源框架能夠解決的,更多的還是看業(yè)務(wù)場(chǎng)景,根據(jù)場(chǎng)景來(lái)給出解決方案。根據(jù)筆者最近幾年的了解,總結(jié)了幾個(gè)點(diǎn),更多的應(yīng)用系統(tǒng)在編碼的時(shí)候,更加關(guān)注數(shù)據(jù)的一致性,這樣系統(tǒng)才是健壯的。
基礎(chǔ)理論相關(guān)
說(shuō)起事務(wù),目前的幾個(gè)理論,ACID事務(wù)特性,CAP分布式理論,以及BASE等,ACID在數(shù)據(jù)庫(kù)事務(wù)中體現(xiàn),CAP和BASE則是分布式事務(wù)的理論,結(jié)合業(yè)務(wù)系統(tǒng),例如訂單管理,例如倉(cāng)儲(chǔ)管理等,可以借鑒這些理論,從而解決問(wèn)題。
ACID 特性
- A(原子性)事務(wù)的原子操作單元,對(duì)數(shù)據(jù)的修改,要么全部執(zhí)行,要么全部不執(zhí)行;
- C(一致性)在事務(wù)開始和完成時(shí),數(shù)據(jù)必須保持一致狀態(tài),相關(guān)的數(shù)據(jù)規(guī)則必須應(yīng)用于事務(wù)的修改,以保證數(shù)據(jù)的完整性,事務(wù)結(jié)束時(shí),所有的內(nèi)部數(shù)據(jù)結(jié)構(gòu)必須正確;
- I(隔離性)保證事務(wù)不受外部并發(fā)操作的獨(dú)立環(huán)境執(zhí)行;
- D(持久性)事務(wù)完成之后,對(duì)于數(shù)據(jù)的修改是永久的,即使系統(tǒng)出現(xiàn)故障也能夠保持;
CAP
- C(一致性)一致性是指數(shù)據(jù)的原子性,在經(jīng)典的數(shù)據(jù)庫(kù)中通過(guò)事務(wù)來(lái)保障,事務(wù)完成時(shí),無(wú)論成功或回滾,數(shù)據(jù)都會(huì)處于一致的狀態(tài),在分布式環(huán)境下,一致性是指多個(gè)節(jié)點(diǎn)數(shù)據(jù)是否一致;
- A(可用性)服務(wù)一直保持可用的狀態(tài),當(dāng)用戶發(fā)出一個(gè)請(qǐng)求,服務(wù)能在一定的時(shí)間內(nèi)返回結(jié)果;
- P(分區(qū)容忍性)在分布式應(yīng)用中,可能因?yàn)橐恍┓植际降脑驅(qū)е孪到y(tǒng)無(wú)法運(yùn)轉(zhuǎn),好的分區(qū)容忍性,使應(yīng)用雖然是一個(gè)分布式系統(tǒng),但是好像一個(gè)可以正常運(yùn)轉(zhuǎn)的整體
BASE
- BA: Basic Availability 基本業(yè)務(wù)可用性;
- S: Soft state 柔性狀態(tài);
- E: Eventual consistency 最終一致性;
最終一致性的幾種做法
單數(shù)據(jù)庫(kù)情況下的事務(wù)
如果應(yīng)用系統(tǒng)是單一的數(shù)據(jù)庫(kù),那么這個(gè)很好保證,利用數(shù)據(jù)庫(kù)的事務(wù)特性來(lái)滿足事務(wù)的一致性,這時(shí)候的一致性是強(qiáng)一致性的。對(duì)于java應(yīng)用系統(tǒng)來(lái)講,很少直接通過(guò)事務(wù)的start和commit以及rollback來(lái)硬編碼,大多通過(guò)spring的事務(wù)模板或者聲明式事務(wù)來(lái)保證。
基于事務(wù)型消息隊(duì)列的最終一致性
借助消息隊(duì)列,在處理業(yè)務(wù)邏輯的地方,發(fā)送消息,業(yè)務(wù)邏輯處理成功后,提交消息,確保消息是發(fā)送成功的,之后消息隊(duì)列投遞來(lái)進(jìn)行處理,如果成功,則結(jié)束,如果沒有成功,則重試,直到成功,不過(guò)僅僅適用業(yè)務(wù)邏輯中,第一階段成功,第二階段必須成功的場(chǎng)景。對(duì)應(yīng)上圖中的C流程。
基于消息隊(duì)列+定時(shí)補(bǔ)償機(jī)制的最終一致性
前面部分和上面基于事務(wù)型消息的隊(duì)列,不同的是,第二階段重試的地方,不再是消息中間件自身的重試邏輯了,而是單獨(dú)的補(bǔ)償任務(wù)機(jī)制。其實(shí)在大多數(shù)的邏輯中,第二階段失敗的概率比較小,所以單獨(dú)獨(dú)立補(bǔ)償任務(wù)表出來(lái),可以更加清晰,能夠比較明確的直到當(dāng)前多少任務(wù)是失敗的。對(duì)應(yīng)上圖的E流程。
業(yè)務(wù)系統(tǒng)業(yè)務(wù)邏輯的commit/rollback機(jī)制
這一點(diǎn)說(shuō)的話確實(shí)不難,commit和rollback是數(shù)據(jù)庫(kù)事務(wù)中的比較典型的概念,但是在系統(tǒng)分布式情況下,需要業(yè)務(wù)代碼中實(shí)現(xiàn)這種,成功了commit,失敗了rollback。
業(yè)務(wù)應(yīng)用系統(tǒng)的冪等性控制
為啥要做冪等呢? 原因很簡(jiǎn)單,在系統(tǒng)調(diào)用沒有達(dá)到期望的結(jié)果后,會(huì)重試。那重試就會(huì)面臨問(wèn)題,重試之后不能給業(yè)務(wù)邏輯帶來(lái)影響,例如創(chuàng)建訂單,第一次調(diào)用超時(shí)了,但是調(diào)用的系統(tǒng)不知道超時(shí)了是成功了還是失敗了,然后他就重試,但是實(shí)際上第一次調(diào)用訂單創(chuàng)建是成功了的,這時(shí)候重試了,顯然不能再創(chuàng)建訂單了。
- 查詢
查詢的API,可以說(shuō)是天然的冪等性,因?yàn)槟悴樵円淮魏筒樵儍纱?,?duì)于系統(tǒng)來(lái)講,沒有任何數(shù)據(jù)的變更,所以,查詢一次和查詢多次一樣的。
- MVCC方案
多版本并發(fā)控制,update with condition,更新帶條件,這也是在系統(tǒng)設(shè)計(jì)的時(shí)候,合理的選擇樂觀鎖,通過(guò)version或者其他條件,來(lái)做樂觀鎖,這樣保證更新及時(shí)在并發(fā)的情況下,也不會(huì)有太大的問(wèn)題。例如update tablexxx set name=#name#,version=version+1 where version=#version# ,或者是 update tablexxx set quality=quality-#subQuality# where quality-#subQuality# >= 0 。
- 單獨(dú)的去重表
如果涉及到的去重的地方特別多,例如ERP系統(tǒng)中有各種各樣的業(yè)務(wù)單據(jù),每一種業(yè)務(wù)單據(jù)都需要去重,這時(shí)候,可以單獨(dú)搞一張去重表,在插入數(shù)據(jù)的時(shí)候,插入去重表,利用數(shù)據(jù)庫(kù)的唯一索引特性,保證唯一的邏輯。
- 分布式鎖
還是拿插入數(shù)據(jù)的例子,如果是分布是系統(tǒng),構(gòu)建唯一索引比較困難,例如唯一性的字段沒法確定,這時(shí)候可以引入分布式鎖,通過(guò)第三方的系統(tǒng),在業(yè)務(wù)系統(tǒng)插入數(shù)據(jù)或者更新數(shù)據(jù),獲取分布式鎖,然后做操作,之后釋放鎖,這樣其實(shí)是把多線程并發(fā)的鎖的思路,引入多多個(gè)系統(tǒng),也就是分布式系統(tǒng)中得解決思路。
- 刪除數(shù)據(jù)
刪除數(shù)據(jù),僅僅第一次刪除是真正的操作數(shù)據(jù),第二次甚至第三次刪除,直接返回成功,這樣保證了冪等。
- 插入數(shù)據(jù)的唯一索引
插入數(shù)據(jù)的唯一性,可以通過(guò)業(yè)務(wù)主鍵來(lái)進(jìn)行約束,例如一個(gè)特定的業(yè)務(wù)場(chǎng)景,三個(gè)字段肯定確定唯一性,那么,可以在數(shù)據(jù)庫(kù)表添加唯一索引來(lái)進(jìn)行標(biāo)示。
- API層面的冪等
這里有一個(gè)場(chǎng)景,API層面的冪等,例如提交數(shù)據(jù),如何控制重復(fù)提交,這里可以在提交數(shù)據(jù)的form表單或者客戶端軟件,增加一個(gè)唯一標(biāo)示,然后服務(wù)端,根據(jù)這個(gè)UUID來(lái)進(jìn)行去重,這樣就能比較好的做到API層面的唯一標(biāo)示。
- 狀態(tài)機(jī)冪等
在設(shè)計(jì)單據(jù)相關(guān)的業(yè)務(wù),或者是任務(wù)相關(guān)的業(yè)務(wù),肯定會(huì)涉及到狀態(tài)機(jī),就是業(yè)務(wù)單據(jù)上面有個(gè)狀態(tài),狀態(tài)在不同的情況下會(huì)發(fā)生變更,一般情況下存在有限狀態(tài)機(jī),這時(shí)候,如果狀態(tài)機(jī)已經(jīng)處于下一個(gè)狀態(tài),這時(shí)候來(lái)了一個(gè)上一個(gè)狀態(tài)的變更,理論上是不能夠變更的,這樣的話,保證了有限狀態(tài)機(jī)的冪等。
異步回調(diào)機(jī)制的引入
A應(yīng)用調(diào)用B,在同步調(diào)用的返回結(jié)果中,B返回成功給到A,一般情況下,這時(shí)候就結(jié)束了,其實(shí)在99.99%的情況是沒問(wèn)題的,但是有時(shí)候?yàn)榱舜_保100%,記住最起碼在系統(tǒng)設(shè)計(jì)中100%,這時(shí)候B系統(tǒng)再回調(diào)A一下,告訴A,你調(diào)用我的邏輯,確實(shí)成功了。其實(shí)這個(gè)邏輯,非常類似TCP協(xié)議中的三次握手。上圖中的B流程。
類似double check機(jī)制的確認(rèn)機(jī)制
還是上圖中異步回調(diào)的過(guò)程,A在同步調(diào)用B,B返回成功了。這次調(diào)用結(jié)束了,但是A為了確保,在過(guò)一段時(shí)間,這個(gè)時(shí)間可以是幾秒,也可以是每天定時(shí)處理,再調(diào)用B一次,查詢一下之前的那次調(diào)用是否成功。例如A調(diào)用B更新訂單狀態(tài),這時(shí)候成功了,延遲幾秒后,A查詢B,確認(rèn)一下狀態(tài)是否是自己剛剛期望的。上圖中的D流程。
總結(jié)
上面的幾點(diǎn)總結(jié),更多的在業(yè)務(wù)系統(tǒng)中體現(xiàn),在超復(fù)雜的系統(tǒng)中,數(shù)據(jù)的一致性,不是說(shuō)簡(jiǎn)單的引入啥中間件能夠解決的,更多的是根據(jù)業(yè)務(wù)場(chǎng)景,來(lái)靈活應(yīng)對(duì)。