請不要繼續將數據庫稱爲 CP 或 AP

Please-stop-calling-databases-CP-or-AP.md

請不要繼續將數據庫稱爲 CP 或 AP

這篇 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 稱爲一個定理 (而不是數據庫軟文中模糊的缺少支撐的概念) 那就必需要精確. 數學是嚴謹的科學. 在證實過程中使用的詞語必須含義相同, 證實才能成立. 但在證實中, 這些定義都是一些特殊場景, 例如:算法

  • 一致性 (Consistency) 在 CAP 中實際上是 線性一致性 (linearizability), 線性一致性是很是特定 (且很是強 Strong) 的一致性概念. 並且他跟 ACID 中的 C 是無關的, 儘管這個 C 也表明 "一致性" (consistency). 我接下來會解釋什麼是線性一致性 (linearizability).
  • 可用性 (Availability) 在 CAP 中定義爲 "系統中的非故障(數據庫)節點針對收到的每一個請求必須作出[非錯誤]響應". 這裏的問題在於, 某個節點可以處理請求是不夠的, 任何非故障節點都要可以處理請求. 許多所謂的 "高可用" (即低停機時間) 系統都不符合這種可用性的定義.
  • 分區容錯性 (Partition Tolerance)(很是錯誤的名稱) 基本上意味着你的通訊創建在可能會延遲或丟包的異步通訊網絡上. 互聯網和咱們的數據中心都有這個屬性, 因此在這個問題上其實沒有任何選擇.

並且還要注意 CAP 定理不只描述的是個舊的系統, 並且是一個很是特定的系統:mongodb

  • CAP 是一個單一讀寫寄存器 (single, read-write register) 系統模型. 例如, CAP定理沒有說明涉及多個對象的事務, 由於這超出了定理範圍. 除非能把這種狀況精簡到單寄存器.
  • CAP 定理考慮的惟一故障場景是網絡分區 (network partition, 即節點保持運行, 可是節點所在的部分網絡沒法通訊產生了分區). 這種故障絕對會發生, 但毫不是惟一會發生的故障: 節點可能會崩潰或被重啓, 可能遇到磁盤空間不足, 可能遇到軟件中的bug, 等等故障. 在構建分佈式系統中, 須要考慮更大範圍的取捨, 過分關注 CAP 定理則會讓你忽視其餘重要的問題.
  • 此外, CAP 定理徹底沒有提到延遲 (latency), 你們關注的可不只僅是可用性. 實際上, CAP 可用的系統容許響應無限慢, 而且這樣仍然能夠稱做 "可用" (available). 若是加載一個頁面要2分鐘, 用戶可就不會認爲你的系統 "可用"了.

若是你使用的相關定義與上面描述的關於 CAP 的定義精確相符, 則 CAP 定理適合你. 不過若是使用其餘的一致性或可用性的概念, 就不能期待 CAP 定理仍然適用. 固然這並不意味着從新定義一些詞彙就超脫現實, 而是說不能把 CAP 定理做爲指導, 並且不要用 CAP 定理來證實你的觀點.數據庫

若是CAP定理不適用, 那就意味着你必須本身去考慮系統中的權衡條件. 你能夠本身定義一致性和可用性, 而且證實你的定理. 但不要叫 CAP 定理了, 由於已經被佔用了.apache

線性一致性 (Linearizability)

若是你不熟悉 線性一致性 (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) 才能線性訪問. 甚至測試一個系統是否能提供線性訪問也是很棘手的.

CAP-Availability

讓咱們簡單地討論下在網絡分區 (network partition) 狀況下放棄線性一致性 (linearizability) 或可用性 (availability) 的必要性.

假設在兩個不一樣的數據中心中都有數據庫的副本 (replicas). 數據複製的確切方法目前並不重要 - 它多是單主 (主/從, master/slave), 多主 (主/主, master/master) 或基於 quorum (即節點選舉制) 的副本 (相似 Amazon Dynamo). 副本的要求是, 在一個數據中心中寫入數據的同時, 都必須將數據寫入另外一個數據中心中的副本. 假設客戶端只鏈接到一個數據中心, 那麼當數據複製的時候就必須依賴兩個數據中心之間的網絡連接.

如今假設網絡鏈接中斷 - 這就是網絡分區 (network partition) 的意思. 猜猜會發生什麼?

很明顯你只能二選一:

  1. 程序能夠繼續寫入數據庫, 因此兩個數據中心中數據庫都是徹底可用的. 但只要數據複製依賴的鏈接被中斷, 在一個數據中心中對數據庫的更改將不會同步到另一個數據中心. 這違反了線性一致性 (linearizability)(在前面的例子中, Alice 可能鏈接到數據中心1, Bob 鏈接到了數據中心2).
  2. 若是不想失去線性一致性 (linearizability), 就必須確保在一個數據中心執行全部讀寫操做 (能夠稱做leader), 在另外一個數據中心 (因爲數據複製依賴的鏈接中斷而沒法更新到最新) 在網路分區恢復正常,數據同步完畢以前必須中止接受讀寫操做. 儘管非 leader 數據庫沒有失效 (failed), 但他沒法處理請求, 所以它不是CAP可用 (CAP-available) 的.

(順便說這基本就是對CAP定理的證實了. 這就是他的所有內容. 本例中使用了2個數據中心, 但這也一樣適用於單一數據中心中的網路問題. 只是想象成2個數據中心時比較好理解.)

