CAP理論與MongoDB一致性、可用性的一些思考

 

   

  大約在五六年前,第一次接觸到了當時已是hot topic的NoSql。不過那個時候學的用的都是mysql,Nosql對於我而言仍是新事物,並無真正使用,只是不明覺厲。可是印象深入的是這麼一張圖片(後來google到圖片來自這裏):html

  

    這張圖片是講數據庫(包括傳統的關係型數據庫和NOSQL)與CAP理論的關係。因爲並NoSql並無實踐經驗,也沒有去深刻了解,對於CAP理論更是隻知其一;不知其二。所以,爲何某一款數據庫被劃分到哪個陣營,並不清楚。
    工做以後對MongoDB使用得比較多,有了必定的瞭解,前段時間又看到了這張圖,因而想搞清楚,MongoDB是否是真的屬於CP陣營,又是爲何?懷疑這個問題的初衷是由於,MongoDB的經典(官方推薦)部署架構中都會使用replica set,而replica set經過冗餘和自動failover提供高可用性(Availability),那麼爲何上圖中說MongoDB犧牲了Avalability呢?而我在MongoDB的官方文檔中搜索「CAP」,並無搜索到任何內容。因而我想本身搞清楚這個疑問,給本身一個答案。
  本文先闡明什麼是CAP理論,以及關於CAP理論的一些文章,而後討論MongoDB在一致性與可用性之間的折中與權衡。
 

CAP理論

  對CAP理論我只知道這三個單詞的意思,其解釋也是來自網上的一些文章,並不必定準確。因此首先得追根溯源,搞清楚這個理論的起源和準確的解釋。我以爲最好的開始就是 wikipedia,從上面能夠看到比較準確的介紹,更爲重要的是能夠看到不少有用的連接,好比CAP理論的出處,發展演變過程。
 
  CAP理論是說對於分佈式數據存儲,最多隻能同時知足一致性(C,Consistency)、可用性(A, Availability)、分區容錯性(P,Partition Tolerance)中的二者。
  一致性,是指對於每一次讀操做,要麼都可以讀到最新寫入的數據,要麼錯誤。
  可用性,是指對於每一次請求,都可以獲得一個及時的、非錯的響應,可是不保證請求的結果是基於最新寫入的數據。
  分區容錯性,是指因爲節點之間的網絡問題,即便一些消息對包或者延遲,整個系統能繼續提供服務(提供一致性或者可用性)。
 
  一致性、可用性都是使用很是寬泛的術語,在不一樣的語義環境下具體所指是不同的,好比在 cap-twelve-years-later-how-the-rules-have-changed一文中Brewer就指出「CAP中的一致性與ACID中的一致性並非同一個問題」,所以後文中除非特別說明,所提到的一致性、可用性都是指在CAP理論中的定義。只有明確了你們都是在一樣的上下文環境,討論纔有意義。
    
  對於分佈式系統,網絡分區(network partition)這種狀況是難以免的,節點間的數據複製必定存在延遲,若是須要保證一致性(對全部讀請求都可以讀到最新寫入的數據),那麼勢必在必定時間內是不可用的(不能讀取),即犧牲了可用性,反之亦然。
  按照維基百科上的描述,CAP之間的相互關係大約起源於1998年,Brewer在2000年的PODC( Symposium on Principles of Distributed Computing)上展現了CAP猜測 [3],在2002年,由另外兩名科學家Seth Gilbert、Nancy Lynch證實了Brewer的猜測,從而從猜測變成了定理 [4]
  

