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