深入淺析mongodb的一致性模型及其實現
我們已經知道,在zookeeper中,寫請求轉發給leader,讀請求follower自己處理,沒有sync的情況下達不到linearizability。Mongodb呢?官方文檔是這麼說的,「MongoDB is consistent by default: reads and writes are issued to the primary member of a replica set. 」
這句話誤導了我很多年,讓我一直以為默認情況下,讀寫操作都發給primary,這總應該linearizable了吧?結果得知真相之後讓我眼淚掉下來。
Write Concern,Read Concern和Read Preference
Mongodb能夠提供靈活的一致性,原因在於它可以設置不同的Write Concern,Read Concern和Read Preference。
Write Concern決定了對資料庫寫入操作的確認級別,比如是否大多數節點都寫入成功了才向客戶端返回確認。
Read Concern決定了讀取操作的返回數據的隔離級別,比如是否將大多數節點都希爾成功的數據返回給讀操作。
Read Preference決定了從哪個節點進行讀取操作。
默認情況
在mongodb 4.0版本中,默認的Read Preference是primary,也就是讀寫操作都發送給primary。
默認的Write Concern是w:1,對於副本集來說,就是只要primary確認寫入就可以了。所以你以為你寫入成功了,但是primary沒有把數據分發出去之前就掛掉了,那麼你以為寫入成功的數據,就消失得無影無蹤了。默認的Read Concern呢?居然是local!所以你從primary讀到了一個數據x,在x還沒被分發時primary掛了,新的primary沒有x的信息,那麼再從新的primary讀數據時,x就好像從來沒存在過一樣。
所以,默認情況下,mongodb的一致性和Linearizability差得遠呢。
Majority
那把Write Concern和Read Concern都設置為majority會怎麼樣呢?
Majority的Write Concern指的是數據分發給副本集的大多數成員之後再確認。
Majority的Read Concern指的是讀取到的數據是被副本集大多數成員確認的。
想像以下情況:
N1是副本集的primary,N2和N3是兩個secondary。客戶端S1讀寫請求都發給N1。
N1突然發生了網路隔離,不能和N2,N3通信了。
N2被選舉為primary,但是N1對此一無所知,仍然認為自己是primary,所以此時副本集里實際上有兩個primary。
客戶端S2以majority的Write Concern寫入新的數據到N2。
N2將數據寫入N3後,向S2返回寫入確認。
客戶端S1以majority的Read Concern從N1讀取數據。
很容易看出,這種情況下是沒有linearizability的。
其實,Mongodb還有一種更糟的情況,N1和N2同時作為primary的時候,S1有可能寫請求發給N2,讀請求發給N1。這樣它連自己寫入的數據都讀不出來。因為mongodb的driver一般都需要連接池,而mongodb又有Read Preference這種實現讀寫分離的選項,所以實現起來就很麻煩。driver除了官方的以外,還有開源社區維護的,沒注意到這些細節的話,很容易出現這種問題。
而且早期的mongodb中,根本沒給你檢測stale primary的機會。。。
https://github.com/mongodb/specifications/blob/master/source/server-discovery-and-monitoring/server-discovery-and-monitoring.rst#using-setversion-and-electionid-to-detect-stale-primaries
Linearizable Read Concern
Linearizable Read Concern是mongodb 3.4中出現的,為了解決上面的問題。有了linearizable的Read Concern,終於可以讀取副本集中最新的數據啦。Linearizable Read Concern有一些使用上的限制:
只能用在primary的read操作上
查詢條件必須唯一的確定一個document
linearizable Read Concern的實現原理是在read操作之前添加了一個空的寫操作,如果當前的primary是stale的,空的寫操作就會失敗,從而被檢查出來。實際上和zookeeper的sync如出一轍殊途同歸。
Causal Consistency
上面的例子中,client的讀寫都發給了primary,不過總讓secondary閑著也不太好,不如搞個讀寫分離吧。
不過一開始的讀寫分離是容易出現問題的:
N1是副本集的primary,N2和N3是兩個secondary。
客戶端S設置Read Preference,可以讓讀請求發給secondary,寫請求發給primary。
S在N1上以majority的Write Concern寫入訂單數據x。
N1把x分發給了N2之後,判定超過半數了,返回成功。
S在N3上讀取x,發現沒有這個訂單。
這真是非常尷尬,自己寫的數據,自己都看不到。所以mongodb 3.6增加了Causal Consistency一致性。
為了實現它,mongodb每個節點都需要能獲取正確的時間。時鐘是分散式系統非常重要的組成部分,在其他文章中我們會著重介紹。
現在mongodb這樣工作:
N1是副本集的primary,N2和N3是兩個secondary。
客戶端S設置Read Preference,可以讓讀請求發給secondary,寫請求發給primary。
S在N1上以majority的Write Concern寫入訂單數據x,記錄時間T。
N1把x分發給了N2之後,判定超過半數了,返回成功。
S在N3上讀取x,讀取操作帶有T作為參數。
N3沒有時間T的操作,開始等待。
N3從N1得到x的數據,意味著N3擁有了T時間的數據。
N3將數據返回給S。
為了達到Causal Consistency,mongodb的客戶端需要額外進行一些操作,例如開啟client session,同時也有相應的一些限制,例如同一時刻只能有一個線程在client session中執行操作。
![](https://pic.pimg.tw/zzuyanan/1488615166-1259157397.png)
![](https://pic.pimg.tw/zzuyanan/1482887990-2595557020.jpg)
TAG:千鋒JAVA開發學院 |