2000年,eric brewer 教授在 acm 分布式計算年會上指出了著名的 cap 理論:
分布式系統不可能同時滿足一緻性(c: consistency),可用性(a: availability)和分區容錯性(p: tolerance of network partition)這三個需求。大約兩年後,seth gilbert 和 nancy lynch 兩人證明了cap理論的正确性。
三者的含義如下:
<code>consistency</code>:一緻性,一個服務是一緻的完整操作或完全不操作(a service that is consistent operates fully or not at all,精确起見列出原文),也有人将其簡稱為資料一緻性,任何一個讀操作總是能讀取到之前完成的寫操作結果,也就是在分布式環境中,多點的資料是一緻的。
<code>availability</code>:可用性,每一個操作總是能夠在确定的時間内傳回,也就是系統随時都是可用的。
<code>tolerance of network partition</code>:分區容忍性,節點 crash 或者網絡分片都不應該導緻一個分布式系統停止服務。
cap 的證明基于 <code>異步網絡</code>,異步網絡也是反映了真實網絡中情況的模型。
真實的網絡系統中,節點之間不可能保持同步,即便是時鐘也不可能保持同步,所有的節點依靠獲得的消息來進行本地計算和通訊。這個概念其實是相當強的,意味着任何逾時判斷也是不可能的,因為沒有共同的時間标準。之後我們會擴充 cap 的證明到弱一點的異步網絡中,這個網絡中時鐘不完全一緻,但是時鐘運作的步調是一緻的,這種系統是允許節點做逾時判斷的。
cap 的證明很簡單,假設兩個節點集{g1, g2},由于網絡分片導緻 g1 和 g2 之間所有的通訊都斷開了,如果在 g1 中寫,在 g2 中讀剛寫的資料, g2 中傳回的值不可能是 g1 中的寫值。由于 <code>a</code> 的要求,g2 一定要傳回這次讀請求,由于 <code>p</code> 的存在,導緻 <code>c</code>一定是不可滿足的。
為什麼不能完全保證這個三點了,個人覺得主要是因為一旦進行分區了,就說明了必須節點之間必須進行通信,涉及到通信,就無法確定在有限的時間内完成指定的行為,如果要求兩個操作之間要完整的進行,肯定會存在某一個時刻隻完成一部分的業務操作,在通信完成的這一段時間内,資料就是不一緻性的。如果要求保證一緻性,那麼就必須在通信完成這一段時間内保護資料,使得任何通路這些資料的操作不可用。
如果想保證一緻性和可用性,那麼資料就不能夠分區。一個簡單的了解就是所有的資料就必須存放在一個資料庫裡面,不能進行資料庫拆分。這個對于大資料量,高并發的網際網路應用來說,是不可接受的。

