引言

2000年7月,加州大學伯克利分校的Eric Brewer教授在ACM PODC會議上提出CAP猜想。2年後,麻省理工學院的Seth Gilbert和Nancy Lynch從理論上證明了CAP。之後,CAP理論正式成為分散式計算領域的公認定理。

概述

CAP 理論對分散式系統的特性做了高度抽象,形成了三個指標:

  • 一致性(Consistency)
  • 可用性(Availability)
  • 分割槽容錯性(Partition Tolerance)

CAP定理我們常見的描述是一個分散式系統最多隻能同時滿足一致性(Consistency)、可用性(Availability)和分割槽容錯性(Partition tolerance)這三項中的兩項

但布魯爾在提出 CAP 猜想的時候,並沒有詳細定義 Consistency、Availability、Partition Tolerance 三個單詞的明確定義。

因此如果初學者去查詢 CAP 定義的時候會感到比較困惑,因為不同的資料對 CAP 的詳細定義有一些細微的差別。

這裡不展開講述這些細微差別,本文選取Robert Greiner的文章來作為參考。

CAP Theorem: Explained

CAP Theorem: Revisited

需要注意CAP Theorem: Explained這篇文章已經被標明outdated(已過時)。網上說的大多數定義是根據第一篇文章來的。

下面帶你逐步理解CAP定理中分散式一致性可用性分割槽容錯性這4個關鍵詞。

分散式

我們都知道CAP定理說的是分散式系統下的定理,但是分散式系統有很多型別,有異構的,比如節點之間是上下游依賴的關係,有同構的,比如分割槽/分片型的、副本型的(主從、多主)。

那麼CAP說的分散式包含以上所有嗎,網上很少有提到CAP說的分散式系統是否有具體的型別。

CAP Theorem: Explained文中描述CAP定理用的是Distributed systems,也沒有具體指明CAP理論下分散式系統的特徵,但是在CAP Theorem: Revisited是這樣描述CAP的:

in a distributed system (a collection of interconnected nodes that share data), you can only have two out of the following three guarantees across a write/read pair: Consistency, Availability, and Partition Tolerance - one of them must be sacrificed.

翻譯過來就是:在一個分散式系統(指互相連線並共享資料的節點的集合)中,當涉及讀寫操作時,只能保證一致性(Consistence)、可用性(Availability)、分割槽容錯性(Partition Tolerance)三者中的兩個,另外一個必須被犧牲。

我們可以看到有兩個很明顯的差別:

  • a collection of interconnected nodes that share data: 第二篇文章中提到了CAP關心的是互聯且共享資料的分散式系統,所以最簡單的例如 Memcache 的叢集,節點之間就沒有連線和共享資料,因此 Memcache 叢集這類分散式系統就不符合 CAP 理論探討的物件;而 MySQL 叢集就是互聯和進行資料複製的,因此是 CAP 理論探討的物件。
  • write/read pair: CAP 關注的是對資料的讀寫操作,而不是分散式系統的所有功能。例如,ZooKeeper 的選舉機制就不是 CAP 探討的物件。

所以CAP定理的分散式系統說的是副本型的分散式系統。

一致性

一致性這個詞在不同的環境下有著不同的含義,被極大的濫用了,導致很難理解:

  1. 多副本的一致性
  2. 一致性hash
  3. CAP理論的一致性
  4. ACID裡的一致性

而這幾個一致性的含義不同。我們接觸一致性最多的還是ACID中的一致性。但這和CAP中的一致性完全不是一個東西,我先告訴你ACID的一致性指的是約束一致性,而CAP中的一致性指的是資料正確。我們先來討論CAP中的一致,最後再向你解釋ACID中的一致性。

在 Robert Greiner 的 CAP Theorem: Explained 文中是這樣描述一致性的:

[C] Consistency - All nodes see the same data at the same time.

翻譯下就是所有節點同時看到相同的資料。

一想不對啊,多節點之前網路傳輸肯定有延遲啊,這裡又要提到個 CAP 的知識點,就是 CAP 是忽略網路延遲的。布魯爾在定義一致性時,並沒有將延遲考慮進去。也就是說,當事務提交時,資料能夠瞬間複製到所有節點。

為了幫你理解一致性,我給你舉一個具體的例子。比如,2 個節點的 KV 儲存,原始的 KV 記錄為“X = 1”。

緊接著,客戶端向節點 1 傳送寫請求“SET X = 2”。

如果節點 1 收到寫請求後,只將節點 1 的 X 值更新為 2,然後返回成功給客戶端。

那麼,此時如果客戶端訪問節點 2 執行讀操作,就無法讀到最新寫入的 X 值,這就不滿足一致性了。

如果節點 1 收到寫請求後,通過節點間的通訊,同時將節點 1 和節點 2 的 X 值都更新為 2,然後返回成功給客戶端。

