什麼是 CAP 理論和 BASE 理論,看這一篇就夠了

楔子

前面我們介紹了 zookeeper,瞭解了它的基本使用。但對於任何一個分佈式系統而言,數據同步永遠都是重中之重。因爲一個集羣當中會有很多節點,那麼客戶端每次寫數據的時候,是隻向一個節點寫入,還是向所有節點寫入。

如果向所有節點寫入,假設節點個數爲 N,那麼客戶端的一次寫請求就會被放大 N 倍,因爲每個節點都要寫一遍,顯然這麼做是非常不明智的。因此我們應該讓客戶端只向一個節點寫入,然後該節點再將數據同步給集羣內的其它節點。

但這就產生了一個問題,如果某個節點的數據同步還沒有完成,就收到了客戶端的讀請求,那麼顯然會返回舊數據。如果想讓客戶端看到的一定是新數據,那麼就必須等到數據在所有節點之間都同步完成之後,才能讓客戶端訪問,而這又會造成集羣服務出現短暫的不可用。

因此面對這種情況,我們必須要做出取捨,至於如何取捨,CAP 理論會告訴我們答案。它對分佈式系統的特性進行了抽象,掌握了 CAP 理論,我們在面對分佈式系統的時候就可以做到心中有數。

CAP 理論

CAP 理論對分佈式系統的特性做了高度抽象,形成了三個指標:

以上這三個指標就稱之爲 CAP,我們來分別介紹。

一致性,即 CAP 中的 C

一致性說的是客戶端的每次讀操作,不管訪問哪個節點,讀到的都是同一份最新的數據(或者讀取失敗,說明節點之間還在同步數據)。不會出現讀不同節點,得到的數據不同這種情況。

所以一致性強調的不是數據完整,而是各節點間的數據一致。

爲了更好地理解一致性這個指標,我們舉一個鍵值對存儲(比如 Redis)的例子。假設當前有兩個節點,裏面存儲了一個鍵值對 X = 1。

緊接着,客戶端向節點 1 發送寫請求 SET X = 2。

如果節點 1 收到寫請求後,只將自身的 X 更新爲 2,然後返回成功給客戶端,那麼這個時候節點 2 的 X 還是 1,此時兩個節點的數據就是非一致的。

如果節點 1 收到寫請求後,不僅自身更新數據,還通過節點間的通訊,將更新操作發送給節點 2,等到自身和節點 2 的 X 都更新爲 2 之後,再返回成功給客戶端。那麼當客戶端完成寫請求後,兩個節點的數據就是一致的了。之後不管客戶端訪問哪個節點,讀取到的都是同一份最新數據。

一致性這個指標,描述的是分佈式系統非常重要的一個特性,強調的是數據的一致。也就是說,在客戶端看來,訪問集羣和訪問單機是等價的,因爲兩者在數據一致性上是一樣的。

但集羣畢竟不是單機,總會有網絡故障的時候,那麼當節點之間無法通信的時候該怎麼辦呢?比如節點 1 在將寫請求同步給節點 2 的時候,發生了網絡故障,這時候如果要保證一致性,也就是讓客戶端訪問任何一個節點都能看到相同的數據,那麼就應該拒絕服務(客戶端讀取失敗),等到數據同步完成之後再提供服務。否則客戶端就可能讀到舊數據,比如訪問節點 2 的時候,因爲網絡原因數據還沒有同步過來。

因此可以把一致性看成是分佈式系統對客戶端的一種承諾:不管訪問哪個節點,返回的都是絕對一致的數據,因爲數據不一致的時候會讀取失敗(拒絕提供服務)。所以再次強調,一致性強調的不是數據完整,而是各節點之間的數據絕對一致。

但有些服務並不追求數據的一致性,返回舊數據也是可以的。當面對這種場景時,再因爲節點間出現了通訊問題(會導致節點間的數據不一致)而拒絕提供服務,就有些不合適了。

這個時候我們就需要犧牲數據的一致性,每個節點使用本地數據來響應客戶端請求,保證服務可用。這就是我們要說的另外一個指標,可用性。

可用性,即 CAP 中的 A

可用性說的是任何來自客戶端的請求,不管訪問哪個節點,都能得到響應數據,但不保證是同一份最新數據。

因此可以把可用性看作是分佈式系統對客戶端的另一種承諾:儘量返回數據,不會不響應,但不保證每個節點返回的數據都是最新的。因此可用性這個指標強調的是服務可用,但不保證數據的絕對一致。

分區容錯性,即 CAP 中的 P

最後的分區容錯性說的是,當節點間出現任意數量的消息丟失或高延遲的時候,系統仍然可以繼續提供服務。也就是說,分佈式系統會告訴客戶端:不管我的內部出現什麼樣的數據同步問題,我會一直運行,提供服務。這個指標,強調的是集羣對分區故障的容錯能力。