上圖顯示了網絡中的兩個節點 n1,n2,他們共享同一資料 v,其值為 v0。n1 上有一個算法 a,我們可以認為 a 是安全、無 bug、可預測和可靠的。n2 上有一個類似的算法 b。在這個例子中,a 寫入 v 的新值而 b 讀取 v 的值。
正常情況過程如下:
1) a 寫入新的 v 值,我們稱作 v1。
2) n1 發送資訊給 n2,更新 v 的值。
3) 現在 b 讀取的 v 值将會是 v1。
如果網絡斷開,意味着從 n1 無法發送資訊到 n2,那麼在第3步的時候,n2 就會包含一個步一緻的 v 值。
即使将其擴充到幾百個事務中,這也會成為一個大問題。如果 m 是一個異步消息,那麼 n1 無法知道 n2 是否收到了消息。即使 m 是保證能發送的,n1 也無法知道是否消息由于分區事件的發生而延遲,或 n2 上的其他故障而延遲。即使将 m 作為同步消息也不能解決問題,因為那将會使得 n1 上 a 的寫操作和 n1 到 n2 的更新事件成為一個原子操作,而這将導緻同樣的等待問題。gilbert 和lynch已經證明,使用其他的變種方式,即使是部分同步模型(每個節點上使用安排好的時鐘)也無法保證原子性。
是以,cap 告訴我們,如果想讓 a 和 b 是高可用的(例如,以最小的延遲提供服務)并且想讓所有的 n1 到 nn(n的值可以是數百甚至是上千)的節點能夠備援網絡的分區(丢失資訊,無法傳遞資訊,硬體無法提供服務,處理失敗),那麼有時我們就得面臨這樣的情況:某些節點認為 v 的值是 v0 而其他節點會認為 v 的值是 v1。
讓我們從事務的角度分析一下。下面的圖中 a 是整個過程,要具有一緻性的話需要等待 a1 進行 write,然後同步到 a2,然後 a2 再進行 write,隻有整個事務完成以後,a2 才能夠進行 read。但是這樣的話使得整個系統的可用性下降,a2 一直阻塞在那裡等待 a1 同步到 a2。這個時候如果對一緻性要求不高的話,a2 可以不等待 a1 資料對于 a2 的寫同步,直接讀取,這樣雖然此時的讀寫不具有一緻性,但是在後面可以通過異步的方式使得 a1 和 a2 的資料最終一緻,達到 <code>最終一緻性</code>。
base 理論是 cap 理論結合實際的産物。 base(basically available, soft-state,eventuallyconsistent)英文中有堿的意思,這個正好和 acid 的酸的意義相對,很有意思。base 恰好和 acid 是相對的,base 要求犧牲高一緻性,獲得可用性或可靠性。
<code>basically availble</code>: 基本可用(支援分區失敗)
<code>soft-state</code>:軟狀态/柔性事務(無狀态連接配接,支援異步)
<code>eventual consistency</code>: 最終一緻性(不要求高一緻性,隻要求最終能夠一緻)
base 理論的核心是:犧牲高一緻性,獲得可用性或可靠性。
當處理 cap 的問題時,你會有幾個選擇。最明顯的是:
<code>放棄 tolerance of network partition</code>。如果你想避免分區問題發生,你就必須要阻止其發生。一種做法是将所有的東西(與事務相關的)都放到一台機器或者一個機架上。這樣還是有可能部分失敗,但你不太可能碰到由分區問題帶來的負面效果。當然,這個選擇會嚴重影響系統的擴充。
<code>放棄 availability</code>。相對于放棄 tolerance of network partition 來說,其反面就是放棄 availability。一旦遇到分區事件,受影響的服務需要等待資料一緻,是以在等待期間就無法對外提供服務。在多個節點上控制這一點會相當複雜,而且恢複的節點需要處理邏輯,以便平滑地傳回服務狀态。
<code>放棄 consistency</code>。放棄一緻性,你的系統可能傳回不太精确的資料,但系統将會變得“最終一緻”,即使是網絡發生分區的時候也是如此。
下面是一個使用 cap 理論的生态系統的分布圖:
任何架構師在設計分布式的系統的時候,都必須在這三者之間進行取舍。首先就是是否選擇分區,由于在一個資料分區内,根據資料庫的acid特性,是可以保證一緻性的,不會存在可用性和一緻性的問題,唯一需要考慮的就是性能問題。對于可用性和一緻性,大多數應用就必須保證可用性,畢竟是網際網路應用,犧牲了可用性,相當于間接的影響了使用者體驗,而唯一可以考慮就是一緻性了。
對于犧牲一緻性的情況最多的就是緩存和資料庫的資料同步問題,我們把緩存看做一個資料分區節點,資料庫看作另外一個節點,這兩個節點之間的資料在任何時刻都無法保證一緻性的。
還有一種犧牲一緻性的方法就是通過一種錯誤補償機制來進行
第一階段和第二階段之間,資料也可不能是一緻性的,也可能出現同樣的情況導緻異常。
1,2008年9月cto of atomikos寫了一篇文章“a cap solution (proving brewer wrong)”,試圖達到cap都得的效果。
這篇文章的核心内容就是放松gilbert和lynch證明中的限制:“系統必須同時達到cap三個屬性”,放松到“系統可以不同時達到cap,而是分時達到”。
他設計的系統如下:
(1)程式如果能夠讀取資料庫的話讀取資料庫,如果不能的話可以使用緩存代替。
(2)所有的讀取操作使用版本号或者其他可以使用樂觀鎖的機制。
(3)用戶端的所有更新操作全部放在隊列中順序處理。更新操作中要包括該更新的讀取操作時的版本資訊。
(4)當分區數量足夠少的時候,可以處理隊列中的更新操作。比較簡單的方式是建立一個跨越所有分布式副本的事務,對每個副本進行更新操作(其他方式比如quorum等等也可以)。如果該更新的讀取操作時的版本資訊不是目前資料庫中資料的版本資訊,則将失敗傳回給用戶端,否則傳回成功。
(5)資料庫操作結果(确認或者取消)通過異步的方式發送到用戶端,可以通過郵件,消息隊列或者其他異步方式。
該系統符合cap如下:
符合c(高一緻性):讀取的資料都是基于快照的,而且錯誤的更新操作不會執行。
符合a(高可用性):讀取和更新都會傳回資料。
符合p(高分區容錯性):允許網絡或者節點出錯。
該設計是符合base理論的。
缺點:
1) 讀資料可能會不一緻,因為之前的寫還在排隊
2) partition必須在有限的時間内解決
3) update操作必須在所有的節點上保持同樣的順序
2, 2011年11月twitter的首席工程師nathan marz寫了一篇文章,描述了他是如何試圖打敗cap定理的: how to beat the cap theorem
本文中,作者還是非常尊重cap定律,并表示不是要“擊敗”cap,而是嘗試對資料存儲系統進行重新設計,以可控的複雜度來實作cap。
marz認為一個分布式系統面臨cap難題的兩大問題就是:在資料庫中如何使用不斷變化的資料,如何使用算法來更新資料庫中的資料。marz提出了幾個由于雲計算的興起而改變的傳統概念:
1) 資料不存在update,隻存在append操作。這樣就把對資料的處理由crud變為cr
2) 所有的資料的操作就隻剩下create和read。把read作為一個query來處理,而一個query就是一個對整個資料集執行一個函數操作。
在這樣的模型下,我們使用最終一緻性的模型來處理資料,可以保證在p的情況下保證a。而所有的不一緻性都可以通過重複進行query去除掉。martz認為就是因為要不斷的更新資料庫中的資料,再加上cap,才導緻那些即便使用最終一緻性的系統也會變得無比複雜,需要用到向量時鐘、讀修複這種技術,而如果系統中不存在會改變的資料,所有的更新都作為建立新資料的方式存在,讀資料轉化為一次請求,這樣就可以避免最終一緻性的複雜性,轉而擁抱cap。