CAP理論起源

  在 Towards Robust Distributed Systems 中,CAP理論的提出者Brewer指出:在分佈式系統中,計算是相對容易的,真正困難的是狀態的維護。那麼對於分佈式存儲或者說數據共享系統,數據的一致性保證也是比較困難的。對於傳統的關係型數據庫,優先考慮的是一致性而不是可用性,所以提出了事務的ACID特性。而對於許多分佈式存儲系統,則是更看重可用性而不是一致性,一致性經過BASE(Basically Available, Soft state, Eventual consistency)來保證。下面這張圖展現了ACID與BASE的區別:
  

  簡而言之:BASE經過最終一致性來儘可能保證服務的可用性。注意圖中最後一句話「But I think it‘s a spectrum」,就是說ACID BASE只是一個度的問題,並非對立的兩個極端。node

  

  2002年,在Brewer's conjecture and the feasibility of consistent, available, partition-tolerant web services中,兩位做者經過異步網絡模型論證了CAP猜測,從而將Brewer的猜測升級成了理論(theorem)。但實話說,我也沒有把文章讀得很明白。mysql

  

  2009年的這篇文章brewers-cap-theorem,做者給出了一個比較簡單的證實:web

  

  如上圖所示,N1,N2兩個節點存儲同一份數據V,當前的狀態是V0。在節點N1上運行的是安全可靠的寫算法A,在節點N2運行的是一樣可靠的讀算法B,即N1節點負責寫操做,N2節點負責讀操做。N1節點寫入的數據也會自動向N2同步,同步的消息稱之爲M。若是N1,N2之間出現分區,那麼就無法保證消息M在必定的時間內到達N2。算法

  從事務的角度來看這各問題sql

  

   α這個事務由操做α1, α2組成,其中α1是寫數據,α2是讀數據。若是是單點,那麼很容易保證α2能讀到α1寫入的數據。若是是分佈式的狀況的狀況,除非能控制 α2的發生時間,不然沒法保證 α2能讀到 α1寫入的數據,但任何的控制(好比阻塞,數據集中化等)要麼破壞了分區容錯性,要麼損失了可用性。mongodb

  另外,這邊文章指出不少狀況下 availability比consistency重要,好比對於facebook google這樣的網站,短暫的不可用就會帶來巨大的損失。數據庫

  

  2010年的這篇文章brewers-cap-theorem-on-distributed-systems/,用了三個例子來闡述CAP,分別是example1:單點的mysql;example2:兩個mysql,但不一樣的mysql存儲不一樣的數據子集(相似sharding);example3:兩個mysql,對A的一個insert操做,須要在B上執行成功才認爲操做完成(相似複製集)。做者認爲在example1和example2上 都能保證強一致性,但不能保證可用性;在example3這個例子,因爲分區(partition)的存在,就須要在一致性與可用性之間權衡。apache

  於我看來,討論CAP理論最好是在「分佈式存儲系統」這個大前提下,可用性也不是說總體服務的可用性,而是分佈式系統中某個子節點的可用性。所以感受上文的例子並非很恰當。緩存

CAP理論發展

    到了2012年,CAP理論的發明人 Brewer就CAP理論再次撰文 《CAP Twelve Years Later: How the "Rules" Have Changed》,這篇文章比較長,但思路清晰,高屋建瓴,很是值得一讀。網上也有對用的中文譯文 《CAP理論十二年回顧:"規則"變了》,翻譯還不錯。
  文章中,最主要的觀點是CAP理論並非說三者不需選擇二者。首先,雖然只要是分佈式系統,就可能存在分區,但分區出現的機率是很小的(不然就須要去優化網絡或者硬件),CAP在大多數時候容許完美的C和A;只有在分區存在的時間段內,才須要在C與A之間權衡。其次,一致性和可用性都是一個度的問題,不是0或者1的問題,可用性能夠在0%到100%之間連續變化,一致性分爲不少級別(好比在casandra,能夠設置 consistency level)。所以,當代CAP實踐的目標應該是針對具體的應用,在合理範圍內最大化數據一致性和可用性的效力。
 
  文章中還指出,分區是一個相對的概念,當超過了預約的通訊時限,即系統若是不能在時限內達成數據一致性,就意味着發生了分區的狀況,必須就當前操做在C和A之間作出選擇。
  從收入目標以及合約規定來說,系統 可用性是首要目標,於是咱們常規會使用緩存或者過後校覈更新日誌來優化系統的可用性。所以,當設計師選擇可用性的時候,由於須要在分區結束後恢復被破壞的不變性約。
  實踐中,大部分團體認爲(位於單一地點的)數據中心內部是沒有分區的,所以在單一數據中心以內能夠選擇CA;CAP理論出現以前,系統都默認這樣的設計思路,包括傳統數據庫在內。
  分區期間,獨立且能自我保證一致性的節點子集合能夠繼續執行操做,只是沒法保證全局範圍的不變性約束不受破壞。 數據分片(sharding)就是這樣的例子,設計師預先將數據劃分到不一樣的分區節點,分區期間單個數據分片多半能夠繼續操做。相反,若是被分區的是內在關係密切的狀態,或者有某些全局性的不變性約束非保持不可,那麼最好的狀況是隻有分區一側能夠進行操做,最壞狀況是操做徹底不能進行。

  上面摘錄中下選線部分跟MongoDB的sharding狀況就很類似,MongoDB的sharded cluste模式下,shard之間在正常狀況下,是無需相互通訊的。

 

  在13年的文章中《better-explaining-cap-theorem》,做者指出「it is really just A vs C!」,由於

  (1)可用性通常是在不一樣的機器之間經過數據的複製來實現

  (2)一致性須要在容許讀操做之間同時更新幾個節點

  (3)temporary partion,即幾點之間的通訊延遲是可能發生了,此時就須要在A 和 C之間權衡。但只有在發生分區的時候才須要考慮權衡。

  在分佈式系統中,網絡分區必定會發生,所以「it is really just A vs C!」

 

