CAP 理論斷言任何基於網絡的數據共享系統,最多隻能知足數據一致性、可用性、分區容忍性三要素中的兩個要素。可是經過顯式處理分區情形,系統設計師能夠作到優化數據一致性和可用性,進而取得三者之間的平衡。算法
自打引入 CAP 理論的十幾年裏,設計師和研究者已經以它爲理論基礎探索了各式各樣新穎的分佈式系統,甚至到了濫用的程度。NoSQL 運動也將 CAP 理論看成對抗傳統關係型數據庫的依據。數據庫
CAP 理論主張任何基於網絡的數據共享系統,都最多隻能擁有如下三條中的兩條:編程
CAP 理論的表述很好地服務了它的目的,即開闊設計師的思路,在多樣化的取捨方案下設計出多樣化的系統。在過去的十幾年裏確實涌現了不可勝數的新系統,也隨之在數據一致性和可用性的相對關係上產生了至關多的爭論。「三選二」的公式一直存在着誤導性,它會過度簡單化各性質之間的相互關係。如今咱們有必要辨析其中的細節。實際上只有「在分區存在的前提下呈現完美的數據一致性和可用性」這種不多見的狀況是 CAP 理論不容許出現的。api
雖然設計師仍然須要在分區的前提下對數據一致性和可用性作取捨,但具體如何處理分區和恢復一致性,這裏面有不可勝數的變通方案和靈活度。當代 CAP 實踐應將目標定爲針對具體的應用,在合理範圍內最大化數據一致性和可用性的「協力」。這樣的思路延伸爲如何規劃分區期間的操做和分區以後的恢復,從而啓發設計師加深對 CAP 的認識,突破過去因爲 CAP 理論的表述而產生的思惟侷限。緩存
理解 CAP 理論的最簡單方式是想象兩個節點分處分區兩側。容許至少一個節點更新狀態會致使數據不一致,即喪失了 C 性質。若是爲了保證數據一致性,將分區一側的節點設置爲不可用,那麼又喪失了 A 性質。除非兩個節點能夠互相通訊,才能既保證 C 又保證 A,這又會致使喪失 P 性質。通常來講跨區域的系統,設計師沒法捨棄 P 性質,那麼就只能在數據一致性和可用性上作一個艱難選擇。不確切地說,NoSQL 運動的主題實際上是創造各類可用性優先、數據一致性其次的方案;而傳統數據庫堅守 ACID 特性(原子性、一致性、隔離性、持久性),作的是相反的事情。下文「ACID、BASE、CAP」小節詳細說明了它們的差別。服務器
事實上,CAP 理論自己就是在相似的討論中誕生的。早在 1990 年代中期,我和同事構建了一系列的基於集羣的跨區域系統(實質上是早期的雲計算),包括搜索引擎、緩存代理以及內容分發系統1。從收入目標以及合約規定來說,系統可用性是首要目標,於是咱們常規會使用緩存或者過後校覈更新日誌來優化系統的可用性。儘管這些策略提高了系統的可用性,但這是以犧牲系統數據一致性爲代價的。網絡
關於「數據一致性 VS 可用性」的第一回合爭論,表現爲 ACID 與 BASE 之爭2。當時 BASE 還不怎麼被人們接受,主要是你們看重 ACID 的優勢而不肯意放棄。提出 CAP 理論,目的是證實有必要開拓更廣闊的設計空間,所以纔有了「三選二」公式。CAP 理論最先在 1998 年秋季提出,1999 年正式發表3,並在 2000 年登上 Symposium on Principles of Distributed Computing 大會的主題演講4,最終確立了該理論的正確性。數據結構
「三選二」的觀點在幾個方面起了誤導做用,詳見下文「CAP 之惑」小節的解釋。首先,因爲分區不多發生,那麼在系統不存在分區的狀況下沒什麼理由犧牲 C 或 A。其次,C 與 A 之間的取捨能夠在同一系統內以很是細小的粒度反覆發生,而每一次的決策可能由於具體的操做,乃至由於牽涉到特定的數據或用戶而有所不一樣。最後,這三種性質均可以在程度上衡量,並非非黑即白的有或無。可用性顯然是在 0% 到 100% 之間連續變化的,一致性分不少級別,連分區也能夠細分爲不一樣含義,如系統內的不一樣部分對因而否存在分區能夠有不同的認知。多線程
要探索這些細微的差異,就要突破傳統的分區處理方式,而這是一項根本性的挑戰。由於分區不多出現,CAP 在大多數時候容許完美的 C 和 A。但當分區存在或可感知其影響的狀況下,就要預備一種策略去探知分區並顯式處理其影響。這樣的策略應分爲三個步驟:探知分區發生,進入顯式的分區模式以限制某些操做,啓動恢復過程以恢復數據一致性並補償分區期間發生的錯誤。併發
ACID 和 BASE 表明了兩種截然相反的設計哲學,分處一致性 - 可用性分佈圖譜的兩極。ACID 注重一致性,是數據庫的傳統設計思路。我和同事在 1990 年代晚期提出 BASE,目的是抓住當時正逐漸成型的一些針對高可用性的設計思路,而且把不一樣性質之間的取捨和消長關係擺上檯面。現代大規模跨區域分佈的系統,包括雲在內,同時運用了這兩種思路。
這兩個術語都好記有餘而精確不足,出現較晚的 BASE 硬湊的感受更明顯,它是「Basically Available, Soft state, Eventually consistent(基本可用、軟狀態、最終一致性)」的首字母縮寫。其中的軟狀態和最終一致性這兩種技巧擅於對付存在分區的場合,並所以提升了可用性。
CAP 與 ACID 的關係更復雜一些,也所以引發更多誤解。其中一個緣由是 ACID 的 C 和 A 字母所表明的概念不一樣於 CAP 的 C 和 A。還有一個緣由是選擇可用性只部分地影響 ACID 約束。ACID 四項特性分別爲:
原子性(A)。全部的系統都受惠於原子性操做。當咱們考慮可用性的時候,沒有理由去改變分區兩側操做的原子性。並且知足 ACID 定義的、高抽象層次的原子操做,實際上會簡化分區恢復。
一致性(C)。ACID 的 C 指的是事務不能破壞任何數據庫規則,如鍵的惟一性。與之相比,CAP 的 C 僅指單一副本這個意義上的一致性,所以只是 ACID 一致性約束的一個嚴格的子集。ACID 一致性不可能在分區過程當中保持,所以分區恢復時須要重建 ACID 一致性。推而廣之,分區期間也許不可能維持某些不變性約束,因此有必要仔細考慮哪些操做應該禁止,分區後又如何恢復這些不變性約束。
隔離性(I)。隔離是 CAP 理論的核心:若是系統要求 ACID 隔離性,那麼它在分區期間最多能夠在分區一側維持操做。事務的可串行性(serializability)要求全局的通訊,所以在分區的狀況下不能成立。只要在分區恢復時進行補償,在分區先後保持一個較弱的正確性定義是可行的。
持久性(D)。犧牲持久性沒有意義,理由和原子性同樣,雖然開發者有理由(持久性成本過高)選擇 BASE 風格的軟狀態來避免實現持久性。這裏有一個細節,分區恢復可能由於回退持久性操做,而無心中破壞某項不變性約束。但只要恢復時給定分區兩側的持久性操做歷史記錄,破壞不變性約束的操做仍是能夠被檢測出來並修正的。一般來說,讓分區兩側的事務都知足 ACID 特性會使得後續的分區恢復變得更容易,而且爲分區恢復時事務的補償工做奠基了基本的條件。
CAP 理論的經典解釋,是忽略網絡延遲的,但在實際中延遲和分區緊密相關。CAP 從理論變爲現實的場景發生在操做的間歇,系統須要在這段時間內作出關於分區的一個重要決定:
依靠屢次嘗試通訊的方法來達到一致性,好比 Paxos 算法或者兩階段事務提交,僅僅是推遲了決策的時間。系統終究要作一個決定;無限期地嘗試下去,自己就是選擇一致性犧牲可用性的表現。
所以以實際效果而言,分區至關於對通訊的時限要求。系統若是不能在時限內達成數據一致性,就意味着發生了分區的狀況,必須就當前操做在 C 和 A 之間作出選擇。這就從延遲的角度抓住了設計的核心問題:分區兩側是否在無通訊的狀況下繼續其操做?
從這個實用的觀察角度出發能夠導出若干重要的推論。第一,分區並非全體節點的一致看法,由於有些節點檢測到了分區,有些可能沒有。第二,檢測到分區的節點即進入分區模式——這是優化 C 和 A 的核心環節。
最後,這個觀察角度還意味着設計師能夠根據指望中的響應時間,有意識地設置時限;時限設得越短,系統進入分區模式越頻繁,其中有些時候並不必定真的發生了分區的狀況,可能只是網絡變慢而已。
有時候在跨區域的系統,放棄強一致性來避免保持數據一致所帶來的高延遲是很是有意義的。Yahoo 的 PNUTS 系統由於以異步的方式維護遠程副本而帶來數據一致性的問題5。但好處是主副本就放在本地,減少操做的等待時間。這個策略在實際中很實用,由於通常來說,用戶數據大都會根據用戶的(平常)地理位置作分區。最理想的情況是每一位用戶都在他的數據主副本附近。
Facebook 使用了相反的策略6:主副本被固定在一個地方,所以遠程用戶通常訪問到的是離他較近,但可能已通過時的數據副本。不過當用戶更新其頁面的時候是直接對主副本進行更新,並且該用戶的全部讀操做也被短暫轉向從主副本讀取,儘管這樣延遲會比較高。20 秒後,該用戶的流量被從新切換回離他較近的副本,此時副本應該已經同步好了剛纔的更新。
CAP 理論常常在不一樣方面被人誤解,對於可用性和一致性的做用範圍的誤解尤其嚴重,可能形成不但願看到的結果。若是用戶根本獲取不到服務,那麼其實談不上 C 和 A 之間作取捨,除非把一部分服務放在客戶端上運行,即所謂的無鏈接操做或稱離線模式7。離線模式正變得愈來愈重要。HTML5 的一些特性,特別是客戶端持久化存儲特性,將會促進離線操做的發展。支持離線模式的系統一般會在 C 和 A 中選擇 A,那麼就不得不在長時間處於分區狀態後進行恢復。
「一致性的做用範圍」其實反映了這樣一種觀念,即在必定的邊界內狀態是一致的,但超出了邊界就無從談起。好比在一個主分區內能夠保證完備的一致性和可用性,而在分區外服務是不可用的。Paxos 算法和原子性多播(atomic multicast)系統通常符合這樣的場景8。像 Google 的通常作法是將主分區歸屬在單一個數據中內心面,而後交給 Paxos 算法去解決跨區域的問題,一方面保證全局協商一致(global consensus)如 Chubby9,一方面實現高可用的持久性存儲如 Megastore10。
分區期間,獨立且能自我保證一致性的節點子集合能夠繼續執行操做,只是沒法保證全局範圍的不變性約束不受破壞。數據分片(sharding)就是這樣的例子,設計師預先將數據劃分到不一樣的分區節點,分區期間單個數據分片多半能夠繼續操做。相反,若是被分區的是內在關係密切的狀態,或者有某些全局性的不變性約束非保持不可,那麼最好的狀況是隻有分區一側能夠進行操做,最壞狀況是操做徹底不能進行。
「三選二」的時候取 CA 而舍 P 是否合理?已經有研究者指出了其中的要害——怎樣纔算「舍 P」含義並不明確11,12。設計師能夠選擇不要分區嗎?哪怕原來選了 CA,當分區出現的時候,你也只能回頭從新在 C 和 A 之間再選一次。咱們最好從機率的角度去理解:選擇 CA 意味着咱們假定,分區出現的可能性要比其餘的系統性錯誤(如天然災難、併發故障)低不少。
這種觀點在實際中頗有意義,由於某些故障組合可能致使同時丟掉 C 和 A,因此說 CAP 三個性質都是一個度的問題。實踐中,大部分團體認爲(位於單一地點的)數據中心內部是沒有分區的,所以在單一數據中心以內能夠選擇 CA;CAP 理論出現以前,系統都默認這樣的設計思路,包括傳統數據庫在內。然而就算可能性不高,單一數據中心徹底有可能出現分區的狀況,一旦出現就會動搖以 CA 爲取向的設計基礎。最後,考慮到跨區域時出現的高延遲,在數據一致性上讓步來換取更好性能的作法相對比較常見。
CAP 還有一個方面不少人認識不清,那就是放棄一致性其實有隱藏負擔,即須要明確瞭解系統中存在的不變性約束。知足一致性的系統有一種保持其不變性約束的天然傾向,即使設計師不清楚系統中全部的不變性約束,至關一部分合理的不變性約束會自動地維持下去。相反,當設計師選擇可用性的時候,由於須要在分區結束後恢復被破壞的不變性約束,顯然必須將各類不變性約束一一列舉出來,可想而知這件工做頗有挑戰又很容易犯錯。放棄一致性爲何難,其核心仍是「併發更新問題」,跟多線程編程比順序編程難的緣由是同樣的。
怎樣緩和分區對一致性和可用性的影響是對設計師的挑戰。其關鍵是以很是明確、公開的方式去管理分區,不只須要主動察覺分區的發生,還須要爲分區期間全部可能受侵害的不變性約束預備專門的恢復過程和計劃。管理分區有三個步驟:
(點擊看大圖)
最後一步的目的是恢復一致性,以及補償在系統分區期間程序產生的錯誤。
圖 1 可見分區的演變過程。普通的操做都是順序的原子操做,所以分區老是在兩筆操做之間開始。一旦系統在操做間歇檢測到分區發生,檢測方一側即進入分區模式。若是確實發生了分區的狀況,那麼通常分區兩側都會進入到分區模式,不過單方面完成分區也是可能的。單方面分區要求在對方按須要通訊的時候,本方要麼能正確響應,要麼不須要通訊;總之操做不得破壞一致性。但無論怎麼樣,因爲檢測方可能有不一致的操做,它必須進入分區模式。採起了 quorum 決定機制的系統即爲單方面分區的例子。其中一方擁有「法定經過節點數」,所以能夠執行操做,而另外一方不能夠執行操做。支持離線操做的系統明顯地含有「分區模式」的概念,一些支持原子多播(atomic multicast)的系統也含有這個概念,如 Java 平臺的 JGroups。
當系統進入到分區模式,它有兩種可行的策略。其一是限制部分操做,所以會削弱可用性。其二是額外記錄一些有利於後面分區恢復的操做信息。系統可經過持續嘗試恢復通訊來察覺分區什麼時候結束。
決定限制哪些操做,主要取決於系統須要維持哪幾項不變性約束。在給定了不變性約束條件以後,設計師須要決定在分區模式下,是否堅持不觸動某項不變性約束,抑或以過後恢復爲前提去冒險觸犯它。例如,對於「表中鍵的唯一性」這項不變性約束,設計師通常都選擇在分區期間放寬要求,允許重複的鍵。重複的鍵很容易在恢復階段檢查出來,假如重複鍵能夠合併,那麼設計師不難恢復這項不變性約束。
對於分區期間必須維持的不變性約束,設計師應當禁止或改動可能觸犯該不變性約束的操做。(通常而言,咱們沒辦法知道操做是否真的會破壞不變性約束,由於沒法知道分區另外一側的狀態。)信用卡扣費等具備外部化特徵的事件常以這種方式工做。適合這種狀況的策略,是記錄下操做意圖,而後在分區恢復後再執行操做。這類事務每每從屬於一些更大的工做流,在工做流明確含有相似「訂單處理中」狀態的狀況下,將操做推遲到分區結束並沒有明顯的壞處。設計師以用戶不易察覺的方式犧牲了可用性。用戶只知道本身下了指令,系統稍後會執行。
說得更歸納一點,分區模式給用戶界面提出了一種根本性的挑戰,即如何傳達「任務正在進行還沒有完成」的信息。研究者已經從離線操做的角度對此問題進行了一些深刻的探索,離線操做能夠當作時間很長的一次分區。例如 Bayou 的日曆程序用顏色來區分顯示可能(暫時)不一致的條目13。工做流應用和帶離線模式的雲服務中也常見相似的提醒,前者的例子如交易中的電子郵件通知,後者的例子如 Google Docs。
在分區模式的討論中,咱們將關注點放在有明確意義的原子操做而非單純的讀寫,其中一個緣由是操做的抽象級別越高,對不變性約束的影響一般就越容易分析清楚。大致來講,設計師要創建一張全部操做與全部不變性約束的叉乘表格(cross product),觀察並肯定其中每一處操做可能與不變性約束相沖突的地方。對於這些衝突狀況,設計師必須決定是否禁止、推遲或修改相應的操做。在實踐中,這類決定還受到分區前狀態和 / 或環境參數的影響。例若有的系統爲特定的數據設立了主節點,那麼通常容許主節點執行操做,不容許其餘節點操做。
對分區兩側跟蹤操做歷史的最佳方式是使用版本向量,版本向量能夠反映操做間的因果依賴關係。向量的元素是(節點, 邏輯時間)數值對,分別對應一個更新了對象的節點和它最後更新的時間。對於同一對象的兩個給定的版本 A 和 B,當全部結點的版本向量一致有 A 的時間大於或等於 B 的時間,且至少有一個節點的版本向量有 A 的時間較大,則 A 新於 B。
若是不可能對版本向量排序,那麼更新操做是併發的,並且有可能出現不一致的狀況。只要知道分區兩側版本向量的沿革。系統不難判斷哪些操做的執行順序是肯定的,哪些操做是併發的。最近的研究成果證實14,當設計師選擇可用性優先,通常最多隻能將一致性收緊到這樣的程度。
到了某個時刻,通訊恢復,分區結束。因爲每一側在分區期間都是可用的,其狀態仍繼續向前進展,可是分區會推遲某些操做並侵犯一些不變性約束。分區結束的時刻,系統知道分區兩側的當前狀態和歷史記錄,由於它在分區模式下記錄了詳盡的日誌。當前狀態不如歷史記錄有價值,由於經過歷史記錄,系統能夠判斷哪些操做違反了不變性約束,產生了何種外在的後果(如發送了響應給用戶)。在分區恢復過程當中,設計師必須解決兩個問題:
一般狀況,矯正當前狀態最簡單的解決方法是回退到分區開始時的狀態,以特定方式推動分區兩側的一系列操做,並在過程當中一直保持一致的狀態。Bayou 就是這個實現機制,它會回滾數據庫到正確的時刻並按無歧義的、肯定性的順序從新執行全部的操做,最終使全部的節點達到相同的狀態15。一樣地,併發版本控制系統 CVS 在合併分支的時候,也是從從一個共享的狀態一致點開始,逐步將更新合併上去。。
大部分系統都存在不能自動合併的衝突。好比,CVS 時不時有些衝突須要手動介入,帶離線模式的 wiki 系統老是把衝突留在產生的文檔裏給用戶處理16。
相反,有些系統用了限制操做的辦法來保證衝突總能合併。一個例子就是 Google Docs 將其文本編輯操做17精簡爲應用樣式、添加文本和刪除文本。所以,雖然總的來講衝突問題不可解,但現實中設計師能夠選擇在分區期間限制使用部分操做,以便系統在恢復的時候可以自動合併狀態。若是要實施這種策略,推遲有風險的操做是相對簡單的實現方式。
還有一種辦法是讓操做能夠交換順序,這種辦法最接近於造成一種解決自動狀態合併問題的通用框架。此類系統將線性合併各日誌並重排操做的順序,而後執行。操做知足交換率,意味着操做有可能從新排列成一種全局一致的最佳順序。不幸的是,只容許知足交換率的操做這個想法實現起來沒那麼容易。好比加法操做能夠交換順序,可是加入了越界檢查的加法就不行了。
Marc Shapiro 及其 INRIA 同事最近的工做18,19對於可交換順序的操做在狀態合併方面的應用起了很大的促進做用。該團隊提出一種從理論上證實能夠保證分區後合併的數據類型,稱爲可交換多副本數據類型(commutative replicated data types,CRDTs)。他們介紹瞭如何使用此類數據結構來
用後一種方法合併狀態會彙總分區兩邊的最大集合。這種方法是對亞馬遜購物車合併算法20的形式化總結和改良,合併後的數據是兩邊購物車的並集,而並運算是一種單調的集合運算。這種策略的壞處是刪掉的購物車商品有可能再次出現。
其實 CRDTs 徹底能夠實現同時支持增、刪操做的分區耐受集合。此方法的本質是維護兩個集合:一個放增長的項目,一個放刪除的項目,兩集合之差即爲真正的集合成員。增集合、刪集合分別合併起來都不困難,於是增刪集合之差合併起來也不困難。在某個時間點上,系統能夠從兩個集合中清理掉刪除的數據項。假如按照通常的設計,像這種清理操做僅在系統沒分區的時候纔可行,屬於設計師必須在分區期間禁止或推遲的特定操做,可是 CRDTs 的清理操做並不會對可用性產生外在的影響。所以經過 CRDTs 來實現狀態,設計師既保證了可用性,又保證了分區後系統自動合併狀態。
比計算分區後狀態更難解決的問題是如何彌補分區期間形成的錯誤。跟蹤和限制分區模式下的操做,這兩種措施足以使設計師確知哪些不變性約束可能被違反,而後分別爲它們制定恢復策略。通常系統在分區恢復期間檢查違反狀況,修復工做也必須在這段時間內完成。
恢復不變性約束的方法有不少,粗陋一點的辦法如「最後寫入者勝」(所以會忽略部分更新),聰明一點的辦法如合併操做和人爲跟進事態(human escalation)。人爲跟進事態的例子如飛機航班「超售」的情形:能夠把乘客登機看做是對以前售票狀況的分區恢復,必須恢復「座位數很多於乘客數」這項不變性約束。那麼當乘客太多的時候,有些乘客將失去座位,客服最好能設法補償他們。
航班的例子揭示了一個外在錯誤(externalized mistake):假如航空公司沒說過乘客必定有座位,這個問題會好解決得多。所以咱們看到推遲有風險的操做的又一個理由——到了分區恢復的時候,咱們才知道真實的狀況。矯正此類錯誤的核心概念是「補償(compensation)」;設計師必須設立補償操做,除了恢復不變性約束,還要糾正外在錯誤。
技術上 CRDTs 只容許局部可驗證的不變性約束,因此沒有補償的必要,雖然這種限制下降了 CRDTs 方法自己的能力。用了 CRDTs 來處理狀態合併的設計方案能夠容許暫時違反全局性的不變量約束,分區結束後才合併狀態,以及履行必要的補償。
恢復外在錯誤一般要求知道一些有關外在輸出的歷史信息。以「喝醉酒打電話」爲例,一位老兄不記得本身昨晚喝高了的時候打過幾個電話,雖然他次日白天恢復了正常狀態,但通話日誌上的記錄都還在,其中有些通話極可能是錯誤的。撥出的電話就是這位老兄的狀態(喝高了)的外在影響。而因爲這位老兄不記得打過什麼電話,也就很難補償其中可能形成的麻煩。
又以機器爲例,電腦可能在分區期間把一份訂單執行了兩次。若是系統能區分兩份同樣的訂單是有意的仍是重複了,它就能取消掉一份重複的訂單。若是此次錯誤產生了外在影響,補償策略能夠是自動生成一封電子郵件,向顧客解釋系統意外將訂單執行了兩次,如今錯誤已經被糾正,附上一張優惠券下次能夠用。假如沒有完善的歷史記錄,就只好靠顧客親自去發現錯誤了。
曾經有人正式研究過將補償性事務做爲處理長壽命事務(long-lived transactions)的一種手段21,22。長時間運行的事務會面臨另外一種形態的分區決策:是長時間持有鎖來保證一致性比較好呢?仍是及早釋放鎖向其餘事務暴露未提交的數據,提升併發能力比較好呢?好比在單筆事務中更新全部的員工記錄就是一個典型例子。按照通常的方式串行化這筆事務,將致使全部的記錄都被鎖定,阻止併發。而補償性事務採起另外一種方式,它將大事務拆成多個分別提交的子事務。若是要停止大事務,系統必須發起一筆新的、起糾正做用的事務,逐一撤銷全部已經提交的子事務,這筆新事務就是所謂的補償性事務。
總的來講,補償性事務的目的是避免停止其餘用了未正確提交數據的事務(即不容許級聯取消)。這種方案不依賴串行化或隔離的手段來保障正確性,其正確性取決於事務序列對狀態和輸出所產生的淨影響。那麼,通過補償,數據庫的狀態到底是不是至關於那些子事務根本沒執行過同樣呢?考慮等價必須連外在行爲也包括在內;舉個例子,把重複扣取的交易款退還給顧客,很難說成等於一開始就沒多收顧客的錢,但從結果上看勉強算扯平了。分區恢復也延續一樣的思路。雖然服務不必定總能直接撤銷其錯誤,但起碼認可錯誤並作出新的補償行爲。怎樣在分區恢復中運用這種思路效果最好,這個問題沒有固定的答案。「自動櫃員機上的補償問題」小節以一個很小的應用領域爲例點出了一些思考方向。
當系統中存在分區,系統設計師不該該盲目地犧牲一致性或可用性。運用以上討論的方法,設計師經過細緻地管理分區期間的不變性約束,兩方面的性質均可以取得最佳的表現。隨着版本向量和 CRDTs 等比較新的技術逐漸被歸入一些簡化其用法的框架,這方面的優化手段會獲得比較廣泛的應用。但引入 CAP 實踐畢竟不像引入 ACID 事務那麼簡單,實施的時候須要對過去的策略進行全面的考慮,最佳的實施方案極大地依賴於具體服務的不變性約束和操做細節。
以自動櫃員機(ATM)的設計來講,強一致性看似符合邏輯的選擇,但現實狀況是可用性遠比一致性重要。理由很簡單:高可用性意味着高收入。無論怎麼樣,討論如何補償分區期間被破壞的不變性約束,ATM 的設計很適合做爲例子。
ATM 的基本操做是存款、取款、查看餘額。關鍵的不變性約束是餘額應大於或等於零。由於只有取款操做會觸犯這項不變性約束,也就只有取款操做將受到特別對待,其餘兩種操做隨時均可以執行。
ATM 系統設計師能夠選擇在分區期間禁止取款操做,由於在那段時間裏沒辦法知道真實的餘額,固然這樣會損害可用性。現代 ATM 的作法正相反,在 stand-in 模式下(即分區模式),ATM 限制淨取款額不得高於 k,好比 k 爲 $200。低於限額的時候,取款徹底正常;當超過限額的時候,系統拒絕取款操做。這樣,ATM 成功將可用性限制在一個合理的水平上,既容許取款操做,又限制了風險。
分區結束的時候,必須有一些措施來恢復一致性和補償分區期間系統所形成的錯誤。狀態的恢復比較簡單,由於操做都是符合交換率的,補償就要分幾種狀況去考慮。最後的餘額低於零違反了不變性約束。因爲 ATM 已經把錢吐出去了,錯誤成了外部實在。銀行的補償辦法是收取透支費並期望顧客償還。由於風險已經受到限制,問題並不嚴重。還有一種狀況是分區期間的某一刻餘額已經小於零(但 ATM 不知道),此時一筆存款從新將餘額變爲正的。銀行能夠追溯產生透支費,也能夠由於顧客已經繳付而忽略該違反狀況。
總而言之,由於通訊延遲的存在,銀行系統不依靠一致性來保證正確性,而更多地依靠審計和補償。「空頭支票詐騙」也是相似的例子,顧客趕在多家分行對帳以前分別取出錢來而後逃跑。透支的錯誤事後纔會被發現,對錯誤的補償也許體現爲法律行動的形式。
感謝 Mike Dahlin、Hank Korth、Marc Shapiro、Justin Sheehy、Amin Vahdat、Ben Zhao 以及 IEEE Computer Society 的志願者們,感謝他們對本文的有益反饋。
Eric Brewer是 University of California, Berkeley 的計算機科學教授,在 Google 擔任基礎設施方面的 VP。他的研究興趣包括雲計算、可伸縮的服務器、傳感器網絡,還有適合發展中地區應用的技術。他還幫助創建了美國聯邦政府的門戶網站 USA.gov。Brewer 從 MIT 得到電子工程和計算機科學的博士學位。他是 National Academy of Engineering 的院士。聯繫方式:brewer@cs.berkeley.edu