那麼在完成寫請求後,不管客戶端訪問哪個節點,讀取到的都是同一份最新寫入的資料,這就叫一致性。

一致性這個指標,描述的是分散式系統非常重要的一個特性,強調的是資料正確。也就是說,對客戶端而言,每次讀都能讀取到最新寫入的資料。注意,這裡說的所有節點同時看到相同的資料,後者要求所有節點資料一致,前者要求客戶端看到的一致。而這也是Robert Greiner 在 CAP Theorem: Revisited中提到的一致性:

Consistency - A read is guaranteed to return the most recent write for a given client.

翻譯下就是對某個指定的客戶端來說,讀操作保證能夠返回最新的寫操作結果。

第一版解釋和第二版解釋的主要差異點表現在:

  • 第一版從節點 node 的角度描述,第二版從客戶端 client 的角度描述。

相比來說,第二版更加符合我們觀察和評估系統的方式,即站在客戶端的角度來觀察系統的行為和特徵。

  • 第一版的關鍵詞是 see,第二版的關鍵詞是 read。

第一版解釋中的 see,其實並不確切,因為節點 node 是擁有資料,而不是看到資料,即使要描述也是用 have;

  • 第二版從客戶端 client 的讀寫角度來描述一致性,定義更加精確。

第一版強調同一時刻擁有相同資料(same time + same data),第二版並沒有強調這點。這就意味著實際上對於節點來說,可能同一時刻擁有不同資料(same time + different data),這和我們通常理解的一致性是有差異的,為何做這樣的改動呢?其實在第一版的詳細解釋中已經提到了,具體內容如下:

A system has consistency if a transaction starts with the system in a consistent state, and ends with the system in a consistent state. In this model, a system can (and does) shift into an inconsistent state during a transaction, but the entire transaction gets rolled back if there is an error during any stage in the process.

翻譯下:

如果事務以系統處於一致狀態開始,並以系統處於一致狀態結束,則系統具有一致性。 在此模型中,系統可以(並且確實)在事務期間轉換為不一致狀態,但是如果在過程的任何階段出現錯誤,則整個事務都會回滾。

這個很好理解,在資料庫事務中我們常常遇到這種情況,事務未提交前是可以讀到與已提交資料不同的值的。對於系統執行事務來說,在事務執行過程中,系統其實處於一個不一致的狀態,不同的節點的資料並不完全一致,因此第一版的解釋“All nodes see the same data at the same time”是不嚴謹的。而第二版強調 client 讀操作能夠獲取最新的寫結果就沒有問題,因為事務在執行過程中,client 是無法讀取到未提交的資料的,只有等到事務提交後,client 才能讀取到事務寫入的資料,而如果事務失敗則會進行回滾,client 也不會讀取到事務中間寫入的資料。

ACID中的一致性

前文說過CAP的一致性與ACID的一致性不是一個東西,這裡補充講講

ACID 是資料庫管理系統為了保證事務的正確性而提出來的一個理論,ACID 包含四個約束。

  1. Atomicity(原子性)

一個事務中的所有操作,要麼全部完成,要麼全部不完成,不會在中間某個環節結束。事務在執行過程中發生錯誤,會被回滾到事務開始前的狀態,就像這個事務從來沒有執行過一樣。

  1. Consistency(一致性)

在事務開始之前和事務結束以後,資料庫的完整性沒有被破壞。

3. Isolation(隔離性)

資料庫允許多個併發事務同時對資料進行讀寫和修改的能力。隔離性可以防止多個事務併發執行時由於交叉執行而導致資料的不一致。事務隔離分為不同級別,包括讀未提交(Read uncommitted)、讀提交(read committed)、可重複讀(repeatable read)和序列化(Serializable)。

4. Durability(永續性)

事務處理結束後,對資料的修改就是永久的,即便系統故障也不會丟失。

可以看到,ACID 中的 A(Atomicity)和 CAP 中的 A(Availability)意義完全不同,而 ACID 中的 C 和 CAP 中的 C 名稱雖然都是一致性,但含義也完全不一樣。ACID 中的 C 是指資料庫的資料完整性,而 CAP 中的 C 是指分散式節點中的資料一致性。ACID 的應用場景是資料庫事務,CAP 關注的是分散式系統資料讀寫這個差異點。

可用性

直接來看第一版對可用性的定義

Every request gets a response on success/failure.

簡單翻譯為:每個請求都能得到成功或者失敗的響應。

這個描述太模糊了,比如我們在上文提到了不同節點資料是可能不一致的,比如兩個節點之間網路不通,那麼此時系統可以選擇直接返回錯誤資訊或者舊資料,那麼此時該請求這個算成功還是失敗呢。所以我們來看第二版對可用性的定義

A non-failing node will return a reasonable response within a reasonable amount of time (no error or timeout).