比如當節點 1 和節點 2 通信出問題(發生網絡分區)的時候,如果系統仍能提供服務,那麼兩個節點是滿足分區容錯性的。而分佈式系統與單機系統不同,它涉及到多節點之間的通訊和交互,節點間的分區故障不可能完全避免,所以在分佈式系統中分區容錯性是必須要考慮的。

CAP 不可能三角

對於一個分佈式系統而言,一致性、可用性、分區容錯性 3 個指標不可兼得,只能在 3 個指標中選擇兩個。

我們知道只要有網絡交互就一定會有延遲和數據丟失,而這種狀況我們必須接受,還必須保證系統不能掛掉。所以就像上面提到的,節點間的分區故障是必然發生的。也就是說,分區容錯性(P)是前提,是必須要保證的,不能說某些節點之間無法正常通信(發生網絡分區)就導致整個集羣不可用。

現在就只剩下一致性(C)和可用性(A)可以選擇了:要麼選擇一致性,保證數據絕對一致;要麼選擇可用性,保證服務可用。如果選擇 C,那麼就是 CP 模型;如果選擇 A,那麼就是 AP 模型。

這裏需要再強調一點,有很多人對 CAP 理論有個誤解,認爲無論在什麼情況下,分佈式系統都只能在 C 和 A 中選擇 1 個。其實在不發生網絡分區的情況下,也就是分佈式系統正常運行時(這也是系統在絕大部分時候所處的狀態),C 和 A 是能夠同時保證的(如果節點之間的數據同步很快的話)。只有當發生分區故障的時候,也就是說需要 P 時,纔會在 C 和 A 之間做出選擇。

CAP 總結

以上就是 CAP 理論的具體內容,以及 CAP 理論的應用,總結如下:

1)CA 模型:

不支持分區容錯,只支持一致性和可用性,但這在分佈式系統中不存在。因爲不支持分區容錯性,也就意味着不允許分區異常,設備、網絡永遠處於理想的可用狀態,從而讓整個分佈式系統滿足一致性和可用性。

但分佈式系統是由衆多節點通過網絡通信連接構建的,設備故障、網絡異常是客觀存在的,而且分佈的節點越多,範圍越廣,出現故障和異常的概率也越大。因此對於分佈式系統而言,分區容錯性(P)是無法避免的,如果避免了 P,那麼只能把分佈式系統回退到單機單實例系統。就比如單機版關係型數據庫 MySQL,如果 MySQL 要考慮主備或集羣部署時,那麼它也必須考慮 P。

2)CP 模型:

因爲分區容錯客觀存在,所以放棄系統的可用性,換取一致性。採用 CP 模型的分佈式系統,一旦因爲消息丟失、延遲過高而發生了網絡分區,就會持續阻塞整個服務,直到分區問題解決,才恢復對外服務,這樣就可以保證數據的一致性。

選擇 CP 一般都是對數據一致性特別敏感,尤其是在支付交易領域,Hbase 等分佈式數據庫領域,都要優先保證數據的一致性,在出現網絡異常時,系統就會暫停服務處理。還有用來分發及訂閱元數據的 Zookeeper、Etcd 等等,也是優先保證 CP 的。

3)AP 模型:

由於分區容錯 P 客觀存在,所以放棄系統的數據一致性,換取可用性。在系統遇到分區異常時,某些節點之間無法通信,數據處於不一致的狀態。但爲了保證可用性,服務節點在收到用戶請求後會立即響應,因此只能返回各自新老不同的數據。

這種捨棄一致性,而保證系統在分區異常下的可用性,在互聯網系統中非常常見。比如微博多地部署,如果不同區域出現網絡中斷,區域內的用戶仍然能發微博、相互評論和點贊,但暫時無法看到其它區域用戶發佈的新微博和互動狀態。

還有類似 12306 這種火車購票系統,在節假日高峯期搶票時也會遇到這種情況,明明某車次有餘票,但真正點擊購買時,卻提示說沒有餘票。就是因爲票已經被搶光了,票的可選數量應該更新爲 0,但因併發過高導致當前訪問的節點還沒有來得及更新就提供服務了(和發生網絡分區是類似的,都是最新數據還沒有同步,就對外提供服務)。因此它返回的是更新之前的舊數據,但其實已經沒有票了。

所以相比 CP,採用 AP 模型的分佈式系統,更注重服務的高可用。用戶訪問系統的時候,都能得到響應數據,不會出現響應錯誤。但當出現分區故障、或者併發量過高導致數據來不及同步時,相同的讀操作,訪問不同的節點,得到的響應數據可能不一樣。典型應用有 Cassandra, DynamoDB, Redis 等 NoSQL。

