1)數據複製原理shell
開啓複製集後,主節點會在 local 庫下生成一個集合叫 oplog.rs,這是一個有限集合,也就是大小是固定的。其中記錄的是整個mongod實例一段時間內數據庫的全部變動(插入/更新/刪除)操做,當空間用完時新記錄自動覆蓋最老的記錄。數據庫
複製集中的從節點就是經過讀取主節點上面的 oplog 來實現數據同步的,MongoDB的oplog(操做日誌)是一種特殊的封頂集合,滾動覆蓋寫入,固定大小。另外oplog的滾動覆蓋寫入方式有兩種:一種是達到設定大小就開始覆蓋寫入;二是設定文檔數,達到文檔數就開始覆蓋寫入(不推薦使用)。服務器
複製集工做方式以下圖:網絡
主節點跟應用程序之間的交互是經過Mongodb驅動進行的,Mongodb複製集有自動故障轉移功能,那麼應用程序是如何找到主節點呢?Mongodb提供了一個rs.isMaster()函數,這個函數能夠識別主節點。默認應用程序讀寫都是在主節點上,默認狀況下,讀和寫都只能在主節點上進行。可是主壓力過大時就能夠把讀操做分離到從節點上從而提升讀性能。下面是MongoDB的驅動支持5種複製集讀選項:數據結構
primary:默認模式,全部的讀操做都在複製集的主節點進行的。多線程
primaryPreferred:在大多數狀況時,讀操做在主節點上進行,可是若是主節點不可用了,讀操做就會轉移到從節點上執行。併發
secondary:全部的讀操做都在複製集的從節點上執行。異步
secondaryPreferred:在大多數狀況下,讀操做都是在從節點上進行的,可是當從節點不可用了,讀操做會轉移到主節點上進行。ide
nearest:讀操做會在複製集中網絡延時最小的節點上進行,與節點類型無關。函數
可是除了primary 模式之外的複製集讀選項都有可能返回非最新的數據,由於複製過程是異步的,從節點上應用操做可能會比主節點有所延後。若是咱們不使用primary模式,請確保業務容許數據存在可能的不一致。
舉個例子:
用客戶端向主節點添加了 100 條記錄,那麼 oplog 中也會有這 100 條的 insert 記錄。從節點經過獲取主節點的 oplog,也執行這 100 條 oplog 記錄。這樣,從節點也就複製了主節點的數據,實現了同步。
須要說明的是:並非從節點只能獲取主節點的 oplog。爲了提升複製的效率,複製集中全部節點之間會互相進行心跳檢測(經過ping)。每一個節點均可以從任何其餘節點上獲取oplog。還有,用一條語句批量刪除 50 條記錄,並非在 oplog 中只記錄一條數據,而是記錄 50 條單條刪除的記錄。oplog中的每一條操做,不管是執行一次仍是屢次執行,對數據集的影響結果是同樣的,i.e 每條oplog中的操做都是冪等的。
2)複製集寫操做
若是啓用複製集的話,在內存中會多一個OPLOG區域,是在節點之間進行同步的一個手段,它會把操做日誌放到OPLOG中來,而後OPLOG會複製到從節點上。從節點接收並執行OPLOG中的操做日誌來達到數據的同步操做。
1) 客戶端的數據進來;
2) 數據操做寫入到日誌緩衝;
3) 數據寫入到數據緩衝;
4) 把日誌緩衝中的操做日誌放到OPLOG中來;
5) 返回操做結果到客戶端(異步);
6) 後臺線程進行OPLOG複製到從節點,這個頻率是很是高的,比日誌刷盤頻率還要高,從節點會一直監聽主節點,OPLOG一有變化就會進行復制操做;
7) 後臺線程進行日誌緩衝中的數據刷盤,很是頻繁(默認100)毫秒,也可自行設置(30-60);
後臺線程進行數據緩衝中的數據刷盤,默認是60秒;
3)Oplog的數據結構
1
2
3
4
5
6
7
8
9
10
11
|
ywnds:PRIMARY> db.oplog.rs.findOne()
{
"ts" : Timestamp(1453059127, 1),
"h" : NumberLong("-6090285017139205124"),
"v" : 2,
"op" : "n",
"ns" : "",
"o" : {
"msg" : "initiating set"
}
}
|
ts:操做發生時的時間戳,這個時間戳包含兩部份內容t和i,t是標準的時間戳(自1970年1月1日 00:00:00 GMT 以來的毫秒數)。而i是一個序號,目的是爲了保證t與i組合出的Mongo時間戳ts能夠惟一的肯定一條操做記錄。
h:此操做的獨一無二的ID。
v:oplog的版本。
op:操做類型(insert、update、delete、db cmd、null),牢牢表明一個消息信息。
ns:操做所處的命名空間(db_name.coll_name)。
o:操做對應的文檔,文檔在更新前的狀態(「msg」 表示信息)。
o2:僅update操做時有,更新操做的變動條件(只記錄更改數據)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
{
"ts" : Timestamp(1481094522, 1),
"t" : NumberLong(1),
"h" : NumberLong("-7286476219427328778"),
"v" : 2,
"op" : "u",
"ns" : "test.foo",
"o2" : {
"_id" : ObjectId("58466e872780d8f6f65951ad")
},
"o" : {
"_id" : ObjectId("58466e872780d8f6f65951ad"),
"a" : 1000
}
}
|
PS:當你想查詢最後一次數據庫操做的oplog記錄時,可使用此語句db.oplog.rs.find().sort({$natural:-1}).limit(1).pretty()。
須要重點強調的是oplog只記錄改變數據庫狀態的操做。好比,查詢就不存儲在oplog中。這是由於oplog只是做爲從節點與主節點保持數據同步的機制。存儲在oplog中的操做也不是徹底和主節點的操做如出一轍的,這些操做在存儲以前先要作等冪變換,也就是說,這些操做能夠在從服務器端屢次執行,只要順序是對的,就不會有問題。例如,使用「$inc」執行的增長更新操做,會被轉換爲「$set」操做。
4)Oplog大小及意義
當你第一次啓動複製集中的節點時,MongoDB會用默認大小創建Oplog。這個默認大小取決於你的機器的操做系統。大多數狀況下,默認的oplog大小是足夠的。在 mongod 創建oplog以前,咱們能夠經過設置 oplogSizeMB 選項來設定其大小。可是,若是已經初始化過複製集,已經創建了Oplog了,咱們須要經過修改Oplog大小中的方式來修改其大小。
OpLog的默認大小:
4.1 複製時間窗口
既然Oplog是一個封頂集合,那麼Oplog的大小就會有一個複製時間窗口的問題。舉個例子,若是Oplog是大小是可用空間的5%,且能夠存儲24小時內的操做,那麼從節點就能夠在中止複製24小時後仍能追遇上主節點,而不須要從新獲取所有數據。若是說從節點在24小時後開始追趕數據,那麼很差意思主節點的oplog已經滾動覆蓋了,把從節點沒有執行的那條語句給覆蓋了。這個時候爲了保證數據一致性就會終止複製。然而,大多數複製集中的操做沒有那麼頻繁,oplog能夠存放遠不止上述的時間的操做記錄。可是,再生產環境中儘量把oplog設置大一些也不礙事。使用rs.printReplicationInfo()能夠查看oplog大小以及預計窗口覆蓋時間。
1
2
3
4
5
6
|
test:PRIMARY> rs.printReplicationInfo()
configured oplog size: 1024MB <--集合大小
log length start to end: 423849secs (117.74hrs) <--預計窗口覆蓋時間
oplog first event time: Wed Sep 09 2015 17:39:50 GMT+0800 (CST)
oplog last event time: Mon Sep 14 2015 15:23:59 GMT+0800 (CST)
now: Mon Sep 14 2015 16:37:30 GMT+0800 (CST)
|
4.2 Oplog大小應隨着實際使用壓力而增長
若是我可以對我複製集的工做狀況有一個很好地預估,若是可能會出現如下的狀況,那麼咱們就可能須要建立一個比默認大小更大的oplog。相反的,若是咱們的應用主要是讀,而寫操做不多,那麼一個小一點的oplog就足夠了。
下列狀況咱們可能須要更大的oplog。
4.2.1 同時更新大量的文檔。
Oplog爲了保證 冪等性 會將多項更新(multi-updates)轉換爲一條條單條的操做記錄。這就會在數據沒有那麼多變更的狀況下大量的佔用oplog空間。
4.2.2 刪除了與插入時相同大小的數據
若是咱們刪除了與咱們插入時一樣多的數據,數據庫將不會在硬盤使用狀況上有顯著提高,可是oplog的增加狀況會顯著提高。
4.2.3 大量In-Place更新
若是咱們會有大量的in-place更新,數據庫會記錄下大量的操做記錄,但此時硬盤中數據量不會有所變化。
4.3 Oplog冪等性
Oplog有一個很是重要的特性——冪等性(idempotent)。即對一個數據集合,使用oplog中記錄的操做重放時,不管被重放多少次,其結果會是同樣的。舉例來講,若是oplog中記錄的是一個插入操做,並不會由於你重放了兩次,數據庫中就獲得兩條相同的記錄。
5)Oplog的狀態信息
咱們能夠經過 rs.printReplicationInfo() 來查看oplog的狀態,包括大小、存儲的操做的時間範圍。關於oplog的更多信息能夠參考Check the Size of the Oplog。
1
2
3
4
5
6
|
test:PRIMARY> rs.printReplicationInfo()
configured oplog size: 1024MB <--集合大小
log length start to end: 423849secs (117.74hrs) <--預計窗口覆蓋時間
oplog first event time: Wed Sep 09 2015 17:39:50 GMT+0800 (CST)
oplog last event time: Mon Sep 14 2015 15:23:59 GMT+0800 (CST)
now: Mon Sep 14 2015 16:37:30 GMT+0800 (CST)
|
在各種異常狀況下,從節點oplog的更新可能落後於主節點一些時間。在從節點上經過 db.getReplicationInfo() 和 db.getReplicationInfo能夠得到如今複製集的狀態與,也能夠知道是否有意外的複製延時。
1
2
3
4
5
6
7
8
9
10
|
test:SECONDARY> db.getReplicationInfo()
{
"logSizeMB" : 1024,
"usedMB" : 168.06,
"timeDiff" : 7623151,
"timeDiffHours" : 2117.54,
"tFirst" : "Fri Aug 19 2016 12:27:04 GMT+0800 (CST)",
"tLast" : "Tue Nov 15 2016 17:59:35 GMT+0800 (CST)",
"now" : "Tue Dec 06 2016 11:27:36 GMT+0800 (CST)"
}
|
6)複製集數據同步過程
Mongodb複製集裏的Secondary會從Primary上同步數據,以保持副本集全部節點的數據保持一致,數據同步主要包含2個過程
6.1 initial sync
6.2 replication(oplog sync)
先經過initial sync同步全量數據,再經過replication不斷重放Primary上的oplog同步增量數據。
initial sync
初始同步會將完整的數據集複製到各個節點上,Secondary啓動後,若是知足如下條件之一,會先進行initial sync。
1. Secondary上oplog爲空,好比新加入的空節點。
2. local.replset.minvalid集合裏_initialSyncFlag標記被設置。當initial sync開始時,同步線程會設置該標記,當initial sync結束時清除該標記,故若是initial sync過程當中途失敗,節點重啓後發現該標記被設置,就知道應該從新進行initial sync。
3. BackgroundSync::_initialSyncRequestedFlag被設置。當向節點發送resync命令時,該標記會被設置,此時會強制從新initial sync。
initial sync同步流程
1. minValid集合設置_initialSyncFlag(db.replset.minvalid.find())。
2. 獲取同步源當前最新的oplog時間戳t0。
3. 從同步源克隆全部的集合數據。
4. 獲取同步源最新的oplog時間戳t1。
5. 同步t0~t1全部的oplog。
6. 獲取同步源最新的oplog時間戳t2。
7. 同步t1~t2全部的oplog。
8. 從同步源讀取index信息,並創建索引(除了_id ,這個以前已經創建完成)。
9. 獲取同步源最新的oplog時間戳t3。
10. 同步t2~t3全部的oplog。
11. minValid集合清除_initialSyncFlag,initial sync結束。
當完成了全部操做後,該節點將會變爲正常的狀態secondary。
replication (sync oplog)
initial sync結束後,Secondary會創建到Primary上local.oplog.rs的tailable cursor,不斷從Primary上獲取新寫入的oplog,並應用到自身。Tailable cursor每次會獲取到一批oplog,Secondary採用多線程重放oplog以提升效率,經過將oplog按照所屬的namespace進行分組,劃分到多個線程裏,保證同一個namespace的全部操做都由一個線程來replay,以保證統一namespace的操做時序跟primary上保持一致(若是引擎支持文檔鎖,只需保證同一個文檔的操做時序與primary一致便可)。
同步場景分析
1. 副本集初始化
初始化選出Primary後,此時Secondary上無有效數據,oplog是空的,會先進行initial sync,而後不斷的應用新的oplog。
2. 新成員加入
因新成員上無有效數據,oplog是空的,會先進行initial sync,而後不斷的應用新的oplog。
3. 有數據的節點加入
有數據的節點加入有以下狀況:
A.該節點與副本集其餘節點斷開鏈接,一段時間後恢復
B.該節點從副本集移除(處於REMOVED)狀態,經過replSetReconfig命令將其從新加入
C.其餘? 因同一個副本集的成員replSetName配置必須相同,除非有誤配置,應該不會有其餘場景
此時,若是該節點最新的oplog時間戳,比全部節點最舊的oplog時間戳還要小,該節點將找不到同步源,會一直處於RECOVERING而不能服務;反之,若是能找到同步源,則直接進入replication階段,不斷的應用新的oplog。
因oplog太舊而處於RECOVERING的節點目前沒法自動恢復,需人工介入處理(故設置合理的oplog大小很是重要),最簡單的方式是發送resync命令,讓該節點從新進行initial sync。
7)多線程複製
MongoDB容許經過多線程進行批量寫操做來提升併發能力,MongoDB將批操做經過命名空間來分組。