MongoDB與CAP        

        在《經過一步步建立sharded cluster來認識MongoDB》一文中,對MongoDB的特性作了一些介紹,包括高性能、高可用、可擴展(水平伸縮),其中,MongoDB的高可用性依賴於replica set的複製與自動failover。對MongoDB數據庫的使用有三種模式:standalone,replica set, shareded cluster,在前文中詳細介紹了shared cluster的搭建過程。

  standalone就是單個mongod,應用程序直接鏈接到這個Mongod,在這種狀況下無分區容錯性可言,也必定是強一致性的。對於sharded cluster,每個shard也都推薦是一個replica set。MongoDB中的shards維護的是獨立的數據子集,所以shards之間出現了分區影響不大(在chunk遷移的過程可能仍是有影響),所以也主要考慮的是shard內部replica set的分區影響。因此,本文中討論MongoDB的一致性、可用性問題,針對的也是MongoDB的replica set。

  對於replica set,只有一個primary節點,接受寫請求和讀請求,其餘的secondary節點接受讀請求。這是一個單寫、多讀的狀況,比多讀、多寫的狀況仍是簡化了許多。後文爲了討論,也是假設replica set由三個基點組成,一個primary,兩個secondary,且全部節點都持久化數據(data-bearing)

  MongoDB關於一致性、可用性的權衡,取決於三者:write-concern、read-concern、read-preference。下面主要是MongoDB3.2版本的狀況,由於read-concern是在MongoDB3.2版本中才引入的。

write-concern:

  write concern表示對於寫操做,MongoDB在什麼狀況下給予客戶端響應。包括下面三個字段:

  { w: <value>, j: <boolean>, wtimeout: <number> }

  w: 表示當寫請求在value個MongoDB實例處理以後才向客戶端返回。取值範圍:

    1:默認值,表示數據寫入到standalone的MongoDB或者replica set的primary以後返回

    0:不用寫入就直接向客戶端返回,性能高,但可能丟數據。不過能夠配合j:True來增長數據的可持久性(durability)

    >1: 只有在replica set環境下才有用,若是value大於的replica set中節點的數目,那麼可能致使阻塞

    ‘majority’: 當數據寫入到replica set的大多數節點以後向客戶端返回,對於這種狀況,通常是配合read-concern使用:

    After the write operation returns with a w: "majority" acknowledgement to the client, the client can read the result of that write with a "majority" readConcern

  j:表示當寫請求在寫入journal以後才向客戶端返回,默認爲False。兩點注意:

    若是在對於未開啓journaling的MongoDB實例使用j:True,會報錯

    在MongoDB3.2及以後,對於w>1, 須要全部實例都寫到journal以後才返回

  wtimeout:表示寫入的超時時間,即在指定的時間(number),若是還不能向客戶端返回(w大於1的狀況),那麼返回錯誤

    默認爲0,至關於沒有設置該選項

 

  在MongoDB3.4中,加入了writeConcernMajorityJournalDefault.這麼一個選項,使得w,j在不一樣的組合下狀況下不一樣:

  