翻譯下:非故障節點將在合理的時間內(無錯誤或超時)返回合理的響應。

這個就比較清晰了,如果節點宕機了,不在討論範圍內,如果節點錯誤或超時了,那麼就是不可用,至於返回是新資料還是舊資料,只要能返回資料就可以,因為第二版沒有強調必須是正確的資料,如果返回的舊資料,肯定是不正確的結果,但可以是一個合理的結果。

我們在來看看上面說的故障是指什麼故障,一般情況下,我們討論的故障是指節點宕機或無法響應,而不是分割槽故障。

分割槽故障:以網路分割槽為例,網路分割槽是指因為網路故障導致網路不連通,不同節點分佈在不同的子網路中,各個子網路內網路正常。其實,你可以這麼理解,節點之間的網路通訊出現了訊息丟失、高延遲的問題。

所以說可用性說的是任何來自客戶端的請求,不管訪問哪個非故障節點,都能得到響應資料,但不保證是同一份最新資料。你也可以把可用性看作是分散式系統對訪問本系統的客戶端的另外一種承諾:我盡力給你返回資料,不會不響應你,但是我不保證每個節點給你的資料都是最新的。

舉個例子

假設下圖兩個節點都是正常工作的,並且可以互相通訊,但是這兩個節點資料同步是非同步的,所以資料不一致,但是如果客戶端請求來了兩個節點都返回資料,此時,有一個節點資料是舊的,這不是正確的結果,但是是合理的響應,所以這種情況是滿足可用性的

再舉個例子

來看下面的圖,當節點 1 和節點 2 通訊出問題的時候,資料無法同步,但是如果兩個節點都能響應客戶端,我們也說這是滿足可用性的。

分割槽容錯性

第一版解釋:

System continues to work despite message loss or partial failure.

簡單翻譯為:出現訊息丟失或者分割槽錯誤時系統能夠繼續執行。

第二版解釋:

The system will continue to function when network partitions occur.

簡單翻譯為:當出現網路分割槽後,系統能夠繼續“履行職責”。

第一版解釋和第二版解釋主要差異點表現在:

  • 第一版用的是 work,第二版用的是 function。

work 強調“執行”,只要系統不宕機,我們都可以說系統在 work,返回錯誤也是 work,拒絕服務也是 work;而 function 強調“發揮作用”“履行職責”,這點和可用性是一脈相承的。也就是說,只有返回 reasonable response 才是 function。相比之下,第二版解釋更加明確。

  • 第一版描述分割槽用的是 message loss or partial failure,第二版直接用 network partitions。

對比兩版解釋,第一版是直接說原因,即 message loss 造成了分割槽,但 message loss 的定義有點狹隘,因為通常我們說的 message loss(丟包),只是網路故障中的一種;第二版直接說現象,即發生了分割槽現象,不管是什麼原因,可能是丟包,也可能是連線中斷,還可能是擁塞,只要導致了網路分割槽,就通通算在裡面。

不過叢集畢竟不是單機,當發生分割槽故障的時候,有時不能僅僅因為節點間出現了通訊問題,無法響應最新寫入的資料,之後在客戶端查詢資料時,就一直返回給客戶端出錯資訊。

分割槽容錯性說的是,當節點間出現任意數量的訊息丟失或高延遲的時候,系統仍然在繼續工作。也就是說,分散式系統在告訴訪問本系統的客戶端:不管我的內部出現什麼樣的資料同步問題,我會一直執行。這個指標,強調的是叢集對分割槽故障的容錯能力。

其實就是上一張圖,假設他是發生了網路分割槽,節點與節點之間無法通訊,但節點與客戶端之間可以互相通訊。在此基礎上,如果這節點2返回了資料,而不是拒絕響應或者超時,通過可用性的分析我們知道下圖這個系統是滿足可用性的,並且是在分割槽故障的時候滿足可用性的,那麼我們可以說這個系統又滿足了分割槽容錯性,但是很明顯不滿足一致性,所以這個系統是AP模型。

講到這裡,你可能有點懵,上面說分割槽容錯性指的是在分割槽故障情況下系統依然工作,看上面的例子,這不必然帶著可用性了麼,那麼CAP的P,說的是發生了分割槽故障還是發生了分割槽故障後還能工作?我相信如果將CAP中的P描述為分割槽故障你更好理解,但實際上並不是,你可以把發生了分割槽故障後還能工作(分割槽容錯性)看成目標,C和A是手段。當發生分割槽故障的時候,要麼你保證可用性,但是資料肯定不一致;要麼你保證資料一致,但是可用性必然無法保證(因為資料無法同步到其他分割槽節點,其他分割槽節點不可用)。 這類似於ACID,資料庫事務中的ACID中的C(一致性)是目標,AID是手段。而CAP中P是目標,C和A是手段。

下面詳細講講

