復制集數據同步
使用復制集的過程中,當主節點有數據變更的時候,備份節點都會進行數據同步操作,需要注意的是,在Mongo的復制集中,備份節點進行數據同步是依賴主節點的oplog
,oplog
數據存放在主節點local數據庫里的一個固定集合中,每個備份節點自身也會維護一份自身的oplog
,記錄著每一次從主節點同步過來的復制數據的操作。這樣,每個備份節點也方便提供給其他備份節點比較和復制使用。備份節點從當前使用的數據集上執行這些操作, 然后再將這些操作寫入自己的oplog
,如果遇到故障或者同步操作失敗的情況(正常情況不會出現,只有當前節點數據丟失或者部分數據損壞才會出現這樣),這個時候備份節點就會停止從主節點進行復制數據操作。
如果說備份節點因為一些原因導致奔潰或者故障了,我們重新啟動當前節點以后,會從自身維護的oplog里面的最后一條數據開始重新進行同步,但是由于復制數據的過程,是先把數據復制過來,寫入到本地以后再去寫入oplog,那么就有可能出現數據同步了,oplog還沒寫入的情況,為了避免出現重復寫入導致出現臟數據,mongo官方做了處理,保證了多次執行oplog數據操作和一次執行的結果是一樣的。需要注意的一點是,oplog的內存大小是固定的,也就是說保存操作的數量是固定的,一般我們使用比較穩定的話,oplog的增長速率也會比較穩定,但是如果我們執行了類似db.collection.remove()
操作,這種情況下,如果清理了1億條文檔數據,那么就會在oplog中存入1億條操作記錄,直至填滿。
初始化啟動后同步流程
當我們啟動復制集以后,首先會檢查當前狀態是否良好,并且確認從哪一臺復制集節點進行數據同步操作,當發現無法從該節點進行數據同步的時候,會選擇其他的節點嘗試進行數據同步操作。當確認以后就開始了數據同步的過程:
- 確定了從哪一個節點進行同步數據源,會在當前的
local.me
中創建一個標識符,并且將自身的所有用戶數據庫刪除,開始以一個全新的狀態來開始數據同步。 - 接著會從復制源上將所有的數據復制到本地,這個過程稱為克隆數據,也是最耗時的操作
- 當從復制源獲取到數據以后,就開始操作oplog了,克隆過程中所有復制來的數據的操作都會記錄到oplog中,因此需要注意,在克隆過程中,由于較為耗時,可能會把復制源的數據進行變動或者修改,導致部分數據無法克隆,這種情況只能等待下一次初始化同步才可以同步回來,因此我們在做初始化同步操作的時候需要格外注意
- 在oplog同步的過程中,會將oplog的每一步操作都記錄下來,直到初始化同步數據源操作完成。這個時候,理論上當前mongo副本集的數據應該和同步數據源的某個時間節點的數據完全一致,這個時候就可以創建數據索引了。如果說集合數據比較多,切索引數量較多,這個創建索引的過程就會較為耗時。
- 如果初始化同步完成后,發現當前節點的數據依然遠遠落后于同步的數據源的數據,那么這個時候oplog同步過程中的最后一步就是將創建索引期間的操作記錄也同步過來,防止這個節點成為備份節點
舊數據的處理
前面我們也提到過,備份節點的數據有可能落后于同步源,當落后的較多的時候,那么這個備份節點就是陳舊節點,陳舊的節點很難跟上同步源數據,如果需要繼續同步,那么勢必要跳過一部分數據操作,當然導致備份節點陳舊的原因有很多,例如此節點曾經出現過停機一段時間,或者是寫入已經超過了自身處理的能力上限,或者讀取數據的請求太多,就會導致備份節點陳舊。當然如果某個節點變為陳舊節點以后,會去查看當前副本集中的其他節點成員,如果發現某個成員的oplog日志比較準確,可以用于同步當前落下的操作,就會改為從這個節點進行同步,如果發現所有的成員節點都不滿足,就會停止當前的復制操作,當前節點的數據需要再次重新完全同步(或者從備份里面恢復)才可以。因此為了避免陳舊節點的出現,我們往往可以考慮,優先讓主節點使用比較大的oplog保存足夠多的操作日志是很好的選擇,因為硬盤發展到現在已經是成本很低的產品了,同時也便于其他節點的數據同步。
心跳
mongo復制集中為了保證成員之間彼此知道其他成員的狀態,比如哪個是主節點,哪個可以是同步源,哪個節點已經不可用了?這個過程也是利用了心跳機制來維護的,默認情況下,mongo節點之間每兩秒就會向其他成員節點發送一個心跳請求,用于通知其他節點當前的狀態,同時也是主節點是否能明確知道當前是否滿足大多數節點狀態的重要依據
成員狀態
每個成員節點都會將當前的狀態作為心跳的信號發送給其他成員節點,常見的成員節點狀態如主節點、備份節點,是我們見到最多的,但是除了這兩個以外,還有其他成員狀態:
STARTUP
當前狀態代表節點剛啟動的時候的狀態,處于這個狀態的時候,mongo會嘗試加載副本集相關的配置,當加載配置完成以后,就會進入STARTUP2
狀態
STARTUP2
當進入到此狀態的時候,代表當前節點正在進行初始化同步操作。如果是普通的節點,這個過程一般不會太久,幾秒鐘就能完成,這個時候mongo會創建幾個線程,用于完成復制和選舉等操作,完成以后狀態就會變為RECOVERING
狀態
RECOVERING
當節點處于當前狀態的時候,代表此節點運行正常,只是暫時還沒完成,暫時無法處理請求。此狀態下,節點處于輕微過載,需要做一些檢查,確保當前處于有效狀態,之后就會切換到正常狀態,當然如果是陳舊節點,也會處于這個狀態,會去查找副本集中oplog較為準確的成員,進行同步oplog,完成以后也會切換到正常狀態。
ARBITER
在正常的操作中,如果節點屬于仲裁節點,就應該處于當前狀態
DOWN
如果節點無法被訪問了,變為不可達節點,那么就會變更為此狀態。當然不可達節點的原因有很多,比如有可能是網絡問題導致的訪問不到此節點
UNKNOWN
如果一個成員節點無法到達其他成員,這個時候對于其他成員來說,此節點的狀態就是未知的,這個時候就會將其標記為UNKNOWN
狀態,標記這個節點為不可達節點
REMOVED
當成員退出副本集的時候,就會處于這個狀態。當然如果重新加入副本集,此節點的狀態又會慢慢變為正常狀態
ROLLBACK
如果當前節點正在執行數據回滾操作,就會將狀態改為此狀態,完成回滾操作以后會恢復為正常狀態
FATAL
如果節點發生了嚴重的錯誤,導致服務不可用,此時應該處理嘗試正常恢復服務,就會將狀態標記為FATAL
,我們可以在日志中通過grep replSet FATAL
關鍵字應該可以查看到發生錯誤的時間等信息
復制集選舉
當一個成員節點加入到復制集以后,發現無法到達主節點,這個時候此節點會認為當前復制集沒有主節點,就會發起申請成為主節點操作,然后將請求發送給復制集中所有當前節點可達的其他成員節點。其他成員節點在接受到這個請求以后,會檢查此節點是否滿足作為主節點的條件,比如先檢查在當前復制集中是否有可達的主節點存在,如果已經有主節點存在了,那么會直接拒絕,如果找不到已經存在的主節點,就會去檢查發起申請的節點的數據是否比當前節點的數據更新,如果比當前數據更新,說明對于當前節點來說,是比較合適作為主節點的,否則也會拒絕申請。
假如整個復制集中超過大半的節點都投了同意票,那么發出申請的節點就會選舉成功,成為master,切換自身的狀態到主節點狀態,如果收到的結果是大部分反對,那么則仍然保持為備份節點的狀態。
當然主節點被選舉出來以后,正常情況下會一直保持著主節點的狀態,除非是心跳檢測以后,不滿足大多數可達的要求,強制退位,或者服務掛了導致退位,除此之外,我們修改了此節點對應的副本集的配置,要求重新加載副本集,也會導致主節點退位。
正常網絡情況良好,副本集內進行選舉的過程是很快的,因為默認情況下心跳是兩秒一次,也就是說網絡沒有異常的情況下,兩秒內就會有成員節點發現主節點狀態出現問題,或者出現了錯誤、不可達等,這個時候就會重新進行選舉,選舉過程很快,但是如果遇到了網絡響應比較慢,甚至可能會導致心跳超時,默認時間為20秒,而且發起選舉可能會導致最終的投票平票,在平票的時候,需要等待30秒以后才能發起第二輪投票,這個時候就會等待很久,甚至好幾分鐘以上。
多機房環境下選舉回滾問題
除了正常情況下的復制集以外,有時候當我們復制集的節點較多的時候,有可能出現在多個機房或者多個網絡內組建復制集的情況,假設我們現在是五個節點組成的復制集,其中A機房是兩個節點,B機房是三個節點,大體如下:
此時A機房是主節點,由于網絡問題,突然A機房和B機房之間無法訪問,此時對于B機房來說是主節點不可達,而由于B機房內還有三個節點存在,滿足大多數特性,這個時候,就會觸發新的主節點選舉操作,但是如果在網絡斷開的一瞬間,原本A機房的主節點正在處理新的請求,如圖所示,此時A機房的oplog是超過了B機房的oplog的,即在斷開之前還有部分數據沒有復制進B機房的備份節點中,這個時候B機房重新選舉出了一個master節點,繼續對外提供服務,當操作了一段時間以后,AB機房此時網絡又可以訪問了,這個時候A機房的節點加入到復制集以后,發現當前節點有部分oplog數據對不上,即斷開的時候多處理的那部分數據,此時就會進入回滾操作(狀態為回滾狀態),A機房的節點會去B機房中找oplog共同操作的最后一條日志數據,即125的oplog,這個時候會選擇回滾到125以后,在進行同步操作。
不過mongo會去查看那些沒有被復制的操作,將這部分受影響的文檔寫入.bson文件,保存在目錄文件下的rollback目錄中,如果這個多出來的126是更新文檔的操作,會將這部分操作寫入collectionname下的bson文件中,然后再去主節點中復制這部分文檔數據。
如果要將被回滾的操作應用到當前主節點,首先使用mongorestore
命令將它們加載到一個臨時集合中,然后在在shell中將這些文檔與同步后的集合進行比較,然后在將確定的數據加載到主集合中去。