read-reference:

  在前文已經講解過,一個replica set由一個primary和多個secondary組成。primary接受寫操做,所以數據必定是最新的,secondary經過oplog來同步寫操做,所以數據有必定的延遲。對於時效性不是很敏感的查詢業務,能夠從secondary節點查詢,以減輕集羣的壓力。

  

 

  MongoDB指出在不一樣的狀況下選用不一樣的read-reference,很是靈活。MongoDB driver支持一下幾種read-reference:

  primary:默認模式,一切讀操做都路由到replica set的primary節點

  primaryPreferred:正常狀況下都是路由到primary節點,只有當primary節點不可用(failover)的時候,才路由到secondary節點。

  secondary:一切讀操做都路由到replica set的secondary節點

  secondaryPreferred:正常狀況下都是路由到secondary節點,只有當secondary節點不可用的時候,才路由到primary節點。

  nearest:從延時最小的節點讀取數據,無論是primary仍是secondary。對於分佈式應用且MongoDB是多數據中心部署,nearest能保證最好的data locality。

 

  若是使用secondary或者secondaryPreferred,那麼須要意識到:

  (1) 由於延時,讀取到的數據可能不是最新的,並且不一樣的secondary返回的數據還可能不同;

  (2) 對於默認開啓了balancer的sharded collection,因爲還未結束或者異常終止的chunk遷移,secondary返回的多是有缺失或者多餘的數據

  (3) 在有多個secondary節點的狀況下,選擇哪個secondary節點呢,簡單來講是「closest」即平均延時最小的節點,具體參加Server Selection Algorithm 

read-concern:

  read concern是在MongoDB3.2中才加入的新特性,表示對於replica set(包括sharded cluster中使用複製集的shard)返回什麼樣的數據。不一樣的存儲引擎對read-concern的支持狀況也是不同的

  read concern有如下三個level:

  local:默認值,返回當前節點的最新數據,當前節點取決於read reference。

  majority:返回的是已經被確認寫入到多數節點的最新數據。該選項的使用須要如下條件: WiredTiger存儲引擎,且使用election protocol version 1;啓動MongoDB實例的時候指定 --enableMajorityReadConcern選項。

  linearizable:3.4版本中引入,這裏略過了,感興趣的讀者參考文檔。

 

  在文章中有這麼一句話:

Regardless of the read concern level, the most recent data on a node may not reflect the most recent version of the data in the system.

  就是說,即使使用了read concern:majority, 返回的也不必定是最新的數據,這個和NWR理論並非一回事。究其根本緣由,在於最終返回的數值只來源於一個MongoDB節點,該節點的選擇取決於read reference。

  在這篇文章中,對readconcern的引入的意義以及實現有詳細介紹,在這裏只引用核心部分:

readConcern 的初衷在於解決『髒讀』的問題,好比用戶從 MongoDB 的 primary 上讀取了某一條數據,但這條數據並無同步到大多數節點,而後 primary 就故障了,從新恢復後 這個primary 節點會將未同步到大多數節點的數據回滾掉,致使用戶讀到了『髒數據』。

當指定 readConcern 級別爲 majority 時,能保證用戶讀到的數據『已經寫入到大多數節點』,而這樣的數據確定不會發生回滾,避免了髒讀的問題。

 一致性 or 可用性?

  回顧一下CAP理論中對一致性 可用性的問題:
  一致性,是指對於每一次讀操做,要麼都可以讀到最新寫入的數據,要麼錯誤。
  可用性,是指對於每一次請求,都可以獲得一個及時的、非錯的響應,可是不保證請求的結果是基於最新寫入的數據。

  前面也提到,本文對一致性 可用性的討論是基於replica set的,是不是shared cluster並不影響。另外,討論是基於單個客戶端的狀況,若是是多個客戶端,彷佛是隔離性的問題,不屬於CAP理論範疇。基於對write concern、read concern、read reference的理解,咱們能夠得出如下結論。

  • 默認狀況(w:一、readconcern:local)若是read preference爲primary,那麼是能夠讀到最新的數據,強一致性;但若是此時primary故障,那麼這個時候會返回錯誤,可用性得不到保證
  • 默認狀況(w:一、readconcern:local)若是read preference爲secondary(secondaryPreferred、primaryPreferred),雖然可能讀到過期的數據,但可以馬上獲得數據,可用性比較好
  • writeconern:majority保證寫入的數據不會被回滾; readconcern:majority保證讀到的必定是不會被回滾的數據
  • 若(w:一、readconcern;majority)即便是從primary讀取,也不能保證必定返回最新的數據,所以是弱一致性
  • 若(w: majority、readcocern:majority),若是是從primary讀取,那麼必定能讀到最新的數據,且這個數據必定不會被回滾,但此時寫可用性就差一些;若是是從secondary讀取,不能保證讀到最新的數據,弱一致性。


  回過來來看,MongoDB所說的高可用性是更普世意義上的可用性:經過數據的複製和自動failover,即便發生物理故障,整個集羣仍是可以在短期內回覆,繼續工做,況且恢復也是自動的。在這個意義上,確實是高可用的。

相關文章
相關標籤/搜索