看完這篇,保證讓你真正明白:分散式系統的CAP理論、CAP如何三選二

四猿外發表於2020-12-30

引言

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 理論正式成為了分散式系統理論的基石之一。

2. 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 發生了分割槽。

綜上,我們可以知道,只要在分散式系統中,節點通訊出現了問題,那麼就出現了分割槽。
在這裡插入圖片描述
那麼,分割槽容忍性是指什麼? 它是說,如果出現了分割槽問題,我們的分散式儲存系統還需要繼續執行。不能因為出現了分割槽問題,整個分散式節點全部就熄火了,罷工了,不做事情了。
在這裡插入圖片描述

3. CAP 怎麼選擇

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

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

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

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

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

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

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

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

  • 要麼遷就外部服務,像外包公司。
  • 要麼讓外部服務遷就你,像銀行。

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

4. 對 CAP 的常見誤解

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

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

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

當沒有出現分割槽問題的時候,系統就應該有完美的資料一致性和可用性。

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

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

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

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

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

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

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

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

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

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

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

5. 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 的一個大問題,從而被人詬病。

6. CAP 的不足

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

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

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

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

7. 引申出來的 BASE

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

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

8. 大廠面試題

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

    • 什麼是 CAP 理論?

    • CAP 中的 P 是什麼意思?

    • 為什麼說分散式系統,只能在 C、A 中二選一?

    • 結合實際應用,CP、AP 該怎麼選擇?


 

我準備了一些純手打的高質量PDF:

深入淺出Java多執行緒、HTTP超全彙總、Java基礎核心總結、程式設計師必知的硬核知識大全、簡歷面試談薪的超全乾貨。

別看數量不多,但篇篇都是乾貨,看完的都說很肝。

領取方式:掃碼關注後,在公眾號後臺回覆:666

相關文章