可用性與分割槽容錯性,傻傻分不清

一致性大家比較好理解,多個數據節點保持資料一致即可,實現方式上就是更新時所有資料節點都更新成功才返回更新成功。

主要是可用性和分割槽容錯性比較難以理解,下面詳細說說。

問題1:分割槽容錯性說分割槽故障正常工作,什麼叫正常工作?這個正常工作是指滿足可用性嗎?

可能有一部分人被我上面的圖(上一張圖)誤導了,覺得分割槽容錯性和可用性是繫結在一起的,其實並不是,來看下一張圖。相比上一張圖,這張圖的節點2與客戶端不通了,當然這不是網路不通,而是客戶端被選擇的結果,這個選擇就是如果節點2出問題了,沒有最新的資料,那麼就讓客戶端不訪問節點2,而是訪問節點1,這樣系統是不是就保證資料一致性了,但是很明顯不滿足可用性了,我節點2沒有宕機,沒有斷網,客戶端你可以訪問我但是你不訪問,你為了拿到最新的資料,保證資料一致性,捨棄了節點2。

此時,該系統是正常工作的嗎,是。該系統發生故障了嗎?發生了。該系統滿足可用性了嗎?沒有保證。我們可以回答上面的問題了,分割槽容錯性說的是發生分割槽故障的時候正常工作,這個正常工作,並不是可用性,當我們捨棄可用性,保證資料一致性的時候,資料也是正常工作。

注意,可用性並不是說系統能工作就叫可用性,而是某個分割槽中的節點和客戶端可以正常工作。

問題2:為什麼說分割槽容錯性是目標,而CA要捨棄一個,不能設計CA系統,捨棄P嗎
  1. 什麼叫分散式系統?多個節點,網路通訊
  2. 網路通訊是不可靠的,tcp也一樣,參考兩軍問題
  3. 分割槽容錯雖然不是時刻發生,但他意味著必然發生,因為網路是不可靠的

所以分割槽容錯性是目標,是分散式系統必須要保證的,不能因為部分節點網路問題導致整個系統不能工作。

因為分散式系統與單機系統不同,它涉及到多節點間的通訊和互動,只要有網路互動就一定會有延遲和資料丟失,而這種狀況我們必須接受,還必須保證系統不能掛掉。所以就像我上面提到的,節點間的分割槽故障是必然發生的。也就是說,分割槽容錯性(P)是前提,是必須要保證的。

假設一個分散式系統我要保證CA,當發生分割槽容錯的時候,為了保證資料一致性,多個節點資料無法同步,那麼我更新操作是等待所有節點更新成功才返回更新成功呢,如果這樣那麼這個更新操作永遠不能成功,再假設我不等待所有節點都更新成功就返回,我保證我讀的時候去拿最新的資料,如果我發起一個讀請求,發現他不是最新的,我要等待最新的資料同步過來,可是已經發生分割槽故障,我永遠等不到最新資料過來,最後只能超時報錯。所以說,當發生分割槽故障的時候,C和A無法同時保證。

問題3:C和A真的無法共存嗎

當然不是,前後強調了很多次了,只有當發生分割槽故障的時候,C和A無法同時保證。不發生分割槽故障的時候,節點與節點之間,節點與客戶端之間都能正常通訊,那麼這個系統此時肯定是既滿足一致性又滿足可用性。

至於我們說的CAP中的P,雖然說當發生分割槽故障的時候什麼什麼,但是由於網路分割槽是必然會發生的,同時分割槽容錯性也是必須保證的,所以我們設計系統時必須假定已經發生了分割槽故障。

當然,如果是單機系統,沒有網路分割槽的可能,那麼自然是滿足CA的,但這個系統也不叫分散式系統了,沒必要討論。

所以我們要知道,C和A無法共存指的是發生分割槽故障的時候無法共存,網路正常兩個都可以滿足的。

是CAP不能共存嗎,是的,準確的說,是分割槽故障導致了CA不能共存

總結

CAP的理論終於講完了。

這裡就不重複CAP的定義了,我覺得理解CAP的關鍵點有以下幾個:

  • CAP探討的分散式系統是副本型的分散式系統。比如Mysql叢集,有了實際例子就很好理解
  • 由於網路不可靠分割槽故障是必然會發生的,所以在分散式系統中P一定要保證
  • 系統可用(正常工作)和CAP說的可用性不一樣,前者站在客戶端的角度,後者站在分割槽節點的角度
  • 分散式系統中C和A可以共存,但無法一直共存,當發生分割槽故障時必須要犧牲一個,不發生的時候兩個都能保證

一點拙見,主要內容還是參考了大佬的文章,這裡添加了一些對於新手比較容易產生的問題並嘗試解答,希望對你有所幫助

參考

極客時間-從0開始學架構

-李運華

極客時間-分散式協議與演算法實戰

-韓健