1. 程式人生 > 其它 >Mongo副本集的組成

Mongo副本集的組成

  本章介紹副本集的各個部分是如何組織在一起的,包括:

    • 副本整合員如何複製新資料;
    • 如何讓新成員開始工作;
    • 選舉機制;
    • 可能的伺服器和網路故障。

1.同步

  複製用於在多臺伺服器之間備份資料。MongoDB的複製功能是使用操作日誌oplog 實現的,操作日誌包含了主節點的每一次寫操作。oplog是主節點的local資料庫中的一個固定集合。備份節點通過査詢這個集合就可以知道需要進行復制的操作。

  每個備份節點都維護著自己的oplog,記錄著每一次從主節點複製資料的操作。這樣,每個成員都可以作為同步源提供給其他成員使用,如圖10-1所示。備份節點從當前使用的同步源中獲取需要執行的操作,然後在自己的資料集上執行這些操作,最後再將這些操作寫入自己的oplog。如果遇到某個操作失敗的情況(只有當同步源的資料損壞或者資料與主節點不一致時才可能發生),那麼備份節點就會停止從當前的同步源複製資料。

  如果某個備份節點由於某些原因掛掉了,當它重新啟動之後,就會自動從oplog中最後一個操作開始進行同步。由於複製操作的過程是先複製資料再寫入oplog,所以,備份節點可能會在已經同步過的資料上再次執行復制操作。MongoDB在設計之初就考慮到了這種情況:將oplog中的同一個操作執行多次,與只執行一次的效果是一樣的。

  由於oplog大小是固定的,它只能儲存特定數量的操作日誌。通常,oplog使用空間的增長速度與系統處理寫請求的速率近乎相同:如果主節點上每分鐘處理了 1 KB的寫入請求,那麼oplog很可能也會在一分鐘內寫入1KB條操作日誌。但是,有一些例外情況:如果單次請求能夠影響到多個文件(比如刪除多個文件或者是多文件更新),oplog中就會出現多條操作日誌。如果單個操作會影響多個文件,那麼每個受影響的文件都會對應oplog中的一條日誌。因此,如果執行db.coll.remove()刪除了 1 000 000個文件,那麼oplog中就會有1 000 000條操作日誌,每條日誌對應一個被刪除的文件。如果執行大量的批量操作,oplog很快就會被填滿。

1.1 初始化同步

  副本集中的成員啟動之後,就會檢査自身狀態,確定是否可以從某個成員那裡進行同步。如果不行的話,它會嘗試從副本的另一個成員那裡進行完整的資料複製。這個過程就是初始化同步(initial syncing),包括幾個步驟,可以從mongod的日誌中看到。

  (1)首先,這個成員會做一些記錄前的準備工作:選擇一個成員作為同步源,在local.me中為自己建立一個識別符號,刪除所有已存在的資料庫,以一個全新的狀態開始進行同步:

Mon Jan    30    11:09:18    [rsSync]    replSet initial sync pending
Mon Jan    
30 11:09:18 [rsSync] replSet syncing to: server-1:27017 Mon Jan 30 11:09:18 [rsSync] build index local.me { id: 1 } Mon Jan 30 11:09:18 [rsSync] build index done 0 records 0 secs Mon Jan 30 11:09:18 [rsSync] replSet initial sync drop all databases Mon Jan 30 11:09:18 [rsSync] dropAUDatabasesExceptLocal 1

  注意,在這個過程中,所有現有的資料都會被刪除。應該只在不需要保留現有資料的情況下做初始化同步(或者將資料移到其他地方),因為mongod會首先將現有資料刪除。

  (2) 然後是克隆(cloning),就是將同步源的所有記錄全部複製到本地。這通常是整個過程中最耗時的部分:

Mon Jan  30 11:09:18    [rsSync]    replSet initial sync clone all databases
Mon Jan  30 11:09:18    [rsSync]    replSet initial sync cloning db: db1
Mon Jan  30 11:09:18 [fileAllocator] allocating new datafile /data/db/db1.ns, filling with zeroes…

  (3) 然後就進入oplog同步的第一步,克隆過程中的所有操作都會被記錄到oplog中。如果有文件在克隆過程中被移動了,就可能會被遺漏,導致沒有被克隆,對於這樣的文件,可能需要重新進行克隆:

Mon Jan 30 15:38:36 [rsSync] oplog sync 1 of 3
Mon Jan 30 15:38:36 [rsBackgroundSync] replSet syncing to: server-1:27017 
Mon Jan 30 15:38:37 [ rsSyncNotifier] replset setting oplog notifier to server-1:27017
Mon Jan 30 15:38:37 [repl writer worker 2] replication update of non-mod failed:
        { ts: Timestamp 1352215827000|17, h: -5618036261007523082, v: 2, op: "u", ns: "db1.someColl", o2: { _id: ObjectId('50992a2a78522Gle750012b71) }, o: { $set: { count.0: 2, count.1: 0 } } }
Mon Jan 30 15:38:37 [repl writer worker 2] replication info adding missing object
Mon Jan 30 15:38:37 [repl writer worker 2] replication missing object not found on source, presumably deleted later in oplog

  上面是一個比較粗略的日誌,顯示了有文件需要重新克隆的情況。在克隆過程中也可能不會遺漏文件,這取決於流量等級和同步源上的操作型別。

  (4) 接下來是oplog同步過程的第二步,用於將第一個oplog同步中的操作記錄下來。

Mon Jan 30 15:39:41 [rsSync] oplog sync 2 of 3

  這個過程比較簡單,也沒有太多的輸出。只有在沒有東西需要克隆時,這個過程才會與第一個不同。

  (5) 到目前為止,本地的資料應該與主節點在某個時間點的資料集完全一致了,可以開始建立索引了。如果集合比較大,或者要建立的索引比較多,這個過程會很耗時間:

Mon Jan    30    15:39:43    [rsSync]    replSet initial sync building indexes
Mon Jan    30    15:39:43    [rsSync]    replSet initial sync cloning indexes for :    db1
Mon Jan    30    15:39:43    [rsSync]    build index db.allObjects { someColl: 1 }
Mon Jan    30    15:39:44    [rsSync]    build index done, scanned 209844 total records.
1.96 secs

  (6) 如果當前節點的資料仍然遠遠落後於同步源,那麼oplog同步過程的最後一步就是將建立索引期間的所有操作全部同步過來,防止該成員成為備份節點。

Tue Nov 6 16:05:59 [rsSync] oplog sync 3 of 3

  (7) 現在,當前成員已經完成了初始化同步,切換到普通同步狀態,這時當前成員就可以成為備份節點了 :

Mon Jan 30 16:07:52 [rsSync] replSet initial sync done
Mon Jan 30 16:07:52 [rsSync] replSet syncing to: server-1:27017
Mon Jan 30 16:07:52 [rsSync] replSet SECONDARY

  如果想跟蹤初始化同步過程,最好的方式就是檢視伺服器日誌。

  從操作者的角度來說,初始化同步是非常簡單的:使用空的資料目錄啟動mongod 即可。但是,更多時候可能需要從備份中恢復(其他章節會詳細介紹)而不是進行初始化同步。從備份中恢復的速度比使用mongod複製全部資料的速度快得多。

  克隆也可能損壞同步源的工作集(working set)。實際部署之後,可能會有一個頻繁使用的資料子集常駐記憶體(因為作業系統要頻繁訪問這個子集)。執行初始化同步時,會強制將當前成員的所有資料分頁載入到記憶體中,這會導致需要頻繁訪問的資料不能常駐記憶體,所以會導致很多請求變慢,因為原本只要在RAM (記憶體)中就可以處理的資料要先從磁碟上載入。不過,對於比較小的資料集和效能比較好的伺服器,初始化同步仍然是個簡單易用的選項。

  初始化同步過程中經常遇到的問題是,第(2)步(克隆)或者第(5)步(建立索引) 耗費了太長的時間。這種情況下,新成員就與同步源的oplog “脫節”:新成員遠遠落後於同步源,導致新成員的資料同步速度趕不上同步源的變化速度,同步源可能會將新成員需要複製的某些資料覆蓋掉。

  這個問題沒有有效的解決辦法,除非在不太忙時執行初始化同步,或者是從備份中恢復資料。如果新成員與同步源的oplog脫節,初始化同步就無法正常進行。下一節會更深入地介紹。

1.2 處理陳舊資料

  如果備份節點遠遠落後於同步源當前的操作,那麼這個備份節點就是陳舊的 (stale)。陳舊的備份節點無法跟上同步源的節奏,因為同步源上的操作領先太多太多:如果要繼續進行同步,備份節點需要跳過一些操作。如果從備份節點曾經停機過,寫入量超過了自身處理能力,或者是有太多的讀請求,這些情況都可能導致備份節點陳舊。

  當一個備份節點陳舊之後,它會査看副本集中的其他成員,如果某個成員的oplog 足夠詳盡,可以用於處理那些落下的操作,就從這個成員處進行同步。如果任何一個成員的oplog都沒有參考價值,那麼這個成員上的複製操作就會中止,這個成員需要重新進行完全同步(或者是從最近的備份中恢復)。

  為了避免陳舊備份節點的出現,讓主節點使用比較大的oplog儲存足夠多的操作日誌是很重要的。大的oplog會佔用更多的磁碟空間。通常來說,這是一個比較好的折衷選擇,因為磁碟會越來越便宜,而且實際中使用的oplog只有一小部分,因此oplog不佔用太多RAM。關於oplog空間佔用的更多資訊,12.4.6節會詳細介紹。

2.心跳

  每個成員都需要知道其他成員的狀態:哪個是主節點?哪個可以作為同步源?哪個掛掉了?為了維護集合的最新檢視,每個成員每隔兩秒鐘就會向其他成員傳送一個心跳請求(heartbeat request)。心跳請求的資訊量非常小,用於檢査每個成員的狀態。

  心跳最重要的功能之一就是讓主節點知道自己是否滿足集合“大多數”的條件。如果主節點不再得到“大多數”伺服器的支援,它就會退位,變成備份節點。

2.1 成員狀態

  各個成員會通過心跳將自己的當前狀態告訴其他成員。我們已經討論過兩種狀態了: 主節點和備份節點。還有其他一些常見狀態。

  • STARTUP

  成員剛啟動時處於這個狀態。在這個狀態下,MongoDB會嘗試載入成員的副本集配置。配置載入成功之後,就進入STARTUP2狀態。

  • STARTUP2

  整個初始化同步都處於這個狀態,但是如果是在普通成員上,這個狀態只會持續幾秒鐘。在這個狀態下,MongoDB會建立幾個執行緒,用於處理複製和選舉,然後就會切換到RECOVERING狀態。

  • RECOVERING

  這個狀態表明成員運轉正常,但是暫時還不能處理讀取請求。如果有成員處於這個狀態,可能會造成輕微的系統過載,以後可能會經常見到。

  啟動時,成員需要做一些檢查以確保自己處於有效狀態,之後才可以處理讀取請求。在啟動過程中,成為備份節點之前,每個成員都要經歷RECOVERING狀態。在處理非常耗時的操作時,成員也可能進入RECOVERING狀態。,比如壓縮或者是響應 replSetMaintenance 命令。

  當一個成員與其他成員脫節時,也會進入RECOVERING狀態。通常來說,這時這個成員處於無效狀態,需要重新同步。但是,成員這時並沒有進入錯誤狀態,因為它期望發現一個擁有足夠詳盡oplog的成員,然後繼續同步oplog,最後回到正常狀態。

  • ARBITER

  在正常的操作中,仲裁者應該始終處於ARBITER狀態。

  系統出現問題時會處於下面這些狀態。

  • DOWN

  如果一個正常執行的成員變得不可達,它就處於DOWN狀態。注意,如果有成員被報告為DOWN狀態,它有可能仍然處於正常執行狀態,不可達的原因可能是網路問題。

  • UNKNOWN

  如果一個成員無法到達其他任何成員,其他成員就無法知道它處於什麼狀態,會將其報告為UNKNOWN狀態。通常,這表明這個未知狀態的成員掛掉了,或者是兩個成員之間存在網路訪問問題。

  • REMOVED

  當成員被移出副本集時,它就處於這個狀態。如果被移出的成員又被重新新增到副本集中,它就會回到“正常”狀態。

  • ROLLBACK

  如果成員正在進行資料回滾(詳見10.4節),它就處於ROLLBACK狀態。回滾過 程結束時,伺服器會轉換為RECOVERING狀態,然後成為備份節點。

  • FATAL

  如果一個成員發生了不可挽回的錯誤,也不再嘗試恢復正常的話,它就處於 FATAL狀態。應該査看詳細日誌來查明為何這個成員處於FATAL狀態(使用"replSet FATAL"關鍵詞在日誌上執行grep,就可以找到成員進入FATAL狀態的時間點)。這時,通常應該重啟伺服器,進行重新同步或者是從備份中恢復。

3.選舉

  當一個成員無法到達主節點時,它就會申請被選舉為主節點。希望被選舉為主節點的成員,會向它能到達的所有成員傳送通知。如果這個成員不符合候選人要求,其他成員可能會知道相關原因:這個成員的資料落後於副本集,或者是已經有一個執行中的主節點(那個力求被選舉成為主節點的成員無法到達這個主節點)。在這些情況下,其他成員不會允許進行選舉。

  假如沒有反對的理由,其他成員就會對這個成員進行選舉投票。如果這個成員得到副本集中“大多數”贊成票,它就選舉成功,會轉換到主節點狀態。如果達不到“大多數”的要求,那麼選舉失敗,它仍然處於備份節點狀態,之後還可以再次申請被選舉為主節點。主節點會一直處於主節點狀態,除非它由於不再滿足“大多數”的要求或者掛了而退位,另外,副本集被重新配置也會導致主節點退位。

  假如網路狀況良好,“大多數”伺服器也都在正常執行,那麼選舉過程是很快的。如果主節點不可用,2秒鐘(之前講過,心跳的間隔是2秒)之內就會有成員發現這個問題,然後會立即開始選舉,整個選舉過程只會花費幾毫秒。但是,實際情況可能不會這麼理想:網路問題,或者是伺服器過載導致響應緩慢,都可能觸發選舉。在這種情況下,心跳會在最多20秒之後超時。如果選舉打成平局,每個成員都需要等待30秒才能開始下一次選舉。所以,如果有太多錯誤發生的話,選舉可能會花費幾分鐘的時間。

4.回滾

  根據上一節講述的選舉過程,如果主節點執行了一個寫請求之後掛了,但是備份節點還沒來得及複製這次操作,那麼新選舉出來的主節點就會漏掉這次寫操作。假如有兩個資料中心,其中一個數據中心擁有一個主節點和一個備份節點,另一個數據中心擁有三個備份節點,如圖10-2所示。

  如果這兩個資料中心之間出現了網路故障,如圖10-3所示。其中左邊第一個資料中心最後的操作是126,但是126操作還沒有被複制到另邊的資料中心。

  右邊的資料中心仍然滿足副本集“大多數”的要求(一共5臺伺服器,3臺即可滿足要求)。因此,其中一臺伺服器會被選舉成為新的主節點,這個新的主節點會繼續處理後續的寫入操作,如圖10-4所示。

  網路恢復之後,左邊資料中心的伺服器就會從其他伺服器開始同步126之後的操作, 但是無法找到這個操作。這種情況發生的時候,A和B會進入回滾(rollback)過程。回滾會將失敗之前未複製的操作撤消。擁有126操作的伺服器會在右邊資料中心伺服器的oplog中尋找共同的操作點。之後會定位到125操作,這是兩個資料中心相匹配的最後一個操作。圖10-5顯示了 oplog的情況。

  這時,伺服器會査看這些沒有被複制的操作,將受這些操作影響的文件寫入一個.bson檔案,儲存在資料目錄下的rollback目錄中。如果126是一個更新操作,伺服器會將被126更新的文件寫入collectionName.bson檔案。然後會從當前主節點中複製這個文件。

  下面是一次典型的回滾過程產生的日誌:

Fri Oct    7    06:30:35    [rsSync]    replSet    syncing to:    server-1
Fri Oct    7    06:30:35    [rsSync]    replSet    our last    op    time written: Oct 7 06:30:05:3
Fri Oct 7 06:30:35 [rsSync] replset source's GTE: Oct 7 06:30:31:1 
Fri Oct    7    06:30:35    [rsSync]    replSet    rollback    0
Fri Oct    7    06:30:35    [rsSync]    replSet    ROLLBACK
Fri Oct    7    06:30:35    [rsSync]    replSet    rollback    1
Fri Oct    7    06:30:35    [rsSync]    replSet    rollback    2 findCommonPoint
Fri Oct 7 06:30:35 [rsSync] replSet info rollback our last optime: Oct 7 06:30:05:3
Fri Oct 7 06:30:35 [rsSync] replSet info rollback their last optime: Oct 7 06:30:31:2
Fri Oct 7 06:30:35 [rsSync] replSet info rollback diff in end of log times:-26 seconds
Fri Oct 7 06:30:35 [rsSync] replSet rollback found matching events at Oct 7 06:30:03:4118
Fri Oct    7    06:30:35    [rsSync]    replSet    rollback findcommonpoint    scanned    :    6
Fri Get    7    06:30:35    [rsSync]    replSet    replSet rollback 3 fixup
Fri Oct    7    06:30:35    [rsSync]    replSet    rollback 3.5
Fri Oct    7    06:30:35    [rsSync]    replSet    rollback 4 n:3
Fri Oct    7    06:30:35    [rsSync]    replSet    minvalid=0ct 7 06:30:31    4e8ed4c7:2
Fri    Oct    7    06:30:35    [rsSync]    replSet    rollback    4.6
Fri    Oct    7    06:30:35    [rsSync]    replSet    rollback    4.7
Fri    Oct    7    06:30:35    [rsSync]    replSet    rollback    5 d:6 u:0
Fri    Oct    7    06:30:35    [rsSync]    replSet    rollback    6
Fri    Oct    7    06:30:35    [rsSync]    replSet    rollback    7
Fri    Oct    7    06:30:35    [rsSync]    replSet    rollback    done
Fri    Oct    7    06:30:35    [rsSync]    replSet    RECOVERING
Fri    Oct    7    06:30:36    [rsSync]    replSet    syncing to:    server-1
Fri    Oct    7    06:30:36    [rsSync]    replSet    SECONDARY

  伺服器開始從另一個成員進行同步(在本例中是server-1),但是發現無法在同步源中找到自己的最後一次操作。這時,它就會切換到回滾狀態("replSetROLLBACK")進行回滾。

  第2步,伺服器在兩個oplog中找到一個共同的點,是26秒之前的一個操作。然後伺服器就會將最近26秒內執行的操作從oplog中撤銷。回滾完成之後,伺服器就進入RECOVERING狀態開始進行正常同步。

  如果要將被回滾的操作應用到當前主節點,首先使用mongorestore命令將它們載入到一個臨時集合:

$ mongorestore --db stage --collection stuff \
>  /data/db/rollback/important.stuff.2012-12-19T18-27-14.0.bson

  現在應該在shell中將這些文件與同步後的集合進行比較。例如,如果有人在被回滾的成員上建立了一個“普通”索引,而當前主節點建立了一個唯一索引,那麼就需要確保被回滾的資料中沒有重複文件,如果有的話要去除重複。

  如果希望保留staging集合中當前版本的文件,可以將其載入主集合:

>  staging.stuff.find().forEach(function(doc) {
…            prod.stuff.insert(doc);
...})

  對於只允許插入的集合,可以直接將被回滾的文件插入主集合。但是,如果是在集合上執行更新操作,在合併回滾資料時就要非常小心地對待。

  一個經常會被誤用的成員配置選項是設定每個成員的投票數量。改變成員的投票數量通常不會得到想要的結果,而且很可能會導致大量的回滾操作(所以上一章的成員屬性列表中沒有介紹這個選項)。除非做好了定期處理回滾的準備,否則不要改變成員的投票數量。

  第11章會講述如何阻止回滾。

4.1 如果回滾失敗

  某些情況下,如果要回滾的內容太多,MongoDB可能承受不了。如果要回滾的資料量大於300 MB,或者要回滾30分鐘以上的操作,回滾就會失敗。對於回滾失畋的節點,必須要重新同步。

  這種情況最常見的原因是備份節點遠遠落後於主節點,而這時主節點卻掛了。如果其中一個備份節點成為主節點,這個主節點與舊的主節點相比,缺少很多操作。為了保證成員不會在回滾中失敗,最好的方式是保持備份節點的資料儘可能最新。

作者:小家電維修

相見有時,後會無期。