大約在五六年前,第一次接觸到了當時已是hot topic的NoSql。不過那個時候學的用的都是mysql,Nosql對於我而言仍是新事物,並無真正使用,只是不明覺厲。可是印象深入的是這麼一張圖片(後來google到圖片來自這裏):html
簡而言之: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理論最好是在「分佈式存儲系統」這個大前提下,可用性也不是說總體服務的可用性,而是分佈式系統中某個子節點的可用性。所以感受上文的例子並非很恰當。緩存
從收入目標以及合約規定來說,系統 可用性是首要目標,於是咱們常規會使用緩存或者過後校覈更新日誌來優化系統的可用性。所以,當設計師選擇可用性的時候,由於須要在分區結束後恢復被破壞的不變性約。實踐中,大部分團體認爲(位於單一地點的)數據中心內部是沒有分區的,所以在單一數據中心以內能夠選擇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!」
在《經過一步步建立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表示對於寫操做,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在不一樣的組合下狀況下不一樣:
在前文已經講解過,一個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是在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 時,能保證用戶讀到的數據『已經寫入到大多數節點』,而這樣的數據確定不會發生回滾,避免了髒讀的問題。
回顧一下CAP理論中對一致性 可用性的問題:
一致性,是指對於每一次讀操做,要麼都可以讀到最新寫入的數據,要麼錯誤。
可用性,是指對於每一次請求,都可以獲得一個及時的、非錯的響應,可是不保證請求的結果是基於最新寫入的數據。
前面也提到,本文對一致性 可用性的討論是基於replica set的,是不是shared cluster並不影響。另外,討論是基於單個客戶端的狀況,若是是多個客戶端,彷佛是隔離性的問題,不屬於CAP理論範疇。基於對write concern、read concern、read reference的理解,咱們能夠得出如下結論。
回過來來看,MongoDB所說的高可用性是更普世意義上的可用性:經過數據的複製和自動failover,即便發生物理故障,整個集羣仍是可以在短期內回覆,繼續工做,況且恢復也是自動的。在這個意義上,確實是高可用的。