MongoDB權威指南 第九章 複製 MongoDB的複製功能很重要,尤爲是如今的存儲引擎還不支持單擊持久性。不只能夠用複製來應對故障切換,數據集成,還能夠作讀擴展,熱備份或做爲離線批處理的數據源。 9.1 主從複製 主從複製是MongoDB最經常使用的複製方式。可用於備份,故障恢復和讀擴展等。 基本就是搭建一個主節點和一個或多個從節點,每一個從節點須要知道主節點的地址。運行mongod --master啓動主服務器。運行mongod --slave --source master_address啓動從服務器。 [root@test02 ~]# mongod --fork --dbpath /data/node2 --logpath /data/mongodb.log --port 10001 --logappend --master 從節點選擇不一樣的目錄和端口,而且用--source爲從節點指明主節點的地址。 [root@test02 ~]# mongod --fork --dbpath /data/node3 --logpath /data/mongodb2.log --port 10002 --logappend --slave --source localhost:10001 全部的從節點都是從主節點複製信息,目前還不能從節點到從節點複製機制,緣由是從節點沒有本身的oplog。 一個集羣中從節點沒有明確的限制,可是多個節點對單點主機發起的查詢也是吃不消的,不超過12個節點的集羣能夠良好運轉。 9.1.1 選項 --only 在從節點上指定只複製特定某個數據庫(默認複製全部數據庫)。 --slavedelay 用在從節點上,當應用主節點的操做時增長延遲。這樣能夠輕鬆設置延時從節點了,這樣的節點對於用戶無心間刪除重要數據或插入垃圾數據起到防禦做用。經過延緩操做,能夠有個恢復時間差。 --fastsync 以主節點的數據快照爲基礎啓動從節點。若是數據目錄一開始時主節點的數據快照,從節點用這個選項啓動要比作完整同步塊不少。 --autoresync 若是主節點和從節點不一樣步,能夠自動同步了。 --oplogsuze 主節點oplog的大小(單位是MB)。 9.1.2 添加以及刪除源 cat >> /etc/hosts <<EOF 192.168.27.212 test02 192.168.27.213 test03 192.168.27.214 test01 EOF 啓動從節點時能夠用--source指定主節點,也能夠在shell中配置這個源。 [root@test02 ~]# mongod --fork --dbpath /data/node3 --logpath /data/mongodb.log --port 10003 --logappend --slave 將192.168.27.212:10001做爲源插入到從節點上。 > db.sources.insert({ "host" : "192.168.27.212:10001"}); 當即查詢會獲得插入的文檔: > use local switched to db local > db.sources.find(); { "_id" : ObjectId("530be5049ab1ad709cfe66b7"), "host" : "test02:10001" 當同步完成後,文檔更新: > db.sources.find(); { "_id" : ObjectId("530bf0ab058022d91574c79c"), "host" : "test02:10001", "source" : "main", "syncedTo" : Timestamp(1393291443, 1), "dbsNextPass" : { "foo" : true, "test" : true } } 9.2 副本集 副本集就是有自動故障恢復功能的主從集羣。主從集羣和副本集最爲明顯的區別就是副本集沒有固定的主節點:整個集羣會選舉出一個主節點,當其不能工做時,則變動到其它節點。副本集總會有一個活躍節點和一個或多個備份節點。 副本集最好的優勢就是全自動化的。 mongod --fork --dbpath /data/node2 --logpath /data/mongodb.log --port 10001 --logappend --replSet myrepl/test03:10002 mongod --fork --dbpath /data/node3 --logpath /data/mongodb.log --port 10002 --logappend --replSet myrepl/test02:10001 副本集的亮點是自檢測功能:在其中指定單臺服務器後,MongoDB會自動搜索並鏈接其他的節點。 啓動幾臺服務器後,日誌會告訴你副本集沒有初始化。須要在shell中初始化副本集。 鏈接任意一個服務器。初始化命令只執行一次: > db.runCommand({"replSetInitiate" : { ... "_id" : "myrepl", ... "members" : [ ... { ... "_id" : 1, ... "host" : "test02:10001" ... }, ... { ... "_id" : 2, ... "host" : "test03:10002" ... } ... ]}}) { "startupStatus" : 4, "info" : "myrepl/test03:10002", "ok" : 0, "errmsg" : "all members and seeds must be reachable to initiate set" } "_id" : "myrepl" 副本集的名稱 "members" : [...] 副本集中的服務器列表,每一個服務器至少兩個鍵。 "_id" : N 每一個服務器惟一的ID "host" : hostname 這個鍵指定服務器主機 或者: config = {"_id" : "myrepl", "members" : [ {"_id" : 0, "host" : "test02:10001"}, {"_id" : 1, "host" : "test03:10002"} ]} rs.initiate(config); rs.status(); myrepl:SECONDARY> rs.status(); { "set" : "myrepl", "date" : ISODate("2014-02-25T02:17:39Z"), "myState" : 2, "syncingTo" : "test03:10002", "members" : [ { "_id" : 0, "name" : "test02:10001", "health" : 1, "state" : 2, "stateStr" : "SECONDARY", "uptime" : 968, "optime" : Timestamp(1393294457, 1), "optimeDate" : ISODate("2014-02-25T02:14:17Z"), "errmsg" : "syncing to: test03:10002", "self" : true }, { "_id" : 1, "name" : "test03:10002", "health" : 1, "state" : 1, "stateStr" : "PRIMARY", "uptime" : 48, "optime" : Timestamp(1393294457, 1), "optimeDate" : ISODate("2014-02-25T02:14:17Z"), "lastHeartbeat" : ISODate("2014-02-25T02:17:38Z"), "lastHeartbeatRecv" : ISODate("2014-02-25T02:17:39Z"), "pingMs" : 1, "syncingTo" : "test02:10001" } ], "ok" : 1 } 若是這時候把primary節點停掉,在secondary節點執行寫操做,就會發生以下錯誤提示: myrepl:SECONDARY> db.test.insert({name : "baobao"}); not master 若是隻有2臺Mongodb,配置複製集羣還不夠安全,須要1個外在角色調整各個節點的角色。 standard:常規節點,存儲一份完整的數據副本,參與選舉投票,可能稱爲活躍節點。 passive:存儲完整的數據副本,參與投票,不能成爲活躍節點。 arbiter:仲裁者只負責投票,不接受複製數據,也不能成爲活躍節點。 當Primary宕掉後,能夠經過Arbiter在Secodarys中選舉一個Primary節點,避免單點故障。 能夠增長一個仲裁節點,只負責仲裁,不作數據存儲。 mongod --fork --dbpath /data/node1 --logpath /data/mongodb.log --port 10003 --logappend --replSet myrepl/test02:10001,test03:10002 myrepl:PRIMARY> rs.addArb("test01:10003"); { "ok" : 1 } 查看各節點的狀態: myrepl:PRIMARY> rs.status(); { "set" : "myrepl", "date" : ISODate("2014-02-25T02:30:26Z"), "myState" : 1, "members" : [ { "_id" : 0, "name" : "test02:10001", "health" : 1, "state" : 1, "stateStr" : "PRIMARY", "uptime" : 1735, "optime" : Timestamp(1393295409, 1), "optimeDate" : ISODate("2014-02-25T02:30:09Z"), "self" : true }, { "_id" : 1, "name" : "test03:10002", "health" : 1, "state" : 2, "stateStr" : "SECONDARY", "uptime" : 204, "optime" : Timestamp(1393295409, 1), "optimeDate" : ISODate("2014-02-25T02:30:09Z"), "lastHeartbeat" : ISODate("2014-02-25T02:30:26Z"), "lastHeartbeatRecv" : ISODate("2014-02-25T02:30:24Z"), "pingMs" : 1, "syncingTo" : "test02:10001" }, { "_id" : 2, "name" : "test01:10003", "health" : 1, "state" : 6, "stateStr" : "UNKNOWN", "uptime" : 17, "lastHeartbeat" : ISODate("2014-02-25T02:30:25Z"), "lastHeartbeatRecv" : ISODate("1970-01-01T00:00:00Z"), "pingMs" : 1, "lastHeartbeatMessage" : "still initializing" } ], "ok" : 1 } 對比三個節點對自身節點性質的判斷: myrepl:PRIMARY> db.isMaster(); { "setName" : "myrepl", "ismaster" : true, "secondary" : false, "hosts" : [ "test03:10002", "test02:10001" ], "arbiters" : [ "test01:10003" ], "primary" : "test03:10002", "me" : "test03:10002", "maxBsonObjectSize" : 16777216, "maxMessageSizeBytes" : 48000000, "localTime" : ISODate("2014-02-25T02:32:29.760Z"), "ok" : 1 } myrepl:SECONDARY> db.isMaster(); { "setName" : "myrepl", "ismaster" : false, "secondary" : true, "hosts" : [ "test02:10001", "test03:10002" ], "arbiters" : [ "test01:10003" ], "primary" : "test03:10002", "me" : "test02:10001", "maxBsonObjectSize" : 16777216, "maxMessageSizeBytes" : 48000000, "localTime" : ISODate("2014-02-25T02:33:50.144Z"), "ok" : 1 } myrepl:SECONDARY> db.isMaster(); { "setName" : "myrepl", "ismaster" : false, "secondary" : true, "hosts" : [ "test02:10001", "test03:10002" ], "arbiters" : [ "test01:10003" ], "primary" : "test03:10002", "me" : "test02:10001", "maxBsonObjectSize" : 16777216, "maxMessageSizeBytes" : 48000000, "localTime" : ISODate("2014-02-25T02:33:50.144Z"), "ok" : 1 } 在節點配置中修改priority鍵,來配置成標準節點或者被動節點。 默認優先級爲1,能夠是0~1000. "arbiterOnly"鍵能夠指定仲裁節點。 備份節點會從活躍節點抽取oplog,並執行操做,就像活躍備份系統中的備份服務器同樣。活躍節點也會寫操做到本身的本地oplog,這樣就能成爲活躍節點了。oplog中的操做也包括嚴格遞增的序號。經過序號判斷數據的時效性。 9.2.3 故障切換和活躍節點的選舉 若是活躍節點壞了,其餘節點會選一個新的活躍節點。新的活躍節點由副本集中的大多數選舉出來。仲裁節點只負責投票,避免出現僵局。新的節點是優先級最高的節點。 活躍節點使用心跳來跟蹤集羣中多少節點對其可見,若是不超過半數,則活躍節點自動降爲備份節點。能夠防止活躍節點一直不放權。 不管活躍節點什麼時候變化,新活躍節點的數據被假定爲系統的最新數據。其餘節點的操做都會回滾,全部節點鏈接新的活躍節點後要從新同步。這些節點會查看本身的oplog,找出其中活躍節點沒有執行過的操做,而後向活躍節點請求這些操做影響的文檔的最新副本。 正在執行從新同步的節點被視爲恢復中,在完成這個過程前,不能成爲活躍節點候選者。 9.3 在從服務器上執行操做 從節點的主要做用是做爲故障恢復機制,以防止主節點數據丟失或者中止服務。 能夠在從節點作備份的數據源。也能夠用來擴展讀取性能,或者進行數據處理。 9.3.1 讀擴展 用MongoDB擴展讀取的一種方式就是將查詢放在從節點上,減輕主節點的負載。當負載是讀密集型時這樣很是不錯。當是寫密集型時,須要用自動分片來擴展。 使用從節點來擴展MongoDB的讀取有個要點,就是數據複製並不一樣步,就是在主節點插入或更新數據口,有片刻從節點的數據不是最新的。 擴展讀取須要打開一個特殊選項slaveOkey,告訴從服務器是否能夠處理請求。 若是直接在secondary上操做,會發生以下錯誤: myrepl:SECONDARY> db.test.find(); error: { "$err" : "not master and slaveOk=false", "code" : 13435 } 須要告知Mongodb集羣,從哪臺機器上進行讀操做: myrepl:SECONDARY> rs.slaveOk(); myrepl:SECONDARY> db.test.find(); { "_id" : ObjectId("530bfc79eee2c2ce39f9cd95"), "name" : "caoqing" } { "_id" : ObjectId("530bfd8f3627cb16c15dcb32"), "name" : "xiaobao" } 9.3.2 用從節點作數據處理 從節點的另一個服務就是做爲一種機制來減輕密集型處理的負載,或做爲聚合,避免影響主節點的性能。用--master啓動一個普通的從節點,同時使用--master和--slave矛盾。這意味着若是能對從節點進行寫入,像日常同樣查詢,就把它做爲一個主節點。從節點仍是會不斷的從主節點複製數據。這樣就能夠對從節點執行阻塞操做而不影響主節點的性能。 從節點第一次啓動時不能有正在複製的數據庫,若是有,數據庫就不能完成同步,只能更新。 用這種技術要保證不能對正在複製主節點數據的從節點上的數據庫執行寫入。從節點不能恢復這些操做,就不能正確的映射主節點。 9.4 工做原理 MongoDB的複製至少須要兩臺服務器或者節點,其中一個主節點,負責處理客戶端請求,其餘的都是從節點,負責映射主節點的數據。主節點記錄在其上的全部操做。 從節點按期輪詢主節點獲取這些操做,而後對數據副本執行這些操做。因爲和主節點執行了相同的操做,從節點就能保持和主節點的數據同步。 9.4.1 oplog 主節點的操做記錄成爲polog(operation log)。oplog存儲在一個特殊的數據庫裏,成爲local。oplog就在其中的oplog.$main集合裏面。oplog的每一個文檔都表明主節點執行的一個操做。 myrepl:PRIMARY> db.oplog.$main.help(); 查看oplog的內容: myrepl:PRIMARY> use local; switched to db local myrepl:PRIMARY> show collections; me oplog.rs replset.minvalid slaves startup_log system.indexes system.replset myrepl:PRIMARY> db.oplog.rs.find(); { "ts" : Timestamp(1393294283, 1), "h" : NumberLong(0), "v" : 2, "op" : "n", "ns" : "", "o" : { "msg" : "initiating set" } } { "ts" : Timestamp(1393294457, 1), "h" : NumberLong("-8949844291534979055"), "v" : 2, "op" : "i", "ns" : "test.test", "o" : { "_id" : ObjectId("530bfc79eee2c2ce39f9cd95"), "name" : "caoqing" } } { "ts" : Timestamp(1393294735, 1), "h" : NumberLong("677282438107403253"), "v" : 2, "op" : "i", "ns" : "test.test", "o" : { "_id" : ObjectId("530bfd8f3627cb16c15dcb32"), "name" : "xiaobao" } } { "ts" : Timestamp(1393295409, 1), "h" : NumberLong("5171944912929102944"), "v" : 2, "op" : "n", "ns" : "", "o" : { "msg" : "Reconfig set", "version" : 2 } } myrepl:PRIMARY> 文檔包含的鍵以下: ts 操做的時間戳。時間戳是一種內部類型,用於跟蹤操做執行的時間。有4字節的時間戳和4字節的遞增計數器構成。 op 操做類型,只有1字節代碼。 ns 執行操做的命名空間。 o 進一步指定要執行操做的文檔。 oplog只記錄改變數據庫狀態的操做。oplog只是做爲從節點和主節點保持數據同步的機制。 存儲在oplog裏的操做不是徹底和主節點的操做如出一轍的。這些操做在存儲以前先作等冪變換,這些操做能夠在從服務器端屢次執行,只要順序是對的,就不會有問題。 oplog在固定集合中,不能保證oplog不超過預先設定的大小。須要在建立mongodb服務時指定--oplogSize,參數指定oplog的大小。 通常64bit-linux,分配5%的剩餘空間,單位爲MB。 9.4.2 同步 從節點第一次啓動時,會對主節點數據進行完整的同步。從節點複製主節點上的每個數據,耗費資源大。同步完成後,從節點查詢主節點的oplog,並執行這些操做,保證數據是最新的。 若是從節點的操做被主節點落下太遠了,從節點就跟不上同步了,從節點發生宕機或者疲於應付讀取時,就會出現這種狀況,也會在執行完完整同步後出現這種狀況,由於oplog可能已經回滾一圈了。 從節點跟不上同步後,複製就會停下,從節點須要從新作完整的同步。能夠用{"resync" : 1}命令手動執行同步,也能夠在啓動從節點是使用--autoresync選項讓其自動同步。從新同步代價高昂,儘可能避免,方法就是配置足夠大的oplog。 9.4.3 複製狀態和本地數據庫 本地數據庫用來存放全部內部複製狀態,主節點和從節點都有。本地數據就是local,其內容不會被複制。能夠確保一盒MongoDB數據庫只有一個本地數據庫。 本地數據庫不限於存放MongoDB的內部狀態。若是有不想複製的文檔,也能夠放在本地數據庫的集合裏。 主節點上的複製狀態還包括從節點上的列表。這個列表存放在slaves集合中: myrepl:PRIMARY> db.slaves.find(); { "_id" : ObjectId("530bfbdc911eb0ac3bf2aa8b"), "config" : { "_id" : 1, "host" : "test03:10002" }, "ns" : "local.oplog.rs", "syncedTo" : Timestamp(1393295409, 1) } 從節點也在本地數據庫中存放狀態。在me集合中存放從節點的惟一標識符,在sources集合中存放源或節點的列表。 myrepl:SECONDARY> db.me.find(); { "_id" : ObjectId("530bfbdc911eb0ac3bf2aa8b"), "host" : "test03" } 主節點和從節點都跟蹤從節點的更新情況,這個是經過存放在"syncedTO"中的時間戳來完成的。 9.4.4 阻塞複製 開發者可使用getLastrror的'w'參數來確保數據的同步性。運行getLastError會進入阻塞狀態,直到N個服務器複製了最新的寫入操做爲止。 檢查本鏈接的上一次數據庫操做的錯誤。 myrepl:PRIMARY> db.runCommand("getlasterror") { "n" : 0, "lastOp" : Timestamp(0, 0), "connectionId" : 3525, "err" : null, "ok" : 1 } 指定"w"選項後,可使用"wtimeout"選項,表示以毫秒爲單位的超時。 阻塞複製會致使寫操做明顯變慢,尤爲是"w"的值比較大時。 9.5 管理 9.5.1 管理 MongoDB包含不少有用的管理工具,用以查看複製的狀態。 經過 db.printReplicationInfo()命令查看oplog狀態。 myrepl:PRIMARY> db.printReplicationInfo(); configured oplog size: 997.7892578125001MB log length start to end: 1126secs (0.31hrs) oplog first event time: Tue Feb 25 2014 10:11:23 GMT+0800 (CST) oplog last event time: Tue Feb 25 2014 10:30:09 GMT+0800 (CST) now: Wed Feb 26 2014 02:07:23 GMT+0800 (CST) 輸出信息包括oplog日誌的大小,操做日誌記錄的起始時間。 查看從庫同步狀態。 myrepl:PRIMARY> db.printSlaveReplicationInfo(); source: test03:10002 syncedTo: Tue Feb 25 2014 10:30:09 GMT+0800 (CST) = 56533 secs ago (15.7hrs) source: test01:10003 no replication info, yet. State: ARBITER 輸出信息包括從庫的主機名,port信息等。 9.5.2 變動oplog的大小 若是發現oplog大小不合適,最簡單的方法就是停掉主節點,刪除local數據庫的文件,用心的設置從新啓動。 # rm -rf /data/node2/local* 爲大型的oplog預分配空間很是耗費時間,且可能致使主節點停機時間增長,儘量的手動預分配數據文件。 9.5.3 複製的認證問題 若是在複製中使用了認證,還須要作些配置,使得從節點能夠訪問俄主節點的數據。在主節點和從節點都須要在本地數據庫增長用戶,每一個節點的用戶名和口令相同。 從節點鏈接到主節點是,會用存儲在local.system.users中的用戶認證。最早嘗試"repl"用戶,若是沒有,則用local.system.users中的第一個可用用戶。