一文看懂分佈式系統之 CAP 理論

引言

CAP 理論,相信很多人都聽過,它是指:

一個分佈式系統最多隻能同時滿足一致性(Consistency)、可用性(Availability)和分區容錯性(Partition tolerance)這三項中的兩項。

爲什麼要理解 CAP 理論?我能說出很多理由來。如果是在職場上,也許最合適的理由是,當領導給出的任務不靠譜時,我們可以依據 CAP 去否決它。

比如,有這麼一個任務,給你定了三大目標:

  1. 既要提升系統的可用性

  2. 又要保證數據的實時可見

  3. 還有提升系統的容錯能力

看到 “既要、又要、還要”,是不是想到了阿里……

OK,如果你深刻理解了 CAP,你會發現完成這個任務是不可能的。但是,如果你不理解 CAP,然後又拍了胸脯保證完成任務,不好意思,職場是不需要眼淚和後悔的。

有點跑題了,書歸正傳。CAP 理論是分佈式設計中最基礎最重要的理論,不懂它,你可能連分析一套分佈式系統的核心設計理念都做不到。

關於 CAP 爲何你讀了那麼多文章都還是搞不明白呢?因爲 CAP 理論來自學術界,而解讀 CAP 理論的人嘗試用工程師的方式去闡述它,這本身就有了問題。

CAP 本身基於狀態,基於瞬態,是一個描述性的理論,它並不解決工程問題。但是,很多工程師卻總是嘗試爲 CAP 做過多解讀。比如,非要說 CAP 理論只能適合某某場景,非要說 CAP 理論裏的一致性是非常強的一致性,把其和事務的一致性混爲一談。

由於 CAP 是學術理論,並不是工程理論,它會捨棄很多現實世界的問題。比如網絡的時長,比如節點內部的處理速度不一致,比如節點間存儲方式和速度的不一致。它說的一致性就是客戶端是否能拿到最新數據,它說的可用性就是允許客戶端拿不到最新數據。而這些東西被工程師們的過多腦補,導致了文章和文章說法不一樣,解析不一樣,闡述背景不一樣。

在今天這篇文章中,我們只解釋和說明,不腦補,不過多從工程角度解讀,只說本質,只指核心,希望能真正說清楚、講明白 CAP 理論。望本文能達到這個目的。

接下來你看到文字,我前前後後寫了 10 天,已經是這篇文章的第三版了,前兩版寫了一半都被我推翻重寫了,因爲我自己看了不滿意。

一方面是對自己知識掌握程度不滿意,本以爲自己明白 CAP 了,直到寫的時候,發現有些還是拿不準。

另一方面是不滿意自己的寫的太晦澀、太教科書,能把知識講的通俗易懂,纔是我希望的。

給大家看看文章上輩子的模樣。

  1. CAP 的由來 ==========

要理解 CAP,首先我們要清楚,爲何會有人提出 CAP?提出 CAP 是爲了解決什麼問題?

時間回到 1985 年,彼時,後來證明了 CAP 理論的 Lynch 教授此時給當時的 IT 界來了一記驚雷:

她通過不可辯駁的證明告訴業界的工程師們,如果在一個不穩定(消息要麼亂序要麼丟了)的網絡環境裏(分佈式異步模型),想始終保持數據一致是不可能的。

這是個什麼概念呢?就是她打破了那些既想提供超高質量服務,又想提供超高性能服務的技術人員的幻想。

這本質是在告訴大家,在分佈式系統裏,需要妥協。

但是,如何妥協?分佈式系統裏到底應該怎麼權衡這種 trade-off?

我們可以想象一下,在 CAP 定理提出之前,沒有這些方向性的指引,在設計和實施分佈式系統時該有多麼混亂。一套分佈式系統是由多個模塊組成的,這些模塊本身可能由不同的開發人員去完成。然而,對於這些人,在公共層面,竟然沒有一個原則去指導他們該怎麼完成這套功能。

比如,我們在同步兩個節點的數據時,如果發生了錯誤,到底我們應該怎麼做呢?如果沒有統一的標準和方向,那很可能在一套分佈式系統中的不同模塊,會出現不同的處理情況。

假設一套系統,由 A、B 兩個模塊構成。

A 模塊的設計理念是:節點間出現了問題,它可能會選擇不斷的重試,一直等到節點通信恢復。

而 B 的設計理念是:節點間出現了問題,它斷開就是了,可能最多就記錄下狀態,等以後處理。

可是,當 A、B 之間出現了通信怎麼辦?那會出現 A 往 B 發請求,出問題會不斷重試。而 B 往 A 發請求,出問題則直接斷開的情況。

當然,在後面我們會說明,CAP 的理念在實際工程中,會允許這種不一致。可是,那種不一致是提前設計好和規劃好的,是根據實際數據的重要性和業務需求做的妥協,而不是這種混亂的妥協。

所以,IT 界的人們就一直在摸索,試圖找到一些綱領去指導分佈式系統的設計,這一找就找了 15 年。

2000 年時,Eric Brewer 教授在 PODC 會議上提出了 CAP 理論,但是由於沒有被證明過,所以,當時只能被稱爲 CAP 猜想。這個猜想引起了巨大的反響,因爲 CAP 很符合人們對設計綱領的預期。

在 2002 年後,經過 Seth Gilbert 和 Nancy Lynch 從理論上證明了 CAP 猜想後,CAP 理論正式成爲了分佈式系統理論的基石之一。

  1. CAP 到底是什麼 ============

CAP 定理表達了一個分佈式系統裏不可能同時滿足以下的三個特性:

2.1. C:數據一致性

什麼是數據一致性?咋一看真的很讓人糊塗,一致性是什麼?是指數據能一起變化,是能讓數據整齊劃一。

那麼問題又來了,數據何時會變化?數據怎麼才能被稱爲一起變化?我們現在來回答這些問題,當我們搞清楚了這些問題,那麼對數據一致性就會有了清晰的理解。

首先第一個問題,數據何時會一起變化?

答案是:僅且僅當包含數據的服務,收到數據更新請求的時候,數據纔會發生變化。而數據更新請求則僅包括數據的增、刪、改這三種請求,而這三種請求又被統稱爲寫請求。所以,數據只有在寫請求的時候纔會發生變化。

那我們來回答第二個問題,數據要怎麼樣才能被稱爲一起變化了?即誰來判斷數據是最終變化了?是服務器對寫請求的返回結果嗎?告訴寫請求成功,數據就一定發生一致性變化了?

NO,數據發生變化是否一致是需要經過讀請求來做檢驗的。那麼讀請求判斷的依據是什麼呢?

假設,我們的分佈式存儲系統有兩個節點,每個節點都包含了一部分需要被變化的數據。如果經過一次寫請求後,兩個節點都發生了數據變化。然後,讀請求把這些變化後的數據都讀取到了,我們就把這次數據修改稱爲數據發生了一致性變化。

但是,這還不是完整的一致性。因爲系統不可能永久的正常運行下去。

如果系統內部發生了問題從而導致系統的節點無法發生一致性變化會怎麼樣呢?當我們這樣做的時候,就意味着想看到最新數據的讀請求們,很可能會看到舊數據,或者說獲取到不同版本的數據。此時,爲了保證分佈式系統對外的數據一致性,於是選擇不返回任何數據。

這裏需要注意一下,CAP 定理是在說在某種狀態下的選擇,和實際工程的理論是有差別的。上面描述的一致性和 ACID 事務中的一致性是兩回事。事務中的一致性包含了實際工程對狀態的後續處理。但是 CAP 定理並不涉及到狀態的後續處理,對於這些問題,後續出現了 BASE 理論等工程結論去處理,目前,只需要明白 CAP 定理主要描述的是狀態。

2.2. A:可用性

奧維德曾經說過:“行動被人們遺忘,結果卻將永存”。

這句話說明了結果的重要性,而可用性在 CAP 裏就是對結果的要求。它要求系統內的節點們接收到了無論是寫請求還是讀請求,都要能處理並給迴響應結果。只是它有兩點必須滿足的條件:

條件 1:返回結果必須在合理的時間以內,這個合理的時間是根據業務來定的。業務說必須 100 毫秒內返回,合理的時間就是 100 毫秒,需要 1 秒內返回,那就是 1 秒,如果業務定的 100 毫秒,結果卻在 1 秒才返回,那麼這個系統就不滿足可用性。

條件 2:需要系統內能正常接收請求的所有節點都返回結果。這包含了兩重含義:

  1. 如果節點不能正常接收請求了,比如宕機了,系統崩潰了,而其他節點依然能正常接收請求,那麼,我們說系統依然是可用的,也就是說,部分宕機沒事兒,不影響可用性指標。

  2. 如果節點能正常接收請求,但是發現節點內部數據有問題,那麼也必須返回結果,哪怕返回的結果是有問題的。比如,系統有兩個節點,其中有一個節點數據是三天前的,另一個節點是兩分鐘前的,如果,一個讀請求跑到了包含了三天前數據的那個節點上,抱歉,這個節點不能拒絕,必須返回這個三天前的數據,即使它可能不太合理。

2.3. P:分區容忍性

分佈式的存儲系統會有很多的節點,這些節點都是通過網絡進行通信。而網絡是不可靠的,當節點和節點之間的通信出現了問題,此時,就稱當前的分佈式存儲系統出現了分區。但是,值得一提的是,分區並不一定是由網絡故障引起的,也可能是因爲機器故障。

比如,我們的分佈式存儲系統有 A、B 兩個節點。那麼,當 A、B 之間由於可能路由器、交換機等底層網絡設備出現了故障,A 和 B 通信出現了問題,但是 A、B 依然都在運行,都在對外提供服務。這時候,就說 A 和 B 發生了分區。

還有一種情況也會發生分區,當 A 出現了宕機,A 和 B 節點之間通信也是出現了問題,那麼我們也稱 A 和 B 發生了分區。

綜上,我們可以知道,只要在分佈式系統中,節點通信出現了問題,那麼就出現了分區。

那麼,分區容忍性是指什麼? 它是說,如果出現了分區問題,我們的分佈式存儲系統還需要繼續運行。不能因爲出現了分區問題,整個分佈式節點全部就熄火了,罷工了,不做事情了。

  1. CAP 怎麼選擇 ===========

我們上面已經知道了,在設計分佈式系統時,架構師們在 C、A、P 這三種特性裏,只能選擇兩種。

但是,這道 CAP 的選擇題,就像別人在問你 “小明的父親有三個孩子,老大叫大朗,老二叫二郎,請問老三叫什麼” 一樣。在以分佈式存系統爲限定條件的 CAP 世界裏,P 是早已經確定的答案,P 是必須的。

因爲,在分佈式系統內,P 是必然的發生的,不選 P,一旦發生分區錯誤,整個分佈式系統就完全無法使用了,這是不符合實際需要的。所以,對於分佈式系統,我們只能能考慮當發生分區錯誤時,如何選擇一致性和可用性。

而根據一致性和可用性的選擇不同,開源的分佈式系統往往又被分爲 CP 系統和 AP 系統。

當一套系統在發生分區故障後,客戶端的任何請求都被卡死或者超時,但是,系統的每個節點總是會返回一致的數據,則這套系統就是 CP 系統,經典的比如 Zookeeper。

如果一套系統發生分區故障後,客戶端依然可以訪問系統,但是獲取的數據有的是新的數據,有的還是老數據,那麼這套系統就是 AP 系統,經典的比如 Eureka。

說了這麼多,其實 CAP 定理本質很簡單,它就是一種分佈式系統設計的不同理念概括,包括它說的一致性,可用性和分區容錯性。這就類似一個大學的校訓,是極度概念化的東西。

所以,大白話來形容下 CAP 吧,CAP 就是告訴程序員們當分佈式系統出現內部問題了,你要做兩種選擇:

遷就外部服務就是我們不能因爲我們自己的問題讓外部服務的業務運行受到影響,所以要優先可用性。而讓外部服務遷就我們,就要優先一致性。

  1. 對 CAP 的常見誤解 ==============

誤解一:分佈式系統因爲 CAP 定理放棄了 C 或者 A 中的其中一個

很多人在沒有對 CAP 做深入瞭解的情況下,聽到很多人說分佈式系統必須在 CAP 三個特性裏選擇兩個,就覺得一套分佈式系統肯定要麼只有可用性要麼只有一致性,不存在完整的可用性和一致性功能。

這種理解是大有問題的。因爲,P 這種問題發生的概率非常低,所以:

當沒有出現分區問題的時候,系統就應該有完美的數據一致性和可用性。

你什麼時候見過一個系統,當內部沒有問題的時候,會經常讓外部請求卡一下的?要麼就冷不丁的提供陳舊的老數據?那還能叫系統嗎?

誤解二:C 和 A 之間的選擇是針對整個分佈式系統的,只能整體考慮 C 和 A 之間的選擇

這個理解也是不對的。當分區發生的時候,其實對一致性和可用性的抉擇是局部性的,而不是針對整個系統的。

可能是在一些子系統做一些抉擇,甚至很可能只需要對某個事件或者數據,做一致性和可用性的抉擇而已。

比如,當我們做一套支付系統的時候,會員的財務相關像賬戶餘額,賬務流水是必須強一致性的。這時候,你就要考慮選 C。但是,會員的名字,會員的支付設置就不必考慮強一致性,可以選擇可用性 A。

一套分佈式系統的運行,就像人生一樣,就是一次又一次的選擇。在不同階段,不同的時刻有不同的事件發生的時候,又怎麼可能會有完全一樣的選擇呢?

誤解三:CAP 的三個特性只有是和否兩種極端選擇,而不是一個範圍

這種二元性的理解更是極其誤導人。

CAP 理論的三種特性不是 Boolean 類型的,不是一致和不一致,可用和不可用,分區和沒分區的這類二選一的選項。而是這三種特性都是範圍類型。

拿可用性來說,就像我從銀行取錢。當我目的是派發壓歲錢的時候,我很可能就想全要新票子,但是,新票子很可能就還得多一個步驟,就是需要拿舊票子去換一些新票,此時,我可以多等會兒,能拿到新票子就好。而當我的目的就是做生活花銷的時候,票子是新是舊,我根本不那麼關心,快點拿到錢就行。這就是可用性的範圍需求之一,對時延性的要求。

再比如,分區容錯則由於探測機制的問題,可能還得各節點搞投票去協商分區是否存在,當某一臺機器出現了問題,可能不影響業務的話,就會被機器投票認爲分區不存在。然後一直等到多數機器出現了問題,纔會投票確認出現了分區問題。這就好像新冠疫情,還會分低、中、高風險區呢,不是一出現通信故障就都被邏輯認定爲分區問題。

  1. CAP 理論的一些疑問 ==============

疑問一:在遵從 CAP 定理的系統中是否適合任意的寫請求

首先,在 CAP 定理中,關於一致性會有多種說法,但是總的來說,都是在描述數據最新版本的可見性。而這些可見性往往代表的是讀請求返回的數據的可見性。

那麼問題來了,當我們要求讀數據的可見性的時候,對寫數據有什麼要求嗎?

比如,我們系統有三個節點,一個客戶端給這個系統發了一個寫請求,要求系統寫入一個值爲 20 的數據。那麼,如果要滿足 CAP 定理中的一致性,就需要在寫完 20 這個數據之後,當其他客戶端請求讀取這個值爲 20 的數據之後,無論請求被轉發到系統中任何節點都能返回這個值。

這就要求寫入這個值爲 20 的寫請求必須成功寫到三個節點上,此時,系統就滿足了寫一致性的。所以,我們可以說對於讀一致性的要求是同時約束了寫一致性的。

其次,在 CAP 定理中,可用性本身要求對讀、寫請求都要處理。如果我們以可用性作爲標準的時候,在發生分區錯誤時,由於我們對讀請求並沒有強行要求返回完全準確的數據,所以,可能在本次讀請求之前的最近一次寫請求可能是部分失敗的。

同樣的例子,我們的分佈式系統由三個節點組成,最近一次寫請求想把值爲 20 的數據寫到三個節點上。但是,由於發生了分區問題,有一個節點通信故障,寫請求寫不過去,因此只有兩個節點包含了值爲 20 的數據。

此時,寫請求會返回給客戶端一個結果,可能會告訴客戶端寫入成功了,也可能告訴客戶端寫入部分成功。

這時候,當後續的讀請求恰巧被髮送到有通信故障的那個節點,系統可能只能返回一個空的結果。但是,由於系統處理和返回了讀寫請求,所以,系統是滿足了 CAP 中的可用性的。

疑問二:數據分片和數據副本的分佈式系統是否都遵守 CAP 定理

我們知道,在一套大規模的分佈式系統裏,一定是既需要把海量數據做切分,存儲到不同的機器上,也需要對這些存儲了數據的機器做副本備份的。

那麼,如果,一個分佈式系統裏只有數據分片存儲或者只有數據副本存儲,他們都會遵守 CAP 定理嗎?

答案是當數據分片時,也是要遵守 CAP 定理,但是,是種非常特殊的遵守。

當在一套分佈式系統只有分片存儲的時候,CAP 理論會表現成什麼樣?

比如,我們有個分佈式系統,由三個節點 a、b、c 組成。其中節點 a 存放了 A 表的數據,b 存放了 B 表的數據,c 存放了 C 表的數據。

