MongoDB的集群模式有三種:
- 主從(Master-Slaver),MongoDB 3.6徹底廢棄
- 副本集(Replica Set)
- 分片(Sharding)
本章主要講述副本集(Replica Set)。
一、副本集介紹
1.1 什么是副本集
MongoDB 副本集是將數據同步在多個服務器的過程,復制提供了數據的冗余備份,并在多個服務器上存儲數據副本,提高了數據的可用性, 并可以保證數據的安全性,同時還允許從硬件故障和服務中斷中恢復數據。
1.2 什么是 Oplog
Oplog
是MongoDB Primary(主節點)和Secondary(從節點)在副本集建立期間和建立完成之后的復制介質。Primary中所有的寫入操作都會記錄到Oplog
中,然后從節點會來主節點拉取Oplog
并應用到自己的節點上。這里的Oplog
是MongoDB local數據庫的一個集合,它是Capped collection,通俗意思就是它是固定大小,循環使用的。如下圖:
Oplog
中的內容如下:
{
"ts" : Timestamp(1446011584, 2),
"h" : NumberLong("1687359108795812092"),
"v" : 2,
"op" : "i",
"ns" : "test.nosql",
"o" : { "_id" : ObjectId("563062c0b085733f34ab4129"), "name" : "mongodb", "score" : "100" }
}
- ts: 操作時間,當前timestamp + 計數器,計數器每秒都被重置
- h:操作的全局唯一標識
- v:oplog版本信息
- op:操作類型
- i:插入操作
- u:更新操作
- d:刪除操作
- c:執行命令(如createDatabase,dropDatabase)
- n:空操作,特殊用途
- ns:操作針對的集合
- o:操作內容,如果是更新操作
- o2:操作查詢條件,僅update操作包含該字段
1.2 副本集的結構與原理
如上圖所示,副本集由一組mongo實例組成,提供了數據冗余與高可用性。相對于主從模式,該模式可以在主節點掛掉的時候通過選舉算法自動選舉出新的主節點,保證服務的可用性。
該模式由三個角色組成:
- primary: 主節點,是唯一能夠接收寫請求的節點。一旦主節點不可用,會選出新的主節點
- secondaries: 從節點,提供數據備份和讀功能,并且能在主節點掛掉的時候被選舉為新的主節點。
- arbiter: 投票節點或者叫仲裁節點。該角色是可選的,所以圖上也沒有畫出來。投票節點其本身并不包含數據集,也無法被升級為主節點,但是,一旦當前的主節點不可用時,投票節點就會參與到新的主節點選舉的投票中。投票節點僅在副本集成員為偶數個的時候需要,如果在擁有奇數個復制集成員的復制集中新增了一個投票節點,復制集可能會遇到選舉僵局。
主節點上能夠完成讀寫操作,從節點僅能用于讀操作。主節點需要記錄所有改變數據庫狀態的操作,這些記錄保存在oplog
中,各個從節點通過此oplog
來復制數據并應用于本地,保持本地的數據與主節點的一致。oplog
具有冪等性,即無論執行幾次其結果一致。
集群中的各節點還會通過傳遞心跳信息來檢測各自的健康狀況。當主節點故障時,多個從節點會觸發一次新的選舉操作,并選舉其中的一個成為新的主節點(通常誰的優先級更高,誰就是新的主節點),心跳信息默認每 2 秒傳遞一次。
客戶端連接到副本集后,不關心具體哪一臺機器是否掛掉。主服務器負責整個副本集的讀寫,副本集定期同步數據備份。一旦主節點掛掉,副本節點就會選舉一個新的主服務器。這一切對于應用服務器不需要關心。
副本集中的副本節點在主節點掛掉后通過心跳機制檢測到后,就會在集群內發起主節點的選舉機制,自動選舉出一位新的主服務器。
MongoDB官方建議副本集至少需要三個節點。3.0之前最多12個節點,3.0開始節點數量能夠達到50個。限制副本節點的數量,主要是因為一個集群中過多的副本節點,增加了復制的成本,反而拖累了集群的整體性能。太多的副本節點參與選舉,也會增加選舉的時間。而官方建議奇數的節點,是為了避免選舉僵局的發生(平局)。
1.3 副本集數據同步
Primary節點寫入數據,Secondary通過讀取Primary的oplog得到復制信息,開始復制數據并且將復制信息寫入到自己的oplog。如果某個操作失敗,則備份節點停止從當前數據源復制數據。如果某個備份節點由于某些原因掛掉了,當重新啟動后,就會自動從oplog的最后一個操作開始同步,同步完成后,將信息寫入自己的oplog,由于復制操作是先復制數據,復制完成后再寫入oplog,有可能相同的操作會同步兩份,不過MongoDB在設計之初就考慮到這個問題,將oplog的同一個操作
執行多次,與執行一次的效果是一樣的。簡單的說就是:
當Primary節點完成數據操作后,Secondary會做出一系列的動作保證數據的同步:
- 檢查自己local庫的oplog.rs集合找出最近的時間戳。
- 檢查Primary節點local庫oplog.rs集合,找出大于此時間戳的記錄。
- 將找到的記錄插入到自己的oplog.rs集合中,并執行這些操作。
副本集的同步和主從同步一樣,都是異步同步的過程,不同的是副本集有個自動故障轉移的功能。其原理是:Secondary端從primary端獲取日志,然后在自己身上完全順序的執行日志所記錄的各種操作(該日志是不記錄查詢操作的),這個日志就是local數據 庫中的oplog.rs表,默認在64位機器上這個表是比較大的,占磁盤大小的5%,oplog.rs的大小可以在啟動參數中設定:--oplogSize 1000,單位是M。
1.4 何時觸發選舉
MongoDB 在下面幾個條件觸發之下進行選舉:
- 初始化副本集時;
- 備份節點無法和主節點通訊時(可能主節點宕或網絡原因);
- Primary 手動降級,rs.stepDown(sec),默認 60s。
選舉的步驟如下:
- 得到每個服務器節點的最后操作時間戳。每個 mongodb 都有 oplog 機制會記錄本機的操作,方便和主服務器進行對比數據是否同步還可以用于錯誤恢復;
- 如果集群中大部分服務器宕機了,保留活著的節點都為 secondary 狀態并停止選舉;
- 如果集群中選舉出來的主節點或者所有從節點最后一次同步時間看起來很舊了,停止選舉等待人工操作;
- 如果上面都沒有問題就選擇最后操作時間戳最新(保證數據最新)的服務器節點作為主節點。
二、示例
副本集的啟動,和從節點加入到副本集的操作,只能在主節點上完成,但是主節點隨時可以轉移到其他從節點。
啟動一個副本集系統的基本步驟如下:
- 在主節點啟動 mongod 服務時,可以使用 --replSet 啟動副本集,同時指定副本集名稱;
- 連接主節點,初始化新的副本集;
- 啟動各個從節點 mongod 服務,同時這些從節點也必須初始化副本集,使用同一個副本集 id;
- 在主節點將這些從節點加入主節點副本集;
下面演示 3 個 mongodb 創建一個副本集系統:
1)主節點啟動 mongod 服務
$ mongod --port 27017 --dbpath "/usr/local/mongodb/data" --replSet simon
simon 為 副本集ID
2) 2 個從節點分別啟動 mongod 服務
$ mongod --port 27017 --dbpath "/usr/local/mongodb/data" --replSet simon
3)連接到主節點 mongodb,進行副本集的初始化和配置
$ mongo
> rs.initiate() # 初始化的副本集
> rs.add("192.168.10.58:27107") # 加入從節點,該節點為 192.168.10.58 主機 27107 端口上的 mongod 服務
> rs.add("192.168.10.138:27107") # 加入從節點,該節點為 192.168.10.138 主機 27107 端口上的 mongod 服務
也可以這樣設置
$ mongo
>rs.initiate({"_id":"simon","members":[
{
"_id":1,
"host":"192.168.10.58:27017",
"priority":1
},
{
"_id":2,
"host":"192.168.10.138:27017",
"priority":1
}
]
}
)
-
_id
: 副本集的名稱 -
members
: 副本集的服務器列表 -
_id
: 服務器的唯一ID -
host
: 服務器主機 -
priority
: 是優先級,默認為1,優先級0為被動節點,不能成為活躍節點。優先級不位0則按照有大到小選出活躍節點 -
arbiterOnly
: 仲裁節點,只參與投票,不接收數據,也不能成為活躍節點。
此時一個副本系統已經完成,所有操作直接面向主節點,實際操作由整個副本集集群提供,默認是從主節點讀取
查看副本集信息
> rs.conf() # 查看副本集的配置
> rs.status() # 查看副本集狀態
> rs.isMaster() # 查看該主機節點是否為副本集主節點
設置從節點讀寫策略
> rs.getMongo().setReadPref(STRATEGY)
具體策略如下:
- Primary 從主節點讀取;
- secondary 從從節點讀取;
- nearest 從網絡延遲最小的節點讀取;
- primaryPreferred 基本上從主節點讀取,主節點不可用時,從從節點的讀取;
- secondaryPreferred 基本上從從節點讀取,從節點不可用時,從主節點讀取;