因此 CAP 理論可以幫助我們思考如何在一致性和可用性之間進行妥協折中,設計出滿足場景特點的分佈式系統。

最後再提一點,在分佈式系統開發中,延遲是非常重要的一個指標。比如名字路由系統,通過延遲評估服務可用性,進行負載均衡和容災;再比如在 Raft 實現中,通過延遲評估領導者節點的服務可用性,以及決定是否發起領導者選舉;再比如類似 Redis 這種查詢量非常大的分佈式緩存,它的目的是能夠快速地返回結果,所以它是 AP 模型。

所以在分佈式系統的開發中,要能意識到延遲的重要性,能通過延遲來衡量服務的可用性。總之能否容忍短暫的延遲是關鍵。

BASE 理論

BASE 理論是 CAP 理論中的 AP 的延伸,所以它強調的是可用性,這個理論廣泛應用在大型互聯網的後臺當中。它的核心思想就是基本可用(Basically Available)和最終一致性(Eventually consistent)。

首先「基本可用」指的是,當分佈式系統在出現不可預知的故障時,允許損失部分功能的可用性,來保障核心功能的可用性。說白了就是服務降級,在服務器資源不夠、或者說壓力過大時,將一些非核心服務暫停,優先保證核心服務的運行。比如:

所以基本可用本質上是一種妥協,也就是在出現節點故障或系統過載的時候,通過犧牲非核心功能的可用性,保障核心功能的穩定運行。而手段也有很多,比如服務降級、體驗降級、流量削峯、延遲響應、接口限流、服務熔斷等等。

然後是最終一致性,它指的是系統中所有的數據副本在經過一段時間的同步後,最終能夠達到一致的狀態。也就是說在數據一致性上,存在一個短暫的延遲,幾乎所有的互聯網系統採用的都是最終一致性。比如 12306 買票,票明明賣光了,但還是顯示有餘票,說明此時數據不一致。但當你在真正購買的時候,又會提示你票賣光了,說明數據最終是一致的。

因此最終一致性應該不難理解,就是節點間的數據存在短暫的不一致,但經過一段時間後,最終會達到一致的狀態。所以 BASE 理論除了引入一個基本可用之外,它和 AP 模型本質上沒太大區別。

只有對數據有強一致性要求,才考慮 CP 模型或分佈式事務,比如:決定系統運行的敏感元數據,需要考慮採用強一致性;與錢有關的支付系統或金融系統的數據,需要考慮採用事務保證一致性。因此,儘管事務型的分佈式系統和強一致性的分佈式系統,使用起來很方便,不需要考慮太多,就像使用單機系統一樣。但是我們要知道,想在分佈式系統中實現強一致性,必然會影響可用性。

如果換個角度思考,我們可以將強一致性理解爲最終一致性的特例,也就是說可以把強一致性看作是不存在延遲的一致性。因此在實踐中我們也可以這樣思考:如果業務的某功能無法容忍一致性的延遲(比如分佈式鎖對應的數據),就需要強一致性;如果能容忍短暫的一致性的延遲(比如 APP 用戶的狀態數據),就可以考慮最終一致性。

所以我們之前介紹基於 Redis 實現分佈式鎖的時候,說過 Redis 在主從切換的時候會出問題,就是因爲分佈式鎖需要的是 CP 模型,而 Redis 是 AP 模型。

小結

BASE 理論是對 CAP 中一致性和可用性權衡的結果,它來源於對大規模互聯網分佈式系統實踐的總結,是基於 CAP 定理逐步演化而來的。它的核心思想是,如果不是必須的話,不推薦使用事務或強一致性,鼓勵可用性和性能優先,根據業務的場景特點,來實現非常彈性的基本可用,以及實現數據的最終一致性。

BASE 理論主張通過犧牲部分功能的可用性,實現整體的基本可用,也就是說通過服務降級的方式,努力保障極端情況下的系統可用性。

說到 BASE 理論,應該會有人想到 ACID 理論。ACID 是傳統數據庫常用的設計理念,追求強一致性模型;而 BASE 理論支持的是大型分佈式系統,通過犧牲強一致性獲得高可用性。BASE 理論在很大程度上,解決了事務型系統在性能、容錯、可用性等方面的痛點。此外 BASE 理論在 NoSQL 中也應用廣泛,是 NoSQL 系統設計的理論支撐。

對於任何集羣而言,不可預知的故障的最終後果,都是系統過載。如何設計過載保護,實現系統在過載時的基本可用,是開發和運營互聯網後臺的分佈式系統的重點。因此在開發實現分佈式系統,要充分考慮如何實現基本可用。

本文參考自:

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/GrTJKDToY6ThT_1zbG6Dfg