注意, 在選項2中的理論上"不可用" (unavailable) 狀況下, 咱們仍然在一個數據中心中愉快地處理着請求. 所以若是系統選擇了線性一致性 (linearizability, 即它不是cap可用的), 這並不必定意味着網絡分區會自動致使應用程序停機. 若是您能夠將全部客戶端請求轉移到 leader 數據中心, 那麼客戶端實際上根本不會感受到停機時間.

在實踐中, 可用性並不徹底與 CAP 可用性 (CAP-availability) 一致. 應用程序的可用性多是經過 SLA 來衡量的 (例如, 99.9% 的合法請求必須在1秒內成功返回響應), 可是這樣的 SLA 指標 CAP 可用 (CAP-available) 和 CAP 不可用 (CAP-unavailable) 的系統都能知足.

在實踐中, 多數據中心繫統一般設計爲異步複製, 所以是非線性的 (non-linearizable). 然而, 這種選擇的緣由一般是因爲廣域網的延遲, 而不只僅是數據中心故障和網絡故障容災.

多數系統既不線性可用 (linearizable) 也不 CAP 可用 (CAP-available)

在 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

那麼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比較有啓發性.)

CP或AP: 錯誤的權衡

事實上咱們很難將一個數據存儲系統明確地劃分爲"AP"或"CP", 這一事實證實了: 這些根本不是系統的正確描述方式.

我認爲咱們不該將數據存儲系統歸類爲"AP"或"CP", 由於:

  • 在程序中, 可能會有多種具備一致性特徵的操做.
  • 在CAP定理的定義下, 許多系統既不一致也不可用. 但歷來沒有據說有人把他們的系統只稱爲"P", 大概是由於這樣看起來很糟糕. 但這並不很糟糕 - 它多是一個徹底合理的設計, 只是不適合分類爲CP或AP.
  • 儘管大多數軟件都不能很好地分類成這兩種狀況, 但人們仍是試圖將軟件硬塞進這兩種狀況中的一種, 從而不可避免地將"一致性"或"可用性"的含義更改成任何適合他們的定義. 不幸的是, 若是詞義發生變化, 那麼CAP定理就再也不適用, 所以CP/AP的區別就變得毫無心義了.
  • 將一個系統分類爲這兩種的任何一種都會丟失大量的細節. 在分佈式系統的設計中, 有許多關於容錯, 延遲, 編程模型的簡單性, 可操做性等方面的權衡. 這些爲分佈式系統的設計提供了支撐. 這些信息是不可能簡單的就這樣描述的. 例如, 儘管 ZooKeeper 有一個"AP"只讀模式, 但該模式仍然提供有序的歷史寫入記錄, 這比Riak或Cassandra等系統中的"AP"提供了強大得多的保障 - 所以將它們歸爲一類是很荒謬的.
  • 就連 Eric Brewer 也認可 CAP 具備誤導性並且過於簡單化. 在2000年, CAP 的意義在於討論分佈式數據系統中的權衡, 這方面的確作得很好. 但它並非一個突破性的正式結果, 也不是一個嚴格的數據系統分類方案. 15年後的今天, 咱們有了更多具備不一樣一致性和容錯模型的衡量工具可供選擇. CAP已經達到了它的目的, 如今是時候繼續前進了.

學會獨立思考

若是CP和AP不適合描述和評價系統, 那麼應該使用什麼來替代呢? 我認爲答案不是惟一的. 不少人都認真思考過這些問題, 並提出了術語和模型來幫助咱們理解問題. 要了解這些觀點, 必須深刻研究文獻.

  • 一個很好的起點是 Doug Terry 的論文, 他在文中用棒球的例子解釋了不一樣水平的最終一致性 (eventual consistency ). 即便你(像我同樣)不是美國人, 對棒球一無所知, 這篇文章也很是清晰易懂.
  • 若是對事務隔離模型感興趣(這與分佈式副本的一致性不同, 但比較相關), 那麼能夠了解下個人小項目 Hermitage.
  • Peter Bailis 等人探索了副本一致性 (replica consistency), 事務隔離 (transaction isolation) 和可用性 (availability) 之間的聯繫 (這篇論文還解釋了 Kyle Kingsbury 喜歡展現的一致性層次結構的意義).
  • 當你讀過這些以後, 該準備好了深刻研究這些文獻. 我在這篇文章中放了大量的文章連接. 必定要看一看: 許多專家已經爲你解決了不少問題.
  • 最後, 若是你沒法閱讀那些論文, 我建議你看看個人, 書中以平易近人的方式總結了最重要的一些理念. (看, 我很是努力地不讓這篇文章成爲一篇軟文.)
  • 若是你想更具體地知道如何正確使用 ZooKeeper, Flavio Junqueira 和 Benjamin Reed 的書是不錯的.

不管你選擇怎樣的學習方式, 我鼓勵你保持好奇心和耐心 - 這些東西來之不易. 可是這是有好處的,由於你學會了權衡利弊, 從而肯定哪一種體系結構最適合您的特定應用程序. 可是不管你作什麼, 請中止談論CP和AP, 由於它們沒有任何意義.

感謝 Kyle Kingsbury 和 Camille Fournier 對本文草稿的註解. 固然, 任何錯誤或使人不快的意見都是個人鍋.

相關文章
相關標籤/搜索