天天看點

MongoDB複制集原理

mongodb複制集由一組mongod執行個體(程序)組成,包含一個primary節點和多個secondary節點,mongodb driver(用戶端)的所有資料都寫入primary,secondary從primary同步寫入的資料,以保持複制集内所有成員存儲相同的資料集,提供資料的高可用。

下圖(圖檔源于mongodb官方文檔)是一個典型的mongdb複制集,包含一個primary節點和2個secondary節點。

MongoDB複制集原理

複制集通過replsetinitiate指令(或mongo shell的rs.initiate())進行初始化,初始化後各個成員間開始發送心跳消息,并發起priamry選舉操作,獲得『大多數』成員投票支援的節點,會成為primary,其餘節點成為secondary。

初始化複制集

『大多數』的定義

假設複制集内投票成員(後續介紹)數量為n,則大多數為 n/2 + 1,當複制集記憶體活成員數量不足大多數時,整個複制集将無法選舉出primary,複制集将無法提供寫服務,處于隻讀狀态。

投票成員數

大多數

容忍失效數

1

2

3

4

5

6

7

通常建議将複制內建員數量設定為奇數,從上表可以看出3個節點和4個節點的複制集都隻能容忍1個節點失效,從『服務可用性』的角度看,其效果是一樣的。(但無疑4個節點能提供更可靠的資料存儲)

正常情況下,複制集的seconary會參與primary選舉(自身也可能會被選為primary),并從primary同步最新寫入的資料,以保證與primary存儲相同的資料。

secondary可以提供讀服務,增加secondary節點可以提供複制集的讀服務能力,同時提升複制集的可用性。另外,mongodb支援對複制集的secondary節點進行靈活的配置,以适應多種場景的需求。

arbiter節點隻參與投票,不能被選為primary,并且不從primary同步資料。

比如你部署了一個2個節點的複制集,1個primary,1個secondary,任意節點當機,複制集将不能提供服務了(無法選出primary),這時可以給複制集添加一個arbiter節點,即使有節點當機,仍能選出primary。

arbiter本身不存儲資料,是非常輕量級的服務,當複制內建員為偶數時,最好加入一個arbiter節點,以提升複制集可用性。

priority0節點的選舉優先級為0,不會被選舉為primary

比如你跨機房a、b部署了一個複制集,并且想指定primary必須在a機房,這時可以将b機房的複制內建員priority設定為0,這樣primary就一定會是a機房的成員。(注意:如果這樣部署,最好将『大多數』節點部署在a機房,否則網絡分區時可能無法選出primary)

mongodb 3.0裡,複制內建員最多50個,參與primary選舉投票的成員最多7個,其他成員(vote0)的vote屬性必須設定為0,即不參與投票。

hidden節點不能被選為主(priority為0),并且對driver不可見。

因hidden節點不會接受driver的請求,可使用hidden節點做一些資料備份、離線計算的任務,不會影響複制集的服務。

delayed節點必須是hidden節點,并且其資料落後與primary一段時間(可配置,比如1個小時)。

因delayed節點的資料比primary落後一段時間,當錯誤或者無效的資料寫入primary時,可通過delayed節點的資料來恢複到之前的時間點。

primary與secondary之間通過oplog來同步資料,primary上的寫操作完成後,會向特殊的local.oplog.rs特殊集合寫入一條oplog,secondary不斷的從primary取新的oplog并應用。

因oplog的資料會不斷增加,local.oplog.rs被設定成為一個capped集合,當容量達到配置上限時,會将最舊的資料删除掉。另外考慮到oplog在secondary上可能重複應用,oplog必須具有幂等性,即重複應用也會得到相同的結果。

如下oplog的格式,包含ts、h、op、ns、o等字段

ts: 操作時間,目前timestamp + 計數器,計數器每秒都被重置

h:操作的全局唯一辨別

v:oplog版本資訊

op:操作類型

i:插入操作

u:更新操作

d:删除操作

c:執行指令(如createdatabase,dropdatabase)

n:空操作,特殊用途

ns:操作針對的集合

o:操作内容,如果是更新操作

o2:操作查詢條件,僅update操作包含該字段

secondary初次同步資料時,會先進行init sync,從primary(或其他資料更新的secondary)同步全量資料,然後不斷通過tailable cursor從primary的local.oplog.rs集合裡查詢最新的oplog并應用到自身。

init sync過程包含如下步驟

t1時間,從primary同步所有資料庫的資料(local除外),通過listdatabases + listcollections + clonecollection敏指令組合完成,假設t2時間完成所有操作。

從primary應用[t1-t2]時間段内的所有oplog,可能部分操作已經包含在步驟1,但由于oplog的幂等性,可重複應用。

根據primary各集合的index設定,在secondary上為相應集合建立index。(每個集合_id的index已在步驟1中完成)。

oplog集合的大小應根據db規模及應用寫入需求合理配置,配置得太大,會造成存儲空間的浪費;配置得太小,可能造成secondary的init sync一直無法成功。比如在步驟1裡由于db資料太多、并且oplog配置太小,導緻oplog不足以存儲[t1, t2]時間内的所有oplog,這就secondary無法從primary上同步完整的資料集。

當需要修改複制集時,比如增加成員、删除成員、或者修改成員配置(如priorty、vote、hidden、delayed等屬性),可通過replsetreconfig指令(rs.reconfig())對複制集進行重新配置。

比如将複制集的第2個成員priority設定為2,可執行如下指令

primary選舉除了在複制集初始化時發生,還有如下場景

複制集被reconfig

secondary節點檢測到primary當機時,會觸發新primary的選舉

當有primary節點主動stepdown(主動降級為secondary)時,也會觸發新的primary選舉

primary的選舉受節點間心跳、優先級、最新的oplog時間等多種因素影響。

複制內建員間預設每2s會發送一次心跳資訊,如果10s未收到某個節點的心跳,則認為該節點已當機;如果當機的節點為primary,secondary(前提是可被選為primary)會發起新的primary選舉。

每個節點都傾向于投票給優先級最高的節點

優先級為0的節點不會主動發起primary選舉

當primary發現有優先級更高secondary,并且該secondary的資料落後在10s内,則primary會主動降級,讓優先級更高的secondary有成為primary的機會。

擁有最新optime(最近一條oplog的時間戳)的節點才能被選為主。

隻有更大多數投票節點間保持網絡連通,才有機會被選primary;如果primary與大多數的節點斷開連接配接,primary會主動降級為secondary。當發生網絡分區時,可能在短時間内出現多個primary,故driver在寫入時,最好設定『大多數成功』的政策,這樣即使出現多個primary,也隻有一個primary能成功寫入大多數。

預設情況下,複制集的所有讀請求都發到primary,driver可通過設定read preference來将讀請求路由到其他的節點。

primary: 預設規則,所有讀請求發到primary

primarypreferred: primary優先,如果primary不可達,請求secondary

secondary: 所有的讀請求都發到secondary

secondarypreferred:secondary優先,當所有secondary不可達時,請求primary

nearest:讀請求發送到最近的可達節點上(通過ping探測得出最近的節點)

預設情況下,primary完成寫操作即傳回,driver可通過設定[write concern(https://docs.mongodb.org/manual/core/write-concern/)來設定寫成功的規則。

如下的write concern規則設定寫必須在大多數節點上成功,逾時時間為5s。

上面的設定方式是針對單個請求的,也可以修改副本集預設的write concern,這樣就不用每個請求單獨設定。

當primary當機時,如果有資料未同步到secondary,當primary重新加入時,如果新的primary上已經發生了寫操作,則舊primary需要復原部分操作,以保證資料集與新的primary一緻。

舊primary将復原的資料寫到單獨的rollback目錄下,資料庫管理者可根據需要使用mongorestore進行恢複。