分布式的CP/AP是個偽二分法?
大家好,歡迎來到Tlog4J課堂,我是Jensen。
分布式CAP定理大家也耳熟能詳了,CAP指的是分布式系統(tǒng)中的三個特性:
在我之前的文章也有提到過——CAP是分布式系統(tǒng)中三個維度的“客戶承諾”:
- 一致性(Consistency):要么我給你返回一個錯誤,要么我給你返回絕對一致的最新數(shù)據(jù),強調(diào)數(shù)據(jù)正確。
- 可用性(Availibility):我一定會給你返回數(shù)據(jù),不會給你返回錯誤,但不保證數(shù)據(jù)最新,強調(diào)的是服務不出錯。
- 分區(qū)容錯性(Partition Tolerance):我會一直運行,不管我的內(nèi)部出現(xiàn)何種數(shù)據(jù)同步問題,強調(diào)的是不掛掉。
在Jeff Hodges精彩的博文筆記中,他建議我們用 CAP 定理來評論分布式系統(tǒng),并且很多人都聽取了這個建議,描述他們的系統(tǒng)為“CP” (有一致性但在網(wǎng)絡分區(qū)的時候不可用),“AP”(可用但是在網(wǎng)絡分區(qū)的時候不一致) 或者有時候 “CA”。
那這次咱們再深入探討一下,不同的系統(tǒng)按CP/AP來劃分,到底合不合理。
回過頭來看一致性
首先我們要了解,數(shù)據(jù)的一致性問題存在于計算機軟硬件層面的任意一個數(shù)據(jù)拷貝的環(huán)節(jié),如CPU與內(nèi)存的數(shù)據(jù)拷貝、內(nèi)存間的數(shù)據(jù)拷貝、內(nèi)存與磁盤的數(shù)據(jù)拷貝、計算機之間的網(wǎng)絡通訊等等。
而分布式系統(tǒng)是基于軟件系統(tǒng)的,系統(tǒng)分布在不同的計算機必然會產(chǎn)生網(wǎng)絡通訊延遲或網(wǎng)絡分區(qū)的情況,以我們現(xiàn)在的計算機技術是無法100%解決一致性問題的。
CAP中的一致性是可線性化的意思,它是非常特殊、非常強的一致性,雖然說ACID中的C也是一致性,但和CAP的一致性沒有任何關系。
那什么是可線性化呢?
舉個例子:如果B操作在完成A操作成功之后,那么整個系統(tǒng)對B操作來說必須表現(xiàn)為A操作已經(jīng)完成了或者更新的狀態(tài)。
這張圖展示了 Alice 還有 Bob, 他們在同一個房間,都在用他們的手機查詢 2014 年世界杯的決賽結果。
就在最終結果剛發(fā)布之后,Alice 刷新了頁面,看到了宣布冠軍的消息,而且很興奮地告訴了 Bob。
Bob 馬上也重新加載了他手機上的頁面,但是他的請求被送到了一個數(shù)據(jù)庫的拷貝,還沒有拿到產(chǎn)生結果的數(shù)據(jù),結果他的手機上顯示決賽還正在進行。
如果 Alice 和 Bob 同時刷新,拿到了不一樣的結果,并不會太讓人意外,因為他們不知道具體服務器到底是先處理了他們中哪一個請求。
但是 Bob 知道他刷新頁面是在 Alice 告訴了他最終結果之后的,所以他預期他查詢的結果一定比 Alice 的更新,但事實是他卻拿到了舊的結果,這就違反了可線性化。
只有 Bob 通過另外一個溝通渠道從 Alice 那里知道了結果, Bob 才能知道他的請求一定在 Alice 之后。
如果 Bob 沒有從 Alice 那里聽到比賽已經(jīng)結束了,他就不會知道他看到的結果是舊的。
如果你在建一個數(shù)據(jù)庫,你不知道用戶們會有什么另外的溝通渠道,所以,如果你想提供可線性化訪問,你就需要讓你的數(shù)據(jù)庫看起來就好像只有一個拷貝,雖然實際上可能有多個備份在多個地方。
這是一個非常昂貴的屬性,因為它要求你做很多協(xié)調(diào)工作,甚至你電腦上的CPU都不提供本地內(nèi)存的可線性化訪問!
在現(xiàn)代的CPU上,你需要用Memory Barrier指令來達到可線性化訪問,甚至測試一個系統(tǒng)是不是可線性化的也是非常困難的。
所以說,脫離了關注點,討論一致性沒有多大意義。
回過頭來看可用性
可用性在CAP中是定義為“每一個請求如果被一個工作中的[數(shù)據(jù)庫]節(jié)點收到,那一定要返回[非錯誤的]結果”。
注意到,這里一部分節(jié)點可以處理這個請求是不充分的,任意一個工作中的節(jié)點都要可以處理這個請求,所以很多自稱高可用的系統(tǒng)通常并沒有滿足這里的可用性的定義,它們只是做了故障轉(zhuǎn)移或者是熔斷降級而已。
CAP中根本沒有提到延遲,而我們其實對延遲比可用性更關心,事實上,滿足CAP可用性的系統(tǒng)可以花任意長的時間來回復一個請求,而且同時保持可用性這個屬性。
但如果你的系統(tǒng)要花兩分鐘來加載一個頁面,你的用戶絕對不會認為它是“可用的”,這也是為什么現(xiàn)在互聯(lián)網(wǎng)項目大多只允許2~10秒的請求延遲。
CP和AP的取舍
CAP定理只考慮了網(wǎng)絡分區(qū)這一種故障情況(比如所有節(jié)點還在運行,但是他們之間的網(wǎng)絡已經(jīng)不工作了),這種故障絕對會發(fā)生,但是這不是唯一會出故障的地方。
節(jié)點可以整個崩潰或者重啟,你可能沒有足夠的磁盤空間,你可能會遇到一個軟件故障(bug),等等,在建分布式系統(tǒng)的時候,你需要考慮到更多得多的問題,如果太關注CAP就容易導致忽略了其他重要的問題。
那為什么在網(wǎng)絡分區(qū)的情況下,我們要放棄可用性和一致性中的一個呢?
舉個例子:你的數(shù)據(jù)庫有兩個拷貝在兩個不同的數(shù)據(jù)中心,具體怎么做備份并不重要,可以是Single-Master,或者多個Leader,或者基于Quorum的備份,要求是當數(shù)據(jù)被寫到一個數(shù)據(jù)中心的時候,它也一定要被寫到另一個數(shù)據(jù)中心。
假設Client只連接到其中一個數(shù)據(jù)中心,而且連接兩個數(shù)據(jù)中心的網(wǎng)絡故障了,網(wǎng)絡中斷了就是我們所說的網(wǎng)絡分區(qū)的意思,接下來會怎樣呢?
我們有兩個選擇:
- 應用還是被允許寫到數(shù)據(jù)庫,兩邊的數(shù)據(jù)庫還是完全可用的。但是一旦兩個數(shù)據(jù)庫之間的網(wǎng)絡中斷了,任何一個數(shù)據(jù)中心的寫操作就不會同步到另一個數(shù)據(jù)中心。這違反了可線性化(用之前的例子,Alice 可能鏈接到了一號數(shù)據(jù)中心,而 Bob 連接到了二號數(shù)據(jù)中心)。
- 如果你不想失去可線性化,就必須保證你的讀寫操作都在同一個數(shù)據(jù)中心,你可能叫它 Leader,另一個數(shù)據(jù)中心,因為網(wǎng)絡故障不能被更新,就必須停止接收讀寫操作,直到網(wǎng)絡恢復,兩邊數(shù)據(jù)庫又進行同步。所以雖然非Leader數(shù)據(jù)庫正常運行,但是他卻不能處理請求,這就違反了 CAP 的可用性定義。
這個其實就是 CAP 定理的證明,咱們這里的例子用到了兩個數(shù)據(jù)中心,但對于一個數(shù)據(jù)中心內(nèi)的網(wǎng)絡故障也同樣適用,之所以這里用兩個數(shù)據(jù)中心是因為更容易考慮這個問題。
當一個系統(tǒng)選擇了可線性化,也就是說不是 CAP 可用的,并不意味著網(wǎng)絡分區(qū)一定會造成應用停運。
如果你可以把用戶的流量轉(zhuǎn)移到Leader數(shù)據(jù)庫,那么用戶根本就不會注意到任何問題。
實際應用中的可用性和CAP可用性并不相同,咱們應用的可用性多數(shù)是通過SLA來衡量的,比如99.9%正確的請求一定要在一秒鐘之內(nèi)返回成功,這實際上是一種整體的衡量。
但是一個系統(tǒng)無論是否滿足CAP可用性其實都可以滿足這樣的SLA,在實際操作中,跨多個數(shù)據(jù)中心的系統(tǒng)經(jīng)常是通過異步備份的,所以不是可線性化的。
但是做出這個選擇的原因經(jīng)常是因為遠距離網(wǎng)絡的延遲,而不是僅僅為了處理數(shù)據(jù)中心的網(wǎng)絡故障。
寫在最后
CAP定理其實是一個被簡化了的理論,以致于被大眾廣泛地誤解了,實際上CAP是一個非常精確的定義。
其實大部分軟件都不在CP/AP這兩類中,但人們還是強行把軟件分為這兩類,這導致為了適用,不可避免地改變對“一致性”或者“可用性”的定義,如果用詞的定義改變了,CAP定理自己也不適用了,那CP/AP劃分也就完全沒有意義了。
所以,在技術面試的時候,我再也不會問“哪些框架是CP的,哪些框架是AP的”這個問題了,這么問其實沒多大意義。
從另一方面看,CAP也是被廣泛接受的分布式基礎理論,很多框架也可以通過不同的配置實現(xiàn)廣義上的CP/AP,與其花時間跟別人解釋一堆按CP/AP來劃分系統(tǒng)是怎么不合理的,不如坦然接受現(xiàn)狀,但是別忘了,心中也要有自己的答案。
獨立思考,保持好奇心和耐心。