如果有一個業務,它的意圖是想往 A 表插入一條新數據,在 B 表刪除一條已有數據,在 C 表更新一條老數據,這個分佈式系統該怎麼處理這種業務?

技術上我們對這種一個意圖想做多件事的情況往往會包裝成一個事務。當我們包裝成一個事務以後,我們可能會通過先在 a 節點執行,然後去 b 節點執行,最後去 c 節點執行,等到都成功了,纔會返回成功。

但是,發生了分區以後怎麼辦?當在 a、b 節點都成功了,到 c 發現發生了通信故障?

此時,根據 CAP 定理,你有兩個選擇,要麼就直接返回一個部分成功的結果給客戶端,要麼直接卡死等客戶端超時或者返回失敗給客戶端。當返回部分成功的時候,這就是選擇了可用性(A),當卡死或者返回失敗給客戶端的時候,就是選擇了一致性(C)。

可是,我們將請求包裝成了事務,而事務是要求要麼都成功,要麼都失敗…… 爲了遵守這種要求,對於分佈式只有分片的情況,迫於客觀條件,只能選擇 C。所以分片的分佈式系統,往往都是 CP 的系統。

可選擇,但是無法選擇是分佈式系統只有分片數據存儲的情況時,遵守 CAP 定理的特殊表現。

而當分佈式系統是多個節點,每個節點存儲了完整的一套數據,別的節點只是完整數據的備份的時候,即使事務只在一臺機器上成功,當發生分區故障的時候,我們也是可以有充分的餘地選擇是單機事務的回退 or 就此認爲寫成功的

單機事務的回退,就可以對外表現爲選擇了一致性。

就此認爲寫成功,則可以認爲選擇了可用性。

疑問三:爲何有時候區分一個系統是 AP 還是 CP 是如此之難

因爲,就像我們前面講過的,由於 AP 或者 CP 的選擇,可能僅侷限爲整套系統的局部,甚至某些特殊的數據上,而我們又是用這種局部的特性去描述了整套系統,所以就導致了區分的困難。而這本身其實也日漸成爲了 CAP 的一個大問題,從而被人詬病。

  1. CAP 的不足 ==========

  2. CAP 定理本身是沒有考慮網絡延遲的問題的,它認爲一致性是立即生效的,但是,要保持一致性,是需要時間成本的,這就導致往往分佈式系統多選擇 AP 方式

  3. 由於時代的演變,CAP 定理在針對所有分佈式系統的時候,出現了一些力不從心的情況,導致很多時候它自己會把以前很嚴謹的數學定義改成了比較鬆弛的業務定義,類似於我們看到,CAP 定理把一致性、可用性、分區容錯都變成了一個範圍屬性,而這和 CAP 定理本身這種數學定理般的稱呼是有衝突的,出現了不符合數學嚴謹定義的問題。

  4. 在實踐中以及後來 CAP 定理的提出者也承認,一致性和可用性並不僅僅是二選一的問題,只是一些重要性的區別,當強調一致性的時候,並不表示可用性是完全不可用的狀態。比如,Zookeeper 只是在 master 出現問題的時候,纔可能出現幾十秒的不可用狀態,而別的時候,都會以各種方式保證系統的可用性。而強調可用性的時候,也往往會採用一些技術手段,去保證數據最終是一致的。CAP 定理並沒有給出這些情況的具體描述。

  5. CAP 理論從工程角度來看只是一種狀態的描述,它告訴大家當有錯的時候,分佈式系統可能處在什麼狀態。但是,狀態是可能變化的。狀態間如何轉換,如何修補,如何恢復是沒有提供方向的。

  6. 引申出來的 BASE =============

正因爲 CAP 以上的種種不足,epay 的架構師 Dan Pritchett 根據他自身在大規模分佈式系統的實踐經驗,總結出了 BASE 理論。BASE 理論是對 CAP 理論的延伸,核心思想是即使無法做到強一致性(Strong Consistency),但應用可以採用適合的方式達到最終一致性(Eventual Consitency)。

BASE 理論是實踐工程的理論,它彌補了 CAP 理論過於抽象的問題,也同時解決了 AP 系統的總體工程實踐思想,是分佈式系統的核心理論之一,我們將在下一篇文章裏,詳細的講解此套理論。

  1. 大廠面試題 ========

在文章最後,來幾道大廠關於 CAP 的面試真題,檢驗一下你的學習效果,hiahiahia


本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/qJtSm63sFuh5tINI6rmYOw