1. 程式人生 > >MongoDB復制集的工作原理介紹(二)

MongoDB復制集的工作原理介紹(二)

不能 數據同步 nat ring $inc tla 2.3 支持 pla

復制集工作原理

1)數據復制原理

開啟復制集後,主節點會在 local 庫下生成一個集合叫 oplog.rs,這是一個有限集合,也就是大小是固定的。其中記錄的是整個mongod實例一段時間內數據庫的所有變更(插入/更新/刪除)操作,當空間用完時新記錄自動覆蓋最老的記錄。

復制集中的從節點就是通過讀取主節點上面的 oplog 來實現數據同步的,MongoDB的oplog(操作日誌)是一種特殊的封頂集合,滾動覆蓋寫入,固定大小。另外oplog的滾動覆蓋寫入方式有兩種:一種是達到設定大小就開始覆蓋寫入;二是設定文檔數,達到文檔數就開始覆蓋寫入(不推薦使用)。

復制集工作方式如下圖:

技術分享圖片

主節點跟應用程序之間的交互是通過Mongodb驅動進行的,Mongodb復制集有自動故障轉移功能,那麽應用程序是如何找到主節點呢?Mongodb提供了一個rs.isMaster()函數,這個函數可以識別主節點。默認應用程序讀寫都是在主節點上,默認情況下,讀和寫都只能在主節點上進行。但是主壓力過大時就可以把讀操作分離到從節點上從而提高讀性能。下面是MongoDB的驅動支持5種復制集讀選項:

primary:默認模式,所有的讀操作都在復制集的主節點進行的。

primaryPreferred:在大多數情況時,讀操作在主節點上進行,但是如果主節點不可用了,讀操作就會轉移到從節點上執行。

secondary:所有的讀操作都在復制集的從節點上執行。

secondaryPreferred:在大多數情況下,讀操作都是在從節點上進行的,但是當從節點不可用了,讀操作會轉移到主節點上進行。

nearest:讀操作會在復制集中網絡延時最小的節點上進行,與節點類型無關。

但是除了primary 模式以外的復制集讀選項都有可能返回非最新的數據,因為復制過程是異步的,從節點上應用操作可能會比主節點有所延後。如果我們不使用primary模式,請確保業務允許數據存在可能的不一致。

舉個例子:

用客戶端向主節點添加了 100 條記錄,那麽 oplog 中也會有這 100 條的 insert 記錄。從節點通過獲取主節點的 oplog,也執行這 100 條 oplog 記錄。這樣,從節點也就復制了主節點的數據,實現了同步。

需要說明的是:並不是從節點只能獲取主節點的 oplog。為了提高復制的效率,復制集中所有節點之間會互相進行心跳檢測(通過ping)。每個節點都可以從任何其他節點上獲取oplog。還有,用一條語句批量刪除 50 條記錄,並不是在 oplog 中只記錄一條數據,而是記錄 50 條單條刪除的記錄。oplog中的每一條操作,無論是執行一次還是多次執行,對數據集的影響結果是一樣的,i.e 每條oplog中的操作都是冪等的。

  • 初始化同步
  • 回滾後的數據追趕
  • 分片的chunk遷移

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的默認大小:

  • 在64位Linux、Windows操作系統上為當前分區可用空間的5%,但最大不會超過50G。
  • 在64位的OS X系統中,MongoDB默認分片183M大小給Oplog。
  • 在32位的系統中,MongoDB分片48MB的空間給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將批操作通過命名空間來分組。

MongoDB復制集的工作原理介紹(二)