CAP理論十二年回顧:"規則"變了

CAP理論斷言任何基於網絡的數據共享系統,最多隻能滿足數據一致性、可用性、分區容忍性三要素中的兩個要素。但是通過顯式處理分區情形,系統設計師可以做到優化數據一致性和可用性,進而取得三者之間的平衡。

自打引入CAP理論的十幾年裏,設計師和研究者已經以它爲理論基礎探索了各式各樣新穎的分佈式系統,甚至到了濫用的程度。NoSQL運動也將CAP理論當作對抗傳統關係型數據庫的依據。

CAP理論主張任何基於網絡的數據共享系統,都最多隻能擁有以下三條中的兩條:

  • 數據一致性(C),等同於所有節點訪問同一份最新的數據副本;
  • 對數據更新具備高可用性(A);
  • 能容忍網絡分區(P)。

CAP理論的表述很好地服務了它的目的,即開闊設計師的思路,在多樣化的取捨方案下設計出多樣化的系統。在過去的十幾年裏確實涌現了不計其數的新系統,也隨之在數據一致性和可用性的相對關係上產生了相當多的爭論。“三選二”的公式一直存在着誤導性,它會過分簡單化各性質之間的相互關係。現在我們有必要辨析其中的細節。實際上只有“在分區存在的前提下呈現完美的數據一致性和可用性”這種很少見的情況是CAP理論不允許出現的。

雖然設計師仍然需要在分區的前提下對數據一致性和可用性做取捨,但具體如何處理分區和恢復一致性,這裏面有不計其數的變通方案和靈活度。當代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、CAP

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理論的經典解釋,是忽略網絡延遲的,但在實際中延遲和分區緊密相關。CAP從理論變爲現實的場景發生在操作的間歇,系統需要在這段時間內做出關於分區的一個重要決定:

  • 取消操作因而降低系統的可用性,還是
  • 繼續操作,以冒險損失系統一致性爲代價

依靠多次嘗試通信的方法來達到一致性,比如Paxos算法或者兩階段事務提交,僅僅是推遲了決策的時間。系統終究要做一個決定;無限期地嘗試下去,本身就是選擇一致性犧牲可用性的表現。

因此以實際效果而言,分區相當於對通信的時限要求。系統如果不能在時限內達成數據一致性,就意味着發生了分區的情況,必須就當前操作在C和A之間做出選擇。這就從延遲的角度抓住了設計的核心問題:分區兩側是否在無通信的情況下繼續其操作?

從這個實用的觀察角度出發可以導出若干重要的推論。第一,分區並不是全體節點的一致見解,因爲有些節點檢測到了分區,有些可能沒有。第二,檢測到分區的節點即進入分區模式——這是優化C和A的核心環節。

最後,這個觀察角度還意味着設計師可以根據期望中的響應時間,有意識地設置時限;時限設得越短,系統進入分區模式越頻繁,其中有些時候並不一定真的發生了分區的情況,可能只是網絡變慢而已。

有時候在跨區域的系統,放棄強一致性來避免保持數據一致所帶來的高延遲是非常有意義的。Yahoo的PNUTS系統因爲以異步的方式維護遠程副本而帶來數據一致性的問題5。但好處是主副本就放在本地,減小操作的等待時間。這個策略在實際中很實用,因爲一般來講,用戶數據大都會根據用戶的(日常)地理位置做分區。最理想的狀況是每一位用戶都在他的數據主副本附近。

Facebook使用了相反的策略6:主副本被固定在一個地方,因此遠程用戶一般訪問到的是離他較近,但可能已經過時的數據副本。不過當用戶更新其頁面的時候是直接對主副本進行更新,而且該用戶的所有讀操作也被短暫轉向從主副本讀取,儘管這樣延遲會比較高。20秒後,該用戶的流量被重新切換回離他較近的副本,此時副本應該已經同步好了剛纔的更新。

CAP之惑

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)。他們介紹瞭如何使用此類數據結構來

  • 保證分區期間進行的所有操作都是可交換順序的,或者
  • 用“格(lattice)”的數學概念來表示數據,並保證相對於“格”來說,分區期間的所有操作都是單調遞增的。

用後一種方法合併狀態會彙總分區兩邊的最大集合。這種方法是對亞馬遜購物車合併算法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不知道),此時一筆存款重新將餘額變爲正的。銀行可以追溯產生透支費,也可以因爲顧客已經繳付而忽略該違反情況。

總而言之,因爲通信延遲的存在,銀行系統不依靠一致性來保證正確性,而更多地依靠審計和補償。“空頭支票詐騙”也是類似的例子,顧客趕在多家分行對賬之前分別取出錢來然後逃跑。透支的錯誤過後纔會被發現,對錯誤的補償也許體現爲法律行動的形式。


轉自:http://www.infoq.com/cn/articles/cap-twelve-years-later-how-the-rules-have-changed

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章