副本集(Replica Set)是一組MongoDB實例組成的集群,由一個主(Primary)服務器和多個備份(Secondary)服務器構成。通過Replication,將數據的更新由Primary推送到其他實例上,在一定的延遲之后,每個MongoDB實例維護相同的數據集副本。通過維護冗余的數據庫副本,能夠實現數據的異地備份,讀寫分離和自動故障轉移。
一,MongoDB版本和環境
在Windows上創建包含三個節點的副本集,使用的環境:
- 數據庫:MongoDB 版本 3.2.9
- Server 環境:Windows Server 2012 R2
- 可視化編程環境:Robomongo 版本 0.08.4
- 三台Windows Server:srv1,srv2,srv3
二,拓撲結構分析
創建一個Replica Set,包含三個成員:一個Primary 成員和兩個Secondary 成員,Primary用於處理客戶端請求,Secondary用於保存Primary的數據副本。客戶端Application 在 Primary 節點上進行讀寫操作,通過Replication的異步同步機制,將數據操作同步到 Secondary 成員,在一定的延遲之后,三個成員擁有相同的數據集。
理論上,每個成員可以分布在不同的數據中心機房內,這些數據中心可能相距甚遠,實現數據的異地備份;可以設置Primary 節點只負責寫入操作,而使Secondary節點負責讀取操作,實現數據集的讀寫分離;如果Primary 連接中斷超過10s,其他節點會自動選舉出一個Primary 節點,負責響應客戶端Application的請求,實現數據的自動故障轉移。
三,技術原理說明
1,以Replica Set模式啟動MongoDB實例
MongoDB Instance有兩種不同的啟動方式:單機模式(Standalone)和副本集模式(Replica Set),在啟動mongod時,如果設置 replSet 參數,那么MongoDB 實例以副本集模式啟動;如果不設置replSet 參數,那么MongoDB以單機模式啟動。
單機實例是指運行在服務器上的一個mongod 進程,該進程不是任何一個Replica Set的成員,因此,單機實例不能自動故障轉移,在產品環境中,風險很高,如果服務器崩潰了,客戶端App至少在一段時間內不可訪問,如果硬件出現問題,可能會造成數據的永久丟失。建議,使用Replica Set,至少保留兩份數據集副本。
2,選舉Primary成員
在Replica Set中有兩種成員:Primary成員和Secondary成員,一個Replica Set只能有一個Primary成員,但可以有多個Secondary成員。Primary用於處理客戶端請求,Secondary用於保存Primary的數據副本。如果Primary崩潰了,Replica Set探測到Primary不可訪問,將啟動自動故障轉移進程,從剩下的Secondary成員中,投票選舉出一個成員作為Primary,接收和處理客戶端的請求。
選舉Primary成員時,使用“大多數”和“一票否決”原則。在Replica Set中,每個成員只能要求自己被選舉為Primary節點。當一個Secondary成員無法與Primary成員連通時,該成員就會發起選舉,請求其他成員將自己選舉為Primary成員,只有得到“大多數”成員的支持,該成員才能被選舉為Primary成員;只要有一個成員否決,選舉就會取消。
不是每一個成員都有投票選舉的權利,在一個Replica Set中,最多有7個成員用於投票選舉的權利,Primary成員是由這7個成員選舉出來的。有投票權利的成員,其屬性:"votes" 是1;若為0,表示該成員沒有投票權利。
3,操作日志
MongoDB使用操作日志(oplog)來實現復制(Replication)功能,oplog包含了Primary成員的每一個更新操作,通過將oplog傳遞到其他Secondary成員中,在其他成員中重做(redo)已經提交的操作,實現數據的異步同步。Replica Set中的每個成員都維護着自己的oplog,記錄着每一個從Primary成員復制操作的數據。復制操作的過程是先復制數據,再將操作寫入到oplog中。如果某一個成員在執行操作時失敗,當該成員重啟之后,自動從oplog中最后一個操作進行同步。由於復制操作的過程是先復制數據,再寫入oplog,該成員可能會在已經同步的數據上再次執行復制操作,MongoDB在設計oplog時,就考慮到這種情況:將oplog中的同一個操作執行多次,與執行一次的結果是一樣的。
oplog保存的是對每個doc的更新操作日志,如果一個命令只更新一個doc,那么Replication進程向oplog插入一條日志;如果一個命令更新多個doc,那么Replication進程向oplog插入多條日志,每一條日志只更新一個doc。
oplog的大小是固定的,只能保存特定數量的操作日志,如果Primary成員更新的數據量特別大,oplog很快就被填滿,Secondary來不及同步數據,Primary成員就將oplog中的日志,這樣,Secondary成員就會變成陳舊的(Stale)。建議,讓Primary成員使用比較大的oplog,保存足夠多的操作日志。
四,創建配置文檔
1,創建mongod 啟動的配置文件
在每台server上創建配置文件,將配置文件存放在目錄C:\data\中。在同一個Replica Set中的所有成員必須有相同的Replica Set Name,這里設置為“rs0”。
--srv1 config_file_name:rs0_1.conf
dbpath=C:\data\db\db_rs0 logpath=C:\data\db\db_rs0\rs0_1.log port=40001 replSet=rs0 --srv2 config_file_name:rs0_2.conf dbpath=C:\data\db\db_rs0 logpath=C:\data\db\db_rs0\rs0_2.log port=40002 replSet=rs0 --srv3 config_file_name:rs0_3.conf dbpath=C:\data\db\db_rs0 logpath=C:\data\db\db_rs0\rs0_3.log port=40003 replSet=rs0
配置參數含義:
- replSet:設置Replica Set的name,在各個配置文件中,其值必須相同。
- dbpath:MongoDB用於存儲數據的目錄,默認值是C:\data\db
- logpath:用於記錄mongod的日志數據
- port:指定MongoDB監聽的端口,默認值是27017
2,以配置文件方式啟動mongod
一般情況下,mongod的參數值是不變的,將這些參數寫入到配置文件中,能夠簡化MongoDB的管理。
mongod 命令有參數:--config 或 -f,用於引用配置文件。
--srv1 mongod -f C:\data\rs0_1.conf --srv2 mongod -f C:\data\rs0_2.conf --srv3 mongod -f C:\data\rs0_3.conf
3,啟動mongo shell
在任意一台Server上打開三個mongo shell,使用參數 --host 指定Server Name,使用 --port 指定端口號。由於mongod沒有使用默認的監聽端口 27017,因此,必須使用 在mongo shell中使用 --port參數顯式指定監聽的Port。
--connect srv1
mongo --host srv1 --port 40001
--connect srv2 mongo --host srv2 --port 40002 --connect srv3 mongo --host srv3 --port 40003
五,配置Replica Set
在不同的Server上運行不同的MongoDB Instance,但是,每個mongod 都不知道其他mongod的存在,為了讓每個mongod能夠感知彼此的存在,需要配置Replica set,增加成員。
1,使用配置文檔為Replica Set 增加成員
在srv1的mongo shell中,創建配置文檔,調用rs.initiate()函數,按照配置文檔來初始化Replica Set。
conf=
{
"_id" : "rs0", "members" : [ { "_id" : 0, "host" : "srv1:40001" }, { "_id" : 1, "host" : "srv2:40002" }, { "_id" : 2, "host" : "srv3:40003" } ] }
rs.initiate(conf)
在配置doc中,使用"_id" : "rs0" 指定Replica Set的name,members數組指定 Replica Set的成員的ID 和 host(“host:port”)。等到所有成員配置完成之后,Replica Set 會自動選舉出一個Primary 節點,兩個Secondary 節點。在Primary 節點上進行更新操作,就能同步到Secondary 節點了。
2,修改Replica Set
如果以rs.initiate()方式初始化Replica Set,那么MongoDB以默認配置文檔初始化Replica Set,可以通過add()函數增加成員。
2.1 向Replica Set中增加一個成員
rs.add("host:port")
2.2 從Replica Set中刪除一個成員
rs.remove("host")
2.3 查看Replica Set的配置
rs.conf()
2.4 重新配置Replica Set
var conf=rs.conf()
conf.members[1].priority =5
--at primary member
rs.reconf(conf)
--at secondary member rs.reconf(conf,{force:true})
2.5 查看Replica Set的狀態
rs.status()
六,維護Replica Set
1,查看Replica Set的配置信息
rs.conf()
配置文檔主要分為三塊:Replica Set 的ID和 Version,Members數組 和 Settings,下面是經過簡化的配置信息。
{
"_id" : "rs0", "version" : 202997, "members" : [ { "_id" : 1, "host" : "srv1:40001", "arbiterOnly" : false, "hidden" : false, "priority" : 5, "votes" : 1 }, {...} ], "settings" : { ..... } }
Replica Set的ID字段唯一標識一個Replica Set,每一個Replica Set都有一個自增的版本號,由Version字段標識,標識Replica Set的不同版本。version字段的初始值是1,每次修改Replica Set的配置時,version字段都會自增。
Settings 字段的值是應用到Replica Set中所有成員的配置信息。
最關鍵的是members數組的字段,標識每個成員的配置信息。
- arbiterOnly:0或1,標識一個仲裁(arbiter),Arbiter的唯一作用是參與Primary的選舉,Arbiter不保存數據,不會為client提供服務,它存在的意義就是為了選舉Primary。
- hidden:0或1,表示該成員是不是隱藏成員,Hidden成員的主要作用是備份數據,可以使用性能較差的服務器作為Hidden成員。Hidden成員不會接收Client的請求,也不會成為Primary。在設置Hidden成員時,必須設置members[n].priorty屬性為0;
- priority:數值類型,用於設置成員成為Primary的優先級。priority越高的成員,越有機會成為Primary。如果priority=0,那么該成員永遠不會成為Primary。
- votes:1或0,表示該成員的投票的數量,在每個Replica Set中,最多有7個成員,其votes屬性值是1。votes 屬性是1的成員(voting members)擁有選舉Primary的權利。一個成員要想成為一個Primary,那么必須獲得voting members的大多成員的支持。
在Replica Set中,如果voting members的數量是5,那么一個成員成為Primary的條件是:獲得超過2個voting members的支持,並且沒有任何voting members 反對。只要有任意一個voting member 反對該成員成為Primary,那么該成員就不能成為Primary。
2,強制一個成員成為Primary
如果將一個成員的priority屬性在Replica Set的所有成員中是最高的,那么該成員最有可能成為Primary。
將成員0的priority設置5,其他成員的priority設置為1,這樣,成員0成為Primary的優先級是最高的。
cfg = rs.conf()
cfg.members[0].priority = 5 cfg.members[1].priority = 1 cfg.members[2].priority = 1 rs.reconfig(cfg)
七,測試數據
1,在Primary上讀寫數據
db.users.insert({_id:1,name:"a",age:24})
2,在Secondary上讀取數據
默認情況下,客戶端不能從Secondary成員中讀取數據。在Secondary成員上顯式執行setSlaveOk之后,才能從Secondary節點讀取數據。
rs.setSalveOk()
db.users.find({_id:1})
八,查看mongod 服務器的命令行參數
db.serverCmdLineOpts()
/* 0 */ { "argv" : [ "mongod", "-f", "C:\\data\\rs0_1.conf" ], "parsed" : { "config" : "C:\\data\\rs0_1.conf", "net" : { "port" : 40001 }, "replication" : { "replSet" : "rs0" }, "storage" : { "dbPath" : "C:\\data\\db\\db_rs0" }, "systemLog" : { "destination" : "file", "path" : "C:\\data\\db\\db_rs0\\rs0_1.log" } }, "ok" : 1 }
參考doc: