這篇 blog 已被翻譯成 俄語, 日語 和 中文. 有關 CAP 問題的更多細節和其餘選擇方案的建議, 請閱讀個人論文 A Critique of the CAP Theorem.html
Jeff Hodges 在他那篇傑出的 blog 文章 "Distributed Systems for Young Bloods" 中建議用 CAP 定理來評判一個系統. 這的確很深刻人心, 以致於這些人直接將他們的系統描述爲 "CP" (consistent but not available under network partitions, 一致但在網絡分區的狀況下不可用), "AP" (available but not consistent under network partitions, 可用但在網絡分區的狀況下不一致), 或 "CA" (meaning "I still haven’t read Coda's post from almost 5 years Ago", 尚未讀過 Coda 的5年前的那篇文章).git
我贊成 Jeff 的全部其餘觀點, 但關於 CAP 定理, 我持有不一樣意見. CAP 定理過於精簡, 且被普遍誤解, 致使它對描述一個系統沒有多大用處. 因此我懇請你們最好仍是放棄 CAP 定理並中止引用和討論. 而且咱們要用更精確的術語來權衡系統.github
(我知道我寫這篇 blog 是在諷刺大家, 但至少這樣我就有了個 URL, 當別人問爲何我不喜歡他們談論 CAP 定理的時候我就能夠甩給他們. 另外我很抱歉這篇文章像是在對大家吼, 但起碼我吼的有理有據(lots of literature references)).web
若是想將 CAP 稱爲一個定理 (而不是數據庫軟文中模糊的缺少支撐的概念) 那就必需要精確. 數學是嚴謹的科學. 在證實過程中使用的詞語必須含義相同, 證實才能成立. 但在證實中, 這些定義都是一些特殊場景, 例如:算法
並且還要注意 CAP 定理不只描述的是個舊的系統, 並且是一個很是特定的系統:mongodb
若是你使用的相關定義與上面描述的關於 CAP 的定義精確相符, 則 CAP 定理適合你. 不過若是使用其餘的一致性或可用性的概念, 就不能期待 CAP 定理仍然適用. 固然這並不意味着從新定義一些詞彙就超脫現實, 而是說不能把 CAP 定理做爲指導, 並且不要用 CAP 定理來證實你的觀點.數據庫
若是CAP定理不適用, 那就意味着你必須本身去考慮系統中的權衡條件. 你能夠本身定義一致性和可用性, 而且證實你的定理. 但不要叫 CAP 定理了, 由於已經被佔用了.apache
若是你不熟悉 線性一致性 (Linearizability, 即 CAP 意義上的 "一致性, consistency"). 簡要的解釋一下, 正式的定義並非十分的直截了當, 不過定義的核心簡單來說是:編程
若是操做 A 成功完成, 進行操做 B, 那麼操做 B 必須觀察到系統處於操做 A 完成, 或更新完畢的統一狀態.
爲了更具體, 考慮一個非線性化的系統, 見下圖 (這個圖出自我未發佈的書中):bash
該圖顯示了位於同一房間的 Alice 和 Bob, 他倆都在刷手機看 2014 足球世界盃的決賽結果. 在最終比分公佈後, Alice 刷新頁面, 興奮地告訴 Bob 獲勝者已經公佈了. Bob 半信半疑的刷新本身手機上的頁面, 可是他的請求被轉發到了一個滯後的數據庫副本 (database replica), 因此他的手機上顯示比賽仍在繼續.
假如 Alice 和 Bob 同時從新加載, 那麼他們獲得了兩個不一樣的查詢結果也是有可能的, 由於他們不知道服務器處理他們各自的請求的確切時間. 然而, Bob 是在聽到 Alice 驚歎最終比分後才點擊了從新加載按鈕 (發起他的查詢), 所以他但願他的查詢結果至少與 Alice 的同樣, 然而卻獲得了舊的查詢結果, 這一事實違反了線性一致性 (Linearizability).
Bob 的請求嚴格地發生在 Alice 的請求以後 (即它們不是併發的), 是由於 Bob 經過一個單獨的通訊通道 (本例中是現實世界的聲音) 聽到 Alice 的查詢結果. 若是 Bob 沒有從 Alice 那裏據說比賽結束了, 他就不會知道他的查詢結果已通過期了.
若是你正在構建一個數據庫, 是沒法知道客戶端可能有哪些其餘通訊渠道 (backchannel) 的. 所以, 若是但願在數據庫中提供線性語義 (linearizable semantics 即CAP中的consistency), 就須要讓數據看起來只有一個副本, 即便數據可能在多個位置有多個副本 (replicas, caches).
實現這樣的保障很是困難, 由於它須要大量的協調. 甚至計算機中的 CPU 都不能提供對本地內存的線性訪問! 在現代 CPU 上須要使用顯式內存屏障指令 (explicit memory barrier instruction) 才能線性訪問. 甚至測試一個系統是否能提供線性訪問也是很棘手的.
讓咱們簡單地討論下在網絡分區 (network partition) 狀況下放棄線性一致性 (linearizability) 或可用性 (availability) 的必要性.
假設在兩個不一樣的數據中心中都有數據庫的副本 (replicas). 數據複製的確切方法目前並不重要 - 它多是單主 (主/從, master/slave), 多主 (主/主, master/master) 或基於 quorum (即節點選舉制) 的副本 (相似 Amazon Dynamo). 副本的要求是, 在一個數據中心中寫入數據的同時, 都必須將數據寫入另外一個數據中心中的副本. 假設客戶端只鏈接到一個數據中心, 那麼當數據複製的時候就必須依賴兩個數據中心之間的網絡連接.
如今假設網絡鏈接中斷 - 這就是網絡分區 (network partition) 的意思. 猜猜會發生什麼?
很明顯你只能二選一:
(順便說這基本就是對CAP定理的證實了. 這就是他的所有內容. 本例中使用了2個數據中心, 但這也一樣適用於單一數據中心中的網路問題. 只是想象成2個數據中心時比較好理解.)
注意, 在選項2中的理論上"不可用" (unavailable) 狀況下, 咱們仍然在一個數據中心中愉快地處理着請求. 所以若是系統選擇了線性一致性 (linearizability, 即它不是cap可用的), 這並不必定意味着網絡分區會自動致使應用程序停機. 若是您能夠將全部客戶端請求轉移到 leader 數據中心, 那麼客戶端實際上根本不會感受到停機時間.
在實踐中, 可用性並不徹底與 CAP 可用性 (CAP-availability) 一致. 應用程序的可用性多是經過 SLA 來衡量的 (例如, 99.9% 的合法請求必須在1秒內成功返回響應), 可是這樣的 SLA 指標 CAP 可用 (CAP-available) 和 CAP 不可用 (CAP-unavailable) 的系統都能知足.
在實踐中, 多數據中心繫統一般設計爲異步複製, 所以是非線性的 (non-linearizable). 然而, 這種選擇的緣由一般是因爲廣域網的延遲, 而不只僅是數據中心故障和網絡故障容災.
在 CAP定理 對一致性 (線性一致性) 和可用性的嚴格定義下, 系統如何運行?
例如, 以具備單 leader 的複製型數據庫爲例, 這是大多數關係型數據庫中配置副本的標準方法. 在此配置中, 若是客戶端與 leader 發生分區, 則沒法寫入數據庫. 儘管或許能夠在從數據庫 (只讀副本) 讀取數據, 但不能寫入的事實意味着任何單 leader 的配置都不是 CAP 可用的. 儘管這樣的配置經常被營銷爲"高可用性".
若是單 leader 集羣不算 CAP 可用, 那是否意味着是 "CP" 的? 沒那麼簡單, 若是容許應用讀取 follower 節點, 而且數據複製是異步的 (大多數數據庫都是這樣的),
那麼當從 follower 進行讀取時, follower 可能會稍微落後於 leader. 在這種狀況下, 讀取就不是線性可用的, 即不符合 CAP 一致性 (CAP-consistent).
此外, 具備 快照隔離(snapshot isolation)/MVCC的數據庫是刻意設計成非線性的, 由於強制實現線性一致性會下降數據庫的併發性能. 例如, PostgreSQL SSI 提供順序一致性 (serializability), 但不提供線性一致性 (linearizability), 而 Oracle 這兩個都不支持. 數據庫稱做"ACID"並不意味着它知足 CAP 定理中對一致性的定義.
因此這些系統不 CAP 一致也不 CAP 可用. 它們既不"CP"也不"AP", 只是"P", 不論"P"究竟是啥. ("三選二"的公式確實是能夠直選一種的, 甚至三個都不選也能夠!)
那麼"NoSQL"呢? 以 MongoDB 爲例: 每一個 shard 中有一個 leader (只要不是在裂腦模(split-brain)式下, 就應該是這樣的), 所以上面的狀況不符合 CAP, Kyle 最近展現 了即便在最高的一致性設置下, MongoDB 也容許非線性讀取, 因此不是 CAP一致的.
還有相似 Dynamo 的衍生品: Riak, Cassandra, Voldemort. 這些一般被稱做"AP"是由於針對高可用優化了麼? 仍是取決於你的設置. 若是能接受單副本讀寫 (R=W=1), 那麼它們確實是 CAP 可用的. 可是, 若是須要仲裁讀寫 (R+W>N), 而且遇到了網絡分區, 節點較少的分區沒法進行仲裁, 所以仲裁操做不是 CAP 可用的 (至少暫時不可用, 直到在節點較少的分區設置其餘的數據庫副本節點).
有人聲稱仲裁機制 (quorum) 的讀寫能夠保證線性一致性 (linearizability), 但我認爲依賴它不是個明智的選擇, 好比一些特性的微妙組合: 鬆散仲裁 (sloppy quorums), 讀修正 (read repair), 會導致棘手的邊緣狀況 (edge cases). 例如刪除的數據又出現了, 副本數量小於本來的寫節點數量 (W) (違反了仲裁機制), 副本節點數量增長超過了副本總數 (N) (仍是違反了仲裁機制). 這些狀況都會致使結果不線性一致.
這些系統並非很糟糕, 你們一直在生產環境中正常使用. 然而到目前爲止, 還不能嚴格地將它們分類爲"AP"或"CP", 這要麼是由於必需要進行特定的操做或配置, 要麼是由於系統不知足 CAP 定理中對一致性或可用性的嚴格定義.
那麼ZooKeeper如何? 它使用了一種共識算法, 所以人們一般將其視爲選擇一致性放棄可用性(即"CP系統")的明確案例.
可是, 若是查看ZooKeeper文檔, 就會發現ZooKeeper在默認狀況下不提供線性讀取. 每一個客戶端鏈接到一個服務器節點, 當進行讀取時, 只能看到該節點上的數據, 不管你在另外一個節點上寫入多少新數據. 這會比起每次讀取都必須獲取仲裁信息或每次讀取都與主節點進行通訊要快得多, 但這也意味着 ZooKeeper 在默認狀況下不知足CAP定理中對一致性的定義.
在ZooKeeper中能夠經過在讀取以前使用同步(sync)命令進行線性化讀取, 但不是默認的, 由於這會帶來性能損失. 確實有人會使用會使用同步, 但一般不是每次操做都用.
ZooKeeper 的可用性如何? ZooKeeper 須要達成多數仲裁才能達成一致, 也就是說, 才能處理寫操做. 若是發生分區, 其中一側是多數節點, 另外一側是少數節點, 那麼多數節點側將繼續可用, 可是少數節點側就不能處理寫操做, 即便節點都是正常 (up) 的. 所以, ZooKeeper 的寫操做在分區狀況下不是 CAP 可用的 (即便多數節點側能夠繼續處理寫操做).
有意思的是, ZooKeeper 3.4.0 添加了只讀模式, 在這種模式下,分區狀況下的少數節點側能夠繼續提供讀請求 - 並且不須要仲裁! 這個只讀模式是 CAP 可用的. 所以, ZooKeeper 在默認狀況下既不是 CAP一致的 (CP), 也不是 CAP 可用的 (AP). 它實際上就是個"P". 但若是須要, 能夠經過調用sync讓它變成CP的, 若是打開正確的選項, 對於讀操做 (而不是寫操做), 它其實是AP的.
但這使人惱火. 僅僅由於 ZooKeeper 在默認狀況下不是線性化的, 就稱做"不一致", 那就嚴重歪曲了它的特性. 實際上 ZooKeeper 提供了很是好的一致性! 它提供的 原子化廣播 (可簡化爲共識機制) 結合了 session保障 (session guarantee) 的因果一致性 (causal consistency) , 這可比讀取寫入, 單調讀取 (monotonic reads) 和一致前綴讀取 (consistent prefix reads) 的組合更強大. 文檔說 ZooKeeper 提供了順序一致性, 可是這是低估了本身, 由於 ZooKeeper 的保證明際上比順序一致性 (sequential consistency) 強不少.
正如ZooKeeper所展現的, 在存在分區的狀況下, 擁有一個既不 CAP 一致也不 CAP 可用的系統是很是合理的, 並且在沒有分區的狀況下, 系統在默認甚至不是線性化的. (我想在 Abadi 的 PACELC 理論框架裏, 這應該算是PC/EL的了, 但我以爲仍是CAP比較有啓發性.)
事實上咱們很難將一個數據存儲系統明確地劃分爲"AP"或"CP", 這一事實證實了: 這些根本不是系統的正確描述方式.
我認爲咱們不該將數據存儲系統歸類爲"AP"或"CP", 由於:
若是CP和AP不適合描述和評價系統, 那麼應該使用什麼來替代呢? 我認爲答案不是惟一的. 不少人都認真思考過這些問題, 並提出了術語和模型來幫助咱們理解問題. 要了解這些觀點, 必須深刻研究文獻.
不管你選擇怎樣的學習方式, 我鼓勵你保持好奇心和耐心 - 這些東西來之不易. 可是這是有好處的,由於你學會了權衡利弊, 從而肯定哪一種體系結構最適合您的特定應用程序. 可是不管你作什麼, 請中止談論CP和AP, 由於它們沒有任何意義.
感謝 Kyle Kingsbury 和 Camille Fournier 對本文草稿的註解. 固然, 任何錯誤或使人不快的意見都是個人鍋.