Eric Brewer:cap Twelve Years Later——how The ‘rules’ Have Changed@2012
以下是根據您提供的文章,提取的主要論點及其詳盡解釋:
這篇文章《CAP 十二年後:規則如何改變》由加州大學柏克萊分校的 Eric Brewer 所著,旨在重新審視其提出的 CAP 定理(一致性 Consistency、可用性 Availability、分區容錯性 Partition tolerance)在實際應用中的意義與侷限,並提出在 CAP 提出十二年後,設計分散式系統時應採取的更細緻、更實用的方法。文章的核心論點在於:原始 CAP 定理的「三選二」表述雖然在早期有助於開拓設計思路,但過於簡化了問題;現代設計者不應簡單地在一致性與可用性之間永久選擇其一,而應學習如何在面對網路分區時,有策略地管理並最大化兩者,並在分區恢復後進行狀態的恢復與錯誤的補償。
核心論點一:CAP 定理的「三選二」表述具有誤導性,過於簡化了問題
作者指出,最初提出 CAP 定理(任何共享數據的網路系統最多只能同時滿足一致性、可用性、分區容錯性這三個性質中的兩個)是為了激勵設計者探索更廣泛的分散式系統設計空間。這個「三選二」的表達方式確實達到了這個目的,尤其是在 NoSQL 運動中被廣泛引用來反駁傳統的 ACID 數據庫,並主張以可用性為優先。
然而,作者認為這種簡化的表述具有誤導性。原因如下:
- 分區是罕見事件,而非常態: CAP 定理所描述的真正衝突(必須在一致性與可用性之間做出取捨)只發生在系統出現網路分區時。在系統正常運行(沒有分區)的大部分時間裡,完全可以同時實現完美的一致性和高可用性。因此,不應該因為分區的可能性,就永久性地犧牲其中一個性質。
- 一致性和可用性是連續的,而非二元的: 文章強調,可用性從 0% 到 100% 是連續的,而一致性也有許多不同的等級(例如單一副本一致性、序列化、因果一致性、最終一致性等)。分區本身也存在程度差異(例如單邊分區)。原始的「三選二」將這些性質視為非黑即白的二元選擇,忽略了實際系統中可能存在的各種中間狀態和權衡。
- 選擇可以在細粒度上進行: 系統的不同子系統,甚至對不同的操作或不同的數據,都可以做出不同的 CAP 權衡。例如,一個系統可能對用戶個人資料選擇高可用性,允許在分區時進行部分更新並在後續同步,而對金融交易則選擇高一致性,在分區時阻止交易。這種細粒度的選擇在「三選二」的框架下難以表達。
- 「犧牲分區容錯性(P)」的說法不切實際: 有些人會說他們選擇了 CA(一致性+可用性),這意味著他們不容忍分區。但在廣域網或甚至同一數據中心內,網路分區是無法完全避免的物理現實。因此,設計者無法選擇「不發生分區」。更準確的說法是,如果設計者優先選擇 CA,那麼一旦發生分區,系統就必須退化為選擇 C 或 A。這意味著設計者實際上是在聲明分區發生的概率極低,低於其他故障。
因此,作者認為「三選二」的表述已經不再適用於指導現代分散式系統的精細設計。
核心論點二:現代分散式系統設計的重點應是如何「顯式地處理分區」,並在分區存在時優化一致性與可用性的結合
既然分區是無法避免且需要處理的,現代設計的目標不應是在 CAP 三角中永久選擇兩個點,而應是最大化在分區發生時一致性與可用性的有用組合。這要求設計者將處理分區作為系統設計的核心部分,而不是一個邊緣情況。
作者提出了一個三步驟的策略來顯式地管理分區:
- 偵測分區的開始: 這通常通過通訊逾時來實現。當一個節點嘗試與另一個節點通訊但逾時時,它就認為可能發生了分區。值得注意的是,分區偵測可能是局部的,一個節點可能偵測到分區,而另一個沒有。通訊逾時的時間長度也決定了系統進入「分區模式」的敏感度。延遲與分區密切相關,逾時本質上是一種「分區決策」:取消操作(犧牲 A)還是繼續操作並冒數據不一致的風險(犧牲 C)。
-
進入顯式分區模式: 一旦偵測到可能的分區,系統進入一個特殊的「分區模式」。在這個模式下,系統不再假定全局一致性能夠輕易維持。設計者需要預先規劃好在分區模式下哪些操作是允許的,哪些是必須限制的,以及如何記錄額外的資訊以便後續恢復。
- 限制操作的決策依據是「不變性(Invariants)」: 系統的許多正確性依賴於某些不變性(例如,銀行帳戶餘額不能為負,訂單號必須唯一)。在分區模式下,由於無法與另一邊通訊,有些操作可能會違反這些不變性。設計者必須針對每個不變性和每種操作,決定在分區時的處理方式:是完全禁止該操作(犧牲 A),還是允許操作但記錄下來待恢復時處理(可能臨時違反一致性),或是修改操作使其即使在分區下也能維持不變性。例如,允許在分區時創建可能重複的訂單號(風險,待恢復時處理),或者在分區時禁止取款操作(犧牲 A)。ATM 機的例子很貼切,它在分區時限制取款金額(犧牲了部分 A,但限制了風險)。
- 記錄歷史和元數據: 為了後續的恢復,在分區模式下執行的操作需要被仔細記錄。版本向量(Version Vectors)是一種有效的方式來跟蹤數據的因果依賴和識別並發更新,幫助系統在恢復時理解發生了什麼。
-
啟動分區恢復過程: 當通訊恢復,分區結束時,系統必須協調兩邊在分區期間獨立進行的操作,使系統狀態重新回到一致。這包括兩個主要部分:
- 狀態的合併與一致性恢復: 分區期間兩邊獨立進行操作,導致狀態 S1 和 S2。恢復過程需要將這些狀態合併成一個一致的 S’。這通常是通過重新應用兩邊的操作歷史來實現。如果操作是可交換的(commutative),則可以以任何順序應用並達到相同的最終狀態。CRDTs(Conflict-Free Replicated Data Types)是一種特殊類型的數據結構,它們的設計保證了即使在分區並獨立更新後,也能自動地、以數學可證明的方式合併並達成一致狀態(例如集合的聯集、計數器的單調增加)。亞馬遜的購物車就是一個例子,合併時簡單取兩個購物車的聯集(丟失了刪除操作),這是選擇了可用性(允許在分區時修改購物車)並保證了自動合併(即使可能誤將之前刪除的商品加回)。
-
對分區期間錯誤的補償: 僅僅使系統內部狀態一致是不夠的,還必須處理在分區期間發生的、已經「外部化」的錯誤或違反不變性的情況。例如,在分區時由於不知道全局狀態而重複扣款,或者允許了超出餘額的取款(如 ATM 例子)。這些錯誤是無法簡單撤銷的,因為它們已經影響了外部世界(例如,錢已經從 ATM 取出,訂單已經發送給供應商)。
- 補償性交易(Compensating Transactions): 解決外部化錯誤的核心思想是「補償」。這起源於處理長時間運行交易(Sagas)的概念。對於無法回滾(Rollback)的操作,系統執行一個新的「補償性交易」來糾正其影響。例如,對於重複扣款,不是回滾原交易,而是發起一個新的退款交易,並可能附帶一封道歉信或優惠券。
- 需要歷史記錄來判斷錯誤: 要知道如何補償,系統必須有分區期間外部化事件的歷史記錄。
- 挑戰: 補償比狀態合併更困難,因為它涉及與外部世界的互動,且通常沒有通用的自動補償框架,需要針對具體應用和可能違反的不變性來設計。ATM 例子說明了補償的複雜性,可能需要根據最終狀態(是否導致透支)來決定是否收取費用或忽略違規。
結論:權衡不是二選一,而是精細管理
文章總結說,分散式系統設計者不應該在存在分區時盲目地犧牲一致性或可用性。相反,通過仔細管理分區期間的不變性、記錄操作歷史,並在恢復時利用版本向量、CRDTs 等技術進行狀態合併和錯誤補償,可以有效地優化這兩個屬性。這種方法比簡單的「三選二」更為複雜,它要求設計者對服務的不變性、操作及其相互作用有深入的理解和周密的規劃。未來的框架工具可以幫助簡化這個過程,但根本上,這是關於在分散式環境下精確管理複雜性的挑戰。
comments
comments for this post are closed