對于設計分布式系統的架構師來說, CAP 是必須掌握的理論。
CAP 定理( CAP theorem )又被稱作布魯爾定理( Brewer’s theorem),由加州大學伯克利分校(計算機領域神一樣的大學)的計算機科學家埃里克·布魯爾在 2000 年的 ACM PODC 上提出的一個猜想。2002 年,麻省理工學院(另一所計算機領域神一樣的大學〉的賽斯·吉爾伯特和南希 ·林奇發表了布魯爾猜想的證明 , 使之成為分布式計算領域公認的一個定理。
在我剛接觸大數據平臺的時候,第一次聽到了CAP的理論,然后公司的架構師說hadoop屬于CP系統(Consistency and Partition tolerance),不明覺厲。后來雖然我大致知道了CAP是什么意思,但也是一知半解,比如我并不清楚CAP的適用范圍是什么,系統、數據還是應用?
最近看到了李云華的一本書:《從零開始學架構》,再一次接觸到了CAP理論,這本書對于CAP的詮釋還是比較通俗易懂的,在這本書的幫助下,再加上有ChatGPT的加持,自己終于對CAP理論有了全新的認識,現在再去理解分布式架構就有一覽眾山小的感覺,在此把我的領悟分享給大家。
一、CAP理論的緣起
在分布式系統的設計重點需要解決兩個問題:橫向擴展和高可用性,橫向擴展是為了解決單點性能瓶頸問題,從而保證可用性,高可用是為了解決單點故障問題,進而保障部分節點故障時的可用性,由此可以看到,分布式系統的核心訴求就是可用性。這個可用性正是CAP中的A;用戶訪問系統時,可以在合理的時間內得到合理的響應。
為了保證可用性,一個分布式系統通常由多個節點組成,這些節點各自維護一份數據,但是不管用戶訪問哪個節點,原則上都應該讀取到相同的數據。為了達到這個效果,一個節點收到寫入請求更新自己的數據后,必須將數據同步到其他節點,以保證各個節點的數據一致性,這個一致性正是CAP中的C:用戶訪問系統時,可以讀取到最近寫入的數據。
分布式系統中,節點之間數據的同步是基于網絡的,由于網絡本身的不可靠性,極端情況下會出現網絡不可用,從而將網絡兩端的節點孤立開來,這就是網絡分區的現象。發生網絡分區時,系統中多個節點數據一定是不一致的,但可以選擇對用戶表現出一致性,代價是犧牲可用性,將未能同步到新數據的部分節點設置為不可用,當然也可以選擇可用性,此時系統中各個節點都是可用的,只是返回給用戶的數據不一致,這里的選擇,就是CAP中的P。
以上就是CAP理論的背景,大家可以知其所以然。
二、CAP定義的辨析
有比較才有鑒別,CAP理論的定義有很多版本,通過比對這些版本的差異,讓我對CAP有了更深入的理解,這里挑選了了2個典型版本:
版本1:
對于一個分布式計算系統,不可能同時滿足一致性( Consistence)、可用性(AvAIlability)、分區容錯性( Partition Tolerance)三個設計約束。
一致性(Consistence):所有節點在同一時刻都能看到相同的數據。
可用性(Availability):每個請求都能得到成功或失敗的響應。
分區容錯性( Partition Tolerance):盡管出現消息丟失或分區錯誤,但系統能夠繼續運行。
版本2:
在一個分布式系統 (指互相連接并共享數據的節點的集合)中,當涉及讀寫操作時,只能保證一致性( Consistence)、可用性( Availability)、分區容錯性( Partition Tolerance)三者中的兩個,另外一個必須被犧牲。
一致性(Consistence):對某個指定的客戶端來說,讀操作保證能夠返回最新的寫操作結果(A read is guaranteed to return the most recent write for a given client)。
可用性(Availability):非故障的節點在合理的時間內返回合理的響應 (不是錯誤和超時的響應)。
分區容錯性( Partition Tolerance):當出現網絡分區后,系統能夠繼續 “履行職責”。
我們來具體看看兩者的差異:
1、第二版強調了CAP 理論適用于哪種類型的分布式系統,第一,互相連接并共享數據的節點的集合,第二,關注的是對數據的讀寫操作,而不是分布式系統的所有功能。
2、在一致性上,第一版從節點 node 的角度描述,第二版從客戶端 client 的角度描述。第一版強調同一時刻擁有相同數據,第二版并沒有強調這點 。這就意味著實際上對于節點來說,可能同一時刻擁有不同數據,這和我們通常理解的一致性是有差異的。
3、在可用性上,第一版的“每個請求都能得到成功或失敗的響應”定義比較模糊,因為無論是否符合CAP理論,我們都可以說請求成功和失敗,因為超時也算失敗,錯誤也算失敗,異常也算失敗,結果不正確也算失敗;即使是成功的響應,也不一定是正確的。例如,本來應該返回100,但實際上返回了90,這就是成功的響應,但并沒有得到正確的結果。相比之下,第二版的解釋明確了不能超時,不能出錯,結果是合理的,注意沒有說“正確”的結果。例如,應該返回100但實際上返回了90,肯定是不正確的結果,但可以是一個合理的結果。
4、在分區容錯性上,第一版用的是“繼續運行”,第二版用的是“履行職責”。只要系統不宕機,我們都可以說系統在運行,返回錯誤和拒絕服務都是運行,而“履行職責”表明發揮正常作用,這點和可用性是一脈相承的,相比之下更加明確。
三、CAP潛在的約束
理論的優點在于清晰簡潔,易于理解,但缺點就是高度抽象化,省略了很多細節,導致在將理論應用到實踐時,由于各種復雜情況,可能出現誤解和偏差, CAP 理論也不例外。
1、CAP有適用范圍
正如定義中所說,CAP是有適用范圍的,亂套用CAP往往謬以千里。
這里以一個電商網站訂單模塊的下單、付款、扣減庫存操作為例說明。在超時的情況下,訂單數據和庫存數據狀態會不一致,這屬不屬于CAP理論的適用范圍呢?答案是不適用。
大家不要一看到不一致就認為適用CAP理論。前面已經講過,CAP的適用范圍只是針對共享、互聯的數據,訂單和庫存系統雖然是互聯的,但沒有共享的數據,超時情況下產生的不一致只能靠對賬和人工修復等方式解決。但如果針對的是各節點的庫存數據的一致性,則適用CAP理論,比如當用戶下單支付后,各節點的庫存數據需要立即更新,以保證所有查看該商品的用戶都能看到最新的庫存信息。
2、CAP理論中,P是必選項
雖然 CAP 理論定義是三個要素中只能取兩個,但放到分布式環境下來思考,我們會發現必須選擇 p (分區容忍)要素,因為網絡本身無法做到 100%可靠,有可能出故障,所以分區是一個必然的現象。如果我們選擇了 CA 而放棄了 P,那么當發生分區現象時,為了保證 C,系統需要禁止寫入,當有寫入請求時,系統返回 error (例如,當前系統不允許寫入),這又和 A 沖突了 ,因為 A 要求返回 no error 和 no timeout。因此,分布式系統理論上不可能選擇 CA 架構 ,只能選擇 CP 或 AP 架構。
3、CAP關注的粒度是數據, 而不是整個系統
CAP 理論的定義和解釋中,用的都是 system、 node 這類系統級的概念,這就給很多人造成了很大的誤導,認為我們在進行架構設計時,整個系統要么選擇 CP,要么選擇 AP。但在實際設計過程中,每個系統不可能只處理一種數據,而是包含多種類型的數據,有的數據必須選擇CP,有的數據必須選擇 AP。而如果我們做設計時,從整個系統的角度去選擇CP還是AP,就會發現顧此失彼,無論怎么做都是有問題的。
一個電商網站核心模塊包括會員,訂單,商品,支付,促銷管理等等,不同的模塊數據特點不同,因此適用不同的CAP架構。
會員模塊包括登錄,個人設置,個人訂單,購物車,收藏夾等功能,這些模塊只要保證AP,即對用戶的及時響應,數據短時間不一致不大會影響使用。訂單模塊的下單付款扣減庫存操作是整個系統的核心,CA都需要保證,在極端情況下犧牲P是可以的。商品模塊的商品上下架保證CP即可,因為后端操作可以允許一定的時延;促銷模塊短時間的數據不一致,結果就是優惠信息看不到,但是已有的優惠保證可用,所以保證AP也可以。
現在大部分電商網站對于支付是獨立的系統,C是必須要保證的,AP中A相對更重要,不能因為分區導致所有人都不能支付。
4、CAP是忽略網絡延遲的
這是一個非常隱含的假設,布魯爾在定義一致性時,并沒有將延遲考慮進去。也就是說,當事務提交時,數據能夠瞬間復制到所有節點。但實際情況下,從節點 A 復制數據到節點 B,總是需要花費一定時間的。這就意味著, CAP理論中的 C 在實踐中是不可能完美實現的,在數據復制的過程中,節點 A 和節點 B 的數據并不一致。對于嚴苛的業務場景。
例如和金錢相關的用戶余額、和搶購相關的商品庫存,技術上是無法做到分布式場景下完美的一致性的。而業務上必須要求一致性,因此單個用戶的余額、單個商品的庫存,理論上要求選擇 CP 而實際上 CP都做不到,只能選擇 CA。也就是說,只能單點寫入,其他節點做備份,無法做到分布式情況下多點寫入,這是符合實際情況的。
5、CAP的AP和CP不是絕對的
CAP理論告訴我們三者只能取兩個,需要犧牲另外一個,這里的犧牲有一定誤導作用,因為犧牲讓很多人理解成什么都不做,實際上,CAP理論的犧牲只是說分區過程無法保證C或者A,但不意味著什么都不做,因為在系統整個運行周期中,大部分時間都是正常的,發生分區現象的時間并不長,分區期間放棄C或A,并不意味著永遠放棄,可以在分區期間進行一些操作,比如記錄日志,從而讓分區故障解決后,系統能夠重新達到CA的狀態。
比如用戶賬號數據,假設選擇了CP,則分區發生后,節點1可以繼續注冊新用戶,節點2無法注冊新用戶,此時節點1可以記錄新增日志,當分區恢復后,節點2讀取節點1的日志就可以讓節點1和節點2同時滿足CA的狀態。
6、CAP的可用性與高可用有區別
HBASE、MongoDB屬于CP架構,Cassandra、CounchDB屬于AP系統,但我們能說后者比前者更高可用么?應該不是。CAP中的高可用,是指在某一次讀操作中,即使發現不一致,也要返回響應,即在合理時間返回合理響應。我們常說的高可用,是指部分實例掛了,能自動摘除,并由其它實例繼續提供服務,關鍵是冗余。
7、CAP的網絡分區有明確定義
網絡故障、節點應用出現問題導致超時,屬于網絡分區,節點宕機或硬件故障,則不屬于。因此如果有人說機器掛了怎么樣,這種情況不屬于CAP理論適用的范圍,CAP關注的是分區時的可用性和一致性,不是說保證整個集群不掛。
四、BASE是CAP的補償
為了解決CAP限制,出現了BASE理論,也就是基本可用(Basically Available)、軟狀態(Soft state)、最終一致性(Eventually Consistent)理論。BASE是對CAP中一致性和可用性權衡的結果,其通過犧牲強一致性來獲取高可用性。
1、基本可用(Basically Available):指的是分布式系統在出現故障的情況下,仍然可以提供服務,盡管這個服務可能不完全,或者有降級,這里的關鍵是部分和核心。
以電商網站為例,假設一個電商網站有訂單服務、商品瀏覽服務、評論服務、搜索服務等多個服務。如果因為某種原因(如網絡分區、硬件故障等)導致評論服務暫時不可用,但是其他服務,如訂單服務、商品瀏覽服務、搜索服務等仍然可用。此時,用戶仍然可以瀏覽商品、下訂單和搜索,只是不能進行評論。也就是說,整個系統仍然是"基本可用"的。
2、軟狀態(Soft state):允許系統中的數據存在一段時間的不一致。
例如,當你第一次訪問一個網站的時候,你的計算機會向DNS服務器查詢這個網站對應的IP地址,然后DNS服務器會返回IP地址,這個IP地址會被你的計算機或者你的網絡設備緩存起來,以備下次訪問的時候使用,以節省查詢時間。但是,如果在這個緩存期間,網站服務器更換了IP地址,那么你的計算機或者網絡設備緩存的DNS解析結果就會過期,這就是一個"軟狀態"。
3、最終一致性(Eventually Consistent):系統中的所有數據副本經過一定時間后,最終能夠達到一致的狀態。這里的關鍵詞是“一定時間”和“最終“,“一定時間”和數據的特性是強關聯的,不同的數據能夠容忍的不一致時間是不同的。
例如,在一個社交媒體網站上,當用戶發布一條新的狀態更新或者照片,他的朋友們可能不能立刻看到這個更新。這是因為這個新的狀態更新需要被復制到數據中心的各個節點,這個過程可能需要一些時間。在這個場景中,用戶通常能接受這種延遲,因此對于最終一致性的時間容忍度相對較大。但是,當用戶在電商網站購買商品時,庫存數據的對最終一致性的時間容忍度相對較小,因此,電商網站需要盡快地更新和傳播庫存變化,以確保所有的用戶都能看到準確的庫存信息。
前面講過,完美的CP是不存在的,即使是幾毫秒的數據復制延遲,在這幾毫秒的時間間隔內,系統是不符合CP要求的,因此,CAP中的CP方案,實際上也是實現了最終一致性。AP方案中犧牲一致性,則只是在分區期間,而不是永遠放棄一致性,在分區故障恢復后,系統應該達到最終一致性,因此,BASE是CAP理論的延伸。
五、CAP和ACID的辨析
CAP中的一致性有時會和ACID的一致性產生概念混淆,那么ACID的C到底跟CAP的C有什么區別呢?
ACID是指數據庫事務正確執行的四個基本特性:原子性(Atomicity)、一致性(Consistency)、隔離性(Isolation)和持久性(Durability)。在這里,一致性的定義是:數據庫的狀態應該從一個一致的狀態轉移到另一個一致的狀態,即事務執行前后都保持業務規則的一致性,如完整性約束等。
兩者的主要區別在于其應用的范圍和關注點不同:
1、CAP中的一致性更多的是從分布式系統的角度考慮,關注的是分布式節點間的數據一致性問題,即所有節點看到的數據是否實時一致。
2、ACID中的一致性是從單個數據庫系統的角度考慮,主要關注的是數據庫在進行事務操作后,數據是否滿足預定義的業務規則和約束,保證數據的正確性。
舉個具體的例子來說明:
假設你有一個銀行應用程序,這個程序有一個簡單的業務規則:客戶的賬戶余額不能小于0。在這個場景下,如果一個客戶想要從他們的賬戶中提取500美元,系統會首先檢查賬戶余額是否滿足這個要求。如果賬戶余額是600美元,那么提取操作是可以進行的,并且完成后,賬戶余額將是100美元。這是一個一致性狀態,因為它滿足了業務規則。
現在,假設在這個事務中,提取操作已經完成,但是在更新余額時,系統突然崩潰了。在這種情況下,系統應該能夠恢復并且完成余額更新,或者它應該完全忽略這個事務,就好像它從未發生過一樣。在任何情況下,客戶的賬戶余額都不應該被錯誤地減少,因為那將違反業務規則。
所以,ACID的一致性是關于在事務開始和結束時滿足特定業務規則和約束的,無論事務過程中是否發生了錯誤或者系統崩潰,數據庫都應始終保持一致性狀態。
六、CAP的應用案例
1、ZooKeeper
ZooKeeper(ZK)是一個分布式的、開放源碼的分布式應用程序協調服務,由雅虎公司開發,并是Apache項目的一部分。ZooKeeper是一種典型的CP(Consistency/一致性, Partition tolerance/分區容錯性)系統。
ZooKeeper的設計就是這樣的。當網絡發生分裂,只有數量大于半數的節點(稱為“過半機制”)可以繼續提供服務,能夠保證數據的一致性。數量少于半數的節點會拒絕服務,以此來保證整個系統的一致性。這意味著在部分節點不可用或者網絡分區的情況下,ZooKeeper可能無法保證所有客戶端的可用性。
ZooKeeper主要被用于管理和協調分布式系統中的狀態信息,如配置信息,狀態同步,命名服務和分布式鎖等。數據的一致性在這些用途中是非常關鍵的,因此ZooKeeper選擇了犧牲一部分可用性以保證一致性。
2、Kafka
CAP定理告訴我們,在網絡分區的情況下,不可能同時保證一致性(Consistency)和可用(Availability) 。但這并不意味著我們不能在一致性和可用性之間找到適合自己的平衡點。
在Kafka中,例如,我們可以通過設置不同的"acks"(acknowledgements)和"min.insync.replicas"參數來權衡一致性和可用性。如果我們設置"acks"為"all"或者"-1",并且將"min.insync.replicas"設置為一個大于1的數,那么我們就可以提高數據的一致性,但可能會降低系統的可用性,因為每次寫操作都需要等待所有的副本都確認。
反之,如果我們設置"acks"為"1",那么寫操作只需要等待一個副本的確認,這就提高了系統的可用性,但可能降低數據的一致性,因為其他的副本可能還沒有完成數據的更新。
3、HBase
HBase的設計目標是提供高速的讀寫訪問大量數據,并保證強一致性。在HBase中,數據是自動分片的,每個分片(稱為region)由一個RegionServer來服務,每個行鍵只由一個RegionServer來服務,所以對單個行的讀寫都是強一致的。
當網絡分區發生時,HBase通常會選擇犧牲部分可用性以保持一致性和分區容錯性。比如說,如果一個RegionServer出現問題,HBase的主節點(HMaster)會將它上面的regions重新分配給其他健康的RegionServers,這個過程中涉及的regions會在短時間內不可用,因此,HBASE屬于CP系統。
4、redis
Redis是一個開源的鍵值存儲系統,它通常用于作為數據庫、緩存和消息代理。Redis單實例是一個基于內存的數據結構存儲,并提供持久化機制。對于單實例的Redis,CAP理論并不適用,因為它并不是一個分布式系統。
在Redis的主從復制模式中,如果主節點故障,系統需要手動干預才能恢復寫服務(即升級一個從節點為新的主節點)。這意味著它不能自動處理網絡分區,因此在嚴格意義上,它也不能被歸類為CAP理論中的任何一類。
對于Redis Cluster,它是一個分布式的Redis解決方案,能夠在一定程度上處理網絡分區的問題。在出現網絡分區時,Redis Cluster會停止對分區節點的操作以保持數據的一致性,所以它更傾向于CP系統。
5、Cassandra
Apache Cassandra是一個開源的分布式NoSQL數據庫,它是為了滿足高可用性和擴展性的需求而設計的。
Cassandra使用了一種名為最終一致性(Eventual Consistency)的模型。在這個模型中,系統不保證在任何時刻所有的副本都是一致的,但保證在沒有新的更新操作的情況下,所有的副本最終會達到一致的狀態。
具體來說,當一個寫操作發生時,Cassandra只需要一部分(可以配置)副本確認就可以認為寫操作成功了。這樣可以提高系統的可用性和寫入性能。然而,這也意味著在某些情況下,讀操作可能會返回過時的數據,因為不一致的副本可能還沒有被更新。
所以,Cassandra是一個典型的AP系統。然而,通過調整配置,例如設置更嚴格的一致性級別,可以使Cassandra在一定程度上提供更強的一致性保證。
希望對你有所啟示。