1. 程式人生 > >mongdb 副本集的原理、搭建、應用

mongdb 副本集的原理、搭建、應用

manual 介紹 不一致 state call img master tomat 降級

在了解了這篇文章之後,可以進行該篇文章的說明和測試。MongoDB 副本集(Replica Set)是有自動故障恢復功能的主從集群,有一個Primary節點和一個或多個Secondary節點組成。類似於MySQL的MMM架構。更多關於副本集的介紹請見官網。也可以在google、baidu上查閱。

副本集中數據同步過程Primary節點寫入數據,Secondary通過讀取Primary的oplog得到復制信息,開始復制數據並且將復制信息寫入到自己的oplog。如果某個操作失敗,則備份節點停止從當前數據源復制數據。如果某個備份節點由於某些原因掛掉了,當重新啟動後,就會自動從oplog的最後一個操作開始同步,同步完成後,將信息寫入自己的oplog,由於復制操作是先復制數據,復制完成後再寫入oplog,有可能相同的操作會同步兩份,不過MongoDB在設計之初就考慮到這個問題,將oplog的同一個操作執行多次,與執行一次的效果是一樣的。簡單的說就是:

當Primary節點完成數據操作後,Secondary會做出一系列的動作保證數據的同步:
1:檢查自己local庫的oplog.rs集合找出最近的時間戳。
2:檢查Primary節點local庫oplog.rs集合,找出大於此時間戳的記錄。
3:將找到的記錄插入到自己的oplog.rs集合中,並執行這些操作。

副本集的同步和主從同步一樣,都是異步同步的過程,不同的是副本集有個自動故障轉移的功能。其原理是:slave端從primary端獲取日誌,然後在自己身上完全順序的執行日誌所記錄的各種操作(該日誌是不記錄查詢操作的),這個日誌就是local數據 庫中的oplog.rs表,默認在64位機器上這個表是比較大的,占磁盤大小的5%,oplog.rs的大小可以在啟動參數中設 定:--oplogSize 1000,單位是M。

註意:在副本集的環境中,要是所有的Secondary都宕機了,只剩下Primary。最後Primary會變成Secondary,不能提供服務。

一:環境搭建

1:準備服務器

192.168.200.25

192.168.200.245

192.168.200.252

2:安裝

http://www.cnblogs.com/zhoujinyi/archive/2013/06/02/3113868.html

3:修改配置,只需要開啟:replSet 參數即可。格式為:

192.168.200.252: --replSet = mmm/192.168.200.245:27017  # mmm是副本集的名稱,192.168.200.25:27017 為實例的位子。

192.168.200.245: --replSet = mmm/192.168.200.252:27017

192.168.200.25: --replSet = mmm/192.168.200.252:27017,192.168.200.245:27017 

4:啟動

啟動後會提示:

replSet info you may need to run replSetInitiate -- rs.initiate() in the shell -- if that is not already done

說明需要進行初始化操作,初始化操作只能執行一次。

5:初始化副本集

登入任意一臺機器的MongoDB執行:因為是全新的副本集所以可以任意進入一臺執行;要是有一臺有數據,則需要在有數據上執行;要多臺有數據則不能初始化。

技術分享
[email protected]:~$ mongo --host=192.168.200.252
MongoDB shell version: 2.4.6
connecting to: 192.168.200.252:27017/test
> rs.initiate({"_id":"mmm","members":[
... {"_id":1,
... "host":"192.168.200.252:27017",
... "priority":1
... },
... {"_id":2,
... "host":"192.168.200.245:27017",
... "priority":1
... }
... ]})
{
    "info" : "Config now saved locally.  Should come online in about a minute.",
    "ok" : 1
}
######
"_id": 副本集的名稱
"members": 副本集的服務器列表
"_id": 服務器的唯一ID
"host": 服務器主機
"priority": 是優先級,默認為1,優先級0為被動節點,不能成為活躍節點。優先級不位0則按照有大到小選出活躍節點。
"arbiterOnly": 仲裁節點,只參與投票,不接收數據,也不能成為活躍節點。
> rs.status() { "set" : "mmm", "date" : ISODate("2014-02-18T04:03:53Z"), "myState" : 1, "members" : [ { "_id" : 1, "name" : "192.168.200.252:27017", "health" : 1, "state" : 1, "stateStr" : "PRIMARY", "uptime" : 76, "optime" : Timestamp(1392696191, 1), "optimeDate" : ISODate("2014-02-18T04:03:11Z"), "self" : true }, { "_id" : 2, "name" : "192.168.200.245:27017", "health" : 1, "state" : 2, "stateStr" : "SECONDARY", "uptime" : 35, "optime" : Timestamp(1392696191, 1), "optimeDate" : ISODate("2014-02-18T04:03:11Z"), "lastHeartbeat" : ISODate("2014-02-18T04:03:52Z"), "lastHeartbeatRecv" : ISODate("2014-02-18T04:03:53Z"), "pingMs" : 0, "syncingTo" : "192.168.200.252:27017" } ], "ok" : 1 }
技術分享

6:日誌

查看252上的日誌:

Tue Feb 18 12:03:29.334 [rsMgr] replSet PRIMARY
…………
…………
Tue Feb 18 12:03:40.341 [rsHealthPoll] replSet member 192.168.200.245:27017 is now in state SECONDARY

至此,整個副本集已經搭建成功了。

上面的的副本集只有2臺服務器,還有一臺怎麽添加?除了在初始化的時候添加,還有什麽方法可以後期增刪節點?

二:維護操作

1:增刪節點。

把25服務加入到副本集中:

rs.add("192.168.200.25:27017")

技術分享
mmm:PRIMARY> rs.add("192.168.200.25:27017")
{ "ok" : 1 }
mmm:PRIMARY> rs.status()
{
    "set" : "mmm",
    "date" : ISODate("2014-02-18T04:53:00Z"),
    "myState" : 1,
    "members" : [
        {
            "_id" : 1,
            "name" : "192.168.200.252:27017",
            "health" : 1,
            "state" : 1,
            "stateStr" : "PRIMARY",
            "uptime" : 3023,
            "optime" : Timestamp(1392699177, 1),
            "optimeDate" : ISODate("2014-02-18T04:52:57Z"),
            "self" : true
        },
        {
            "_id" : 2,
            "name" : "192.168.200.245:27017",
            "health" : 1,
            "state" : 2,
            "stateStr" : "SECONDARY",
            "uptime" : 2982,
            "optime" : Timestamp(1392699177, 1),
            "optimeDate" : ISODate("2014-02-18T04:52:57Z"),
            "lastHeartbeat" : ISODate("2014-02-18T04:52:59Z"),
            "lastHeartbeatRecv" : ISODate("2014-02-18T04:53:00Z"),
            "pingMs" : 0,
            "syncingTo" : "192.168.200.252:27017"
        },
        {
            "_id" : 3,
            "name" : "192.168.200.25:27017",
            "health" : 1,
            "state" : 6,
            "stateStr" : "UNKNOWN",             #等一會就變成了 SECONDARY 
            "uptime" : 3,
            "optime" : Timestamp(0, 0),
            "optimeDate" : ISODate("1970-01-01T00:00:00Z"),
            "lastHeartbeat" : ISODate("2014-02-18T04:52:59Z"),
            "lastHeartbeatRecv" : ISODate("1970-01-01T00:00:00Z"),
            "pingMs" : 0,
            "lastHeartbeatMessage" : "still initializing"
        }
    ],
    "ok" : 1
}
技術分享

把25服務從副本集中刪除:

rs.remove("192.168.200.25:27017")

技術分享
mmm:PRIMARY> rs.remove("192.168.200.25:27017")
Tue Feb 18 13:01:09.298 DBClientCursor::init call() failed
Tue Feb 18 13:01:09.299 Error: error doing query: failed at src/mongo/shell/query.js:78
Tue Feb 18 13:01:09.300 trying reconnect to 192.168.200.252:27017
Tue Feb 18 13:01:09.301 reconnect 192.168.200.252:27017 ok
mmm:PRIMARY> rs.status()
{
    "set" : "mmm",
    "date" : ISODate("2014-02-18T05:01:19Z"),
    "myState" : 1,
    "members" : [
        {
            "_id" : 1,
            "name" : "192.168.200.252:27017",
            "health" : 1,
            "state" : 1,
            "stateStr" : "PRIMARY",
            "uptime" : 3522,
            "optime" : Timestamp(1392699669, 1),
            "optimeDate" : ISODate("2014-02-18T05:01:09Z"),
            "self" : true
        },
        {
            "_id" : 2,
            "name" : "192.168.200.245:27017",
            "health" : 1,
            "state" : 2,
            "stateStr" : "SECONDARY",
            "uptime" : 10,
            "optime" : Timestamp(1392699669, 1),
            "optimeDate" : ISODate("2014-02-18T05:01:09Z"),
            "lastHeartbeat" : ISODate("2014-02-18T05:01:19Z"),
            "lastHeartbeatRecv" : ISODate("2014-02-18T05:01:18Z"),
            "pingMs" : 0,
            "lastHeartbeatMessage" : "syncing to: 192.168.200.252:27017",
            "syncingTo" : "192.168.200.252:27017"
        }
    ],
    "ok" : 1
}
技術分享

192.168.200.25 的節點已經被移除。

2:查看復制的情況

db.printSlaveReplicationInfo()

技術分享
mmm:PRIMARY> db.printSlaveReplicationInfo()
source:   192.168.200.245:27017
     syncedTo: Tue Feb 18 2014 13:02:35 GMT+0800 (CST)
         = 145 secs ago (0.04hrs)
source:   192.168.200.25:27017
     syncedTo: Tue Feb 18 2014 13:02:35 GMT+0800 (CST)
         = 145 secs ago (0.04hrs)
技術分享

source:從庫的ip和端口。

syncedTo:目前的同步情況,以及最後一次同步的時間。

從上面可以看出,在數據庫內容不變的情況下他是不同步的,數據庫變動就會馬上同步。

3:查看副本集的狀態

rs.status()

技術分享
mmm:PRIMARY> rs.status()
{
    "set" : "mmm",
    "date" : ISODate("2014-02-18T05:12:28Z"),
    "myState" : 1,
    "members" : [
        {
            "_id" : 1,
            "name" : "192.168.200.252:27017",
            "health" : 1,
            "state" : 1,
            "stateStr" : "PRIMARY",
            "uptime" : 4191,
            "optime" : Timestamp(1392699755, 1),
            "optimeDate" : ISODate("2014-02-18T05:02:35Z"),
            "self" : true
        },
        {
            "_id" : 2,
            "name" : "192.168.200.245:27017",
            "health" : 1,
            "state" : 2,
            "stateStr" : "SECONDARY",
            "uptime" : 679,
            "optime" : Timestamp(1392699755, 1),
            "optimeDate" : ISODate("2014-02-18T05:02:35Z"),
            "lastHeartbeat" : ISODate("2014-02-18T05:12:27Z"),
            "lastHeartbeatRecv" : ISODate("2014-02-18T05:12:27Z"),
            "pingMs" : 0,
            "syncingTo" : "192.168.200.252:27017"
        },
        {
            "_id" : 3,
            "name" : "192.168.200.25:27017",
            "health" : 1,
            "state" : 2,
            "stateStr" : "SECONDARY",
            "uptime" : 593,
            "optime" : Timestamp(1392699755, 1),
            "optimeDate" : ISODate("2014-02-18T05:02:35Z"),
            "lastHeartbeat" : ISODate("2014-02-18T05:12:28Z"),
            "lastHeartbeatRecv" : ISODate("2014-02-18T05:12:28Z"),
            "pingMs" : 0,
            "syncingTo" : "192.168.200.252:27017"
        }
    ],
    "ok" : 1
}
技術分享

4:副本集的配置

rs.conf()/rs.config()

技術分享
mmm:PRIMARY> rs.conf()
{
    "_id" : "mmm",
    "version" : 4,
    "members" : [
        {
            "_id" : 1,
            "host" : "192.168.200.252:27017"
        },
        {
            "_id" : 2,
            "host" : "192.168.200.245:27017"
        },
        {
            "_id" : 3,
            "host" : "192.168.200.25:27017"
        }
    ]
}
技術分享

5:操作Secondary

默認情況下,Secondary是不提供服務的,即不能讀和寫。會提示:
error: { "$err" : "not master and slaveOk=false", "code" : 13435 }

在特殊情況下需要讀的話則需要:
rs.slaveOk() ,只對當前連接有效。

mmm:SECONDARY> db.test.find()
error: { "$err" : "not master and slaveOk=false", "code" : 13435 }
mmm:SECONDARY> rs.slaveOk()
mmm:SECONDARY> db.test.find()
{ "_id" : ObjectId("5302edfa8c9151a5013b978e"), "a" : 1 }

6:更新ing

三:測試

1:測試副本集數據復制功能

在Primary(192.168.200.252:27017)上插入數據:

mmm:PRIMARY> for(var i=0;i<10000;i++){db.test.insert({"name":"test"+i,"age":123})}
mmm:PRIMARY> db.test.count()
10001

在Secondary上查看是否已經同步:

mmm:SECONDARY> rs.slaveOk()
mmm:SECONDARY> db.test.count()
10001

數據已經同步。

2:測試副本集故障轉移功能

關閉Primary節點,查看其他2個節點的情況:

技術分享
mmm:PRIMARY> rs.status()
{
    "set" : "mmm",
    "date" : ISODate("2014-02-18T05:38:54Z"),
    "myState" : 1,
    "members" : [
        {
            "_id" : 1,
            "name" : "192.168.200.252:27017",
            "health" : 1,
            "state" : 1,
            "stateStr" : "PRIMARY",
            "uptime" : 5777,
            "optime" : Timestamp(1392701576, 2678),
            "optimeDate" : ISODate("2014-02-18T05:32:56Z"),
            "self" : true
        },
        {
            "_id" : 2,
            "name" : "192.168.200.245:27017",
            "health" : 1,
            "state" : 2,
            "stateStr" : "SECONDARY",
            "uptime" : 2265,
            "optime" : Timestamp(1392701576, 2678),
            "optimeDate" : ISODate("2014-02-18T05:32:56Z"),
            "lastHeartbeat" : ISODate("2014-02-18T05:38:54Z"),
            "lastHeartbeatRecv" : ISODate("2014-02-18T05:38:53Z"),
            "pingMs" : 0,
            "syncingTo" : "192.168.200.252:27017"
        },
        {
            "_id" : 3,
            "name" : "192.168.200.25:27017",
            "health" : 1,
            "state" : 2,
            "stateStr" : "SECONDARY",
            "uptime" : 2179,
            "optime" : Timestamp(1392701576, 2678),
            "optimeDate" : ISODate("2014-02-18T05:32:56Z"),
            "lastHeartbeat" : ISODate("2014-02-18T05:38:54Z"),
            "lastHeartbeatRecv" : ISODate("2014-02-18T05:38:53Z"),
            "pingMs" : 0,
            "syncingTo" : "192.168.200.252:27017"
        }
    ],
    "ok" : 1
}

#關閉
mmm:PRIMARY> use admin
switched to db admin
mmm:PRIMARY> db.shutdownServer()

#進入任意一臺:
mmm:SECONDARY> rs.status()
{
    "set" : "mmm",
    "date" : ISODate("2014-02-18T05:47:41Z"),
    "myState" : 2,
    "syncingTo" : "192.168.200.25:27017",
    "members" : [
        {
            "_id" : 1,
            "name" : "192.168.200.252:27017",
            "health" : 0,
            "state" : 8,
            "stateStr" : "(not reachable/healthy)",
            "uptime" : 0,
            "optime" : Timestamp(1392701576, 2678),
            "optimeDate" : ISODate("2014-02-18T05:32:56Z"),
            "lastHeartbeat" : ISODate("2014-02-18T05:47:40Z"),
            "lastHeartbeatRecv" : ISODate("2014-02-18T05:45:57Z"),
            "pingMs" : 0
        },
        {
            "_id" : 2,
            "name" : "192.168.200.245:27017",
            "health" : 1,
            "state" : 2,
            "stateStr" : "SECONDARY",
            "uptime" : 5888,
            "optime" : Timestamp(1392701576, 2678),
            "optimeDate" : ISODate("2014-02-18T05:32:56Z"),
            "errmsg" : "syncing to: 192.168.200.25:27017",
            "self" : true
        },
        {
            "_id" : 3,
            "name" : "192.168.200.25:27017",
            "health" : 1,
            "state" : 1,
            "stateStr" : "PRIMARY",
            "uptime" : 2292,
            "optime" : Timestamp(1392701576, 2678),
            "optimeDate" : ISODate("2014-02-18T05:32:56Z"),
            "lastHeartbeat" : ISODate("2014-02-18T05:47:40Z"),
            "lastHeartbeatRecv" : ISODate("2014-02-18T05:47:39Z"),
            "pingMs" : 0,
            "syncingTo" : "192.168.200.252:27017"
        }
    ],
    "ok" : 1
}
技術分享

看到192.168.200.25:27017 已經從 SECONDARY 變成了 PRIMARY。具體的信息可以通過日誌文件得知。繼續操作:

在新主上插入:

mmm:PRIMARY> for(var i=0;i<10000;i++){db.test.insert({"name":"test"+i,"age":123})}
mmm:PRIMARY> db.test.count()
20001

重啟啟動之前關閉的192.168.200.252:27017

技術分享
mmm:SECONDARY> rs.status()
{
    "set" : "mmm",
    "date" : ISODate("2014-02-18T05:45:14Z"),
    "myState" : 2,
    "syncingTo" : "192.168.200.245:27017",
    "members" : [
        {
            "_id" : 1,
            "name" : "192.168.200.252:27017",
            "health" : 1,
            "state" : 2,
            "stateStr" : "SECONDARY",
            "uptime" : 12,
            "optime" : Timestamp(1392702168, 8187),
            "optimeDate" : ISODate("2014-02-18T05:42:48Z"),
            "errmsg" : "syncing to: 192.168.200.245:27017",
            "self" : true
        },
        {
            "_id" : 2,
            "name" : "192.168.200.245:27017",
            "health" : 1,
            "state" : 2,
            "stateStr" : "SECONDARY",
            "uptime" : 11,
            "optime" : Timestamp(1392702168, 8187),
            "optimeDate" : ISODate("2014-02-18T05:42:48Z"),
            "lastHeartbeat" : ISODate("2014-02-18T05:45:13Z"),
            "lastHeartbeatRecv" : ISODate("2014-02-18T05:45:12Z"),
            "pingMs" : 0,
            "syncingTo" : "192.168.200.25:27017"
        },
        {
            "_id" : 3,
            "name" : "192.168.200.25:27017",
            "health" : 1,
            "state" : 1,
            "stateStr" : "PRIMARY",
            "uptime" : 9,
            "optime" : Timestamp(1392702168, 8187),
            "optimeDate" : ISODate("2014-02-18T05:42:48Z"),
            "lastHeartbeat" : ISODate("2014-02-18T05:45:13Z"),
            "lastHeartbeatRecv" : ISODate("2014-02-18T05:45:13Z"),
            "pingMs" : 0
        }
    ],
    "ok" : 1
}
技術分享

啟動之前的主,發現其變成了SECONDARY,在新主插入的數據,是否已經同步:

mmm:SECONDARY> db.test.count()
Tue Feb 18 13:47:03.634 count failed: { "note" : "from execCommand", "ok" : 0, "errmsg" : "not master" } at src/mongo/shell/query.js:180
mmm:SECONDARY> rs.slaveOk()
mmm:SECONDARY> db.test.count()
20001

已經同步。

註意

所有的Secondary都宕機、或則副本集中只剩下一個節點,則該節點只能為Secondary節點,也就意味著整個集群智能進行讀操作而不能進行寫操作,當其他的恢復時,之前的primary節點仍然是primary節點。

當某個節點宕機後重新啟動該節點會有一段的時間(時間長短視集群的數據量和宕機時間而定)導致整個集群中所有節點都成為secondary而無法進行寫操作(如果應用程序沒有設置相應的ReadReference也可能不能進行讀取操作)。

官方推薦的最小的副本集也應該具備一個primary節點和兩個secondary節點。兩個節點的副本集不具備真正的故障轉移能力。

四:應用

1:手動切換Primary節點到自己給定的節點
上面已經提到過了優先集priority,因為默認的都是1,所以只需要把給定的服務器的priority加到最大即可。讓245 成為主節點,操作如下:

技術分享
mmm:PRIMARY> rs.conf() #查看配置
{
    "_id" : "mmm",
    "version" : 6,  #每改變一次集群的配置,副本集的version都會加1。
    "members" : [
        {
            "_id" : 1,
            "host" : "192.168.200.252:27017"
        },
        {
            "_id" : 2,
            "host" : "192.168.200.245:27017"
        },
        {
            "_id" : 3,
            "host" : "192.168.200.25:27017"
        }
    ]
}
mmm:PRIMARY> rs.status() #查看狀態
{
    "set" : "mmm",
    "date" : ISODate("2014-02-18T07:25:51Z"),
    "myState" : 1,
    "members" : [
        {
            "_id" : 1,
            "name" : "192.168.200.252:27017",
            "health" : 1,
            "state" : 2,
            "stateStr" : "SECONDARY",
            "uptime" : 47,
            "optime" : Timestamp(1392708304, 1),
            "optimeDate" : ISODate("2014-02-18T07:25:04Z"),
            "lastHeartbeat" : ISODate("2014-02-18T07:25:50Z"),
            "lastHeartbeatRecv" : ISODate("2014-02-18T07:25:50Z"),
            "pingMs" : 0,
            "lastHeartbeatMessage" : "syncing to: 192.168.200.25:27017",
            "syncingTo" : "192.168.200.25:27017"
        },
        {
            "_id" : 2,
            "name" : "192.168.200.245:27017",
            "health" : 1,
            "state" : 2,
            "stateStr" : "SECONDARY",
            "uptime" : 47,
            "optime" : Timestamp(1392708304, 1),
            "optimeDate" : ISODate("2014-02-18T07:25:04Z"),
            "lastHeartbeat" : ISODate("2014-02-18T07:25:50Z"),
            "lastHeartbeatRecv" : ISODate("2014-02-18T07:25:51Z"),
            "pingMs" : 0,
            "lastHeartbeatMessage" : "syncing to: 192.168.200.25:27017",
            "syncingTo" : "192.168.200.25:27017"
        },
        {
            "_id" : 3,
            "name" : "192.168.200.25:27017",
            "health" : 1,
            "state" : 1,
            "stateStr" : "PRIMARY",
            "uptime" : 13019,
            "optime" : Timestamp(1392708304, 1),
            "optimeDate" : ISODate("2014-02-18T07:25:04Z"),
            "self" : true
        }
    ],
    "ok" : 1
}
mmm:PRIMARY> cfg=rs.conf() # { "_id" : "mmm", "version" : 4, "members" : [ { "_id" : 1, "host" : "192.168.200.252:27017" }, { "_id" : 2, "host" : "192.168.200.245:27017" }, { "_id" : 3, "host" : "192.168.200.25:27017" } ] } mmm:PRIMARY> cfg.members[1].priority=2 #修改priority 2 mmm:PRIMARY> rs.reconfig(cfg) #重新加載配置文件,強制了副本集進行一次選舉,優先級高的成為Primary。在這之間整個集群的所有節點都是secondary mmm:SECONDARY> rs.status() { "set" : "mmm", "date" : ISODate("2014-02-18T07:27:38Z"), "myState" : 2, "syncingTo" : "192.168.200.245:27017", "members" : [ { "_id" : 1, "name" : "192.168.200.252:27017", "health" : 1, "state" : 2, "stateStr" : "SECONDARY", "uptime" : 71, "optime" : Timestamp(1392708387, 1), "optimeDate" : ISODate("2014-02-18T07:26:27Z"), "lastHeartbeat" : ISODate("2014-02-18T07:27:37Z"), "lastHeartbeatRecv" : ISODate("2014-02-18T07:27:38Z"), "pingMs" : 0, "lastHeartbeatMessage" : "syncing to: 192.168.200.245:27017", "syncingTo" : "192.168.200.245:27017" }, { "_id" : 2, "name" : "192.168.200.245:27017", "health" : 1, "state" : 1, "stateStr" : "PRIMARY", "uptime" : 71, "optime" : Timestamp(1392708387, 1), "optimeDate" : ISODate("2014-02-18T07:26:27Z"), "lastHeartbeat" : ISODate("2014-02-18T07:27:37Z"), "lastHeartbeatRecv" : ISODate("2014-02-18T07:27:38Z"), "pingMs" : 0, "syncingTo" : "192.168.200.25:27017" }, { "_id" : 3, "name" : "192.168.200.25:27017", "health" : 1, "state" : 2, "stateStr" : "SECONDARY", "uptime" : 13126, "optime" : Timestamp(1392708387, 1), "optimeDate" : ISODate("2014-02-18T07:26:27Z"), "errmsg" : "syncing to: 192.168.200.245:27017", "self" : true } ], "ok" : 1 }
技術分享

這樣,給定的245服務器就成為了主節點。

2:添加仲裁節點

把25節點刪除,重啟。再添加讓其為仲裁節點:

rs.addArb("192.168.200.25:27017")
技術分享
mmm:PRIMARY> rs.status()
{
    "set" : "mmm",
    "date" : ISODate("2014-02-18T08:14:36Z"),
    "myState" : 1,
    "members" : [
        {
            "_id" : 1,
            "name" : "192.168.200.252:27017",
            "health" : 1,
            "state" : 2,
            "stateStr" : "SECONDARY",
            "uptime" : 795,
            "optime" : Timestamp(1392711068, 100),
            "optimeDate" : ISODate("2014-02-18T08:11:08Z"),
            "lastHeartbeat" : ISODate("2014-02-18T08:14:35Z"),
            "lastHeartbeatRecv" : ISODate("2014-02-18T08:14:35Z"),
            "pingMs" : 0,
            "syncingTo" : "192.168.200.245:27017"
        },
        {
            "_id" : 2,
            "name" : "192.168.200.245:27017",
            "health" : 1,
            "state" : 1,
            "stateStr" : "PRIMARY",
            "uptime" : 14703,
            "optime" : Timestamp(1392711068, 100),
            "optimeDate" : ISODate("2014-02-18T08:11:08Z"),
            "self" : true
        },
        {
            "_id" : 3,
            "name" : "192.168.200.25:27017",
            "health" : 1,
            "state" : 7,
            "stateStr" : "ARBITER",
            "uptime" : 26,
            "lastHeartbeat" : ISODate("2014-02-18T08:14:34Z"),
            "lastHeartbeatRecv" : ISODate("2014-02-18T08:14:34Z"),
            "pingMs" : 0,
            "syncingTo" : "192.168.200.252:27017"
        }
    ],
    "ok" : 1
}
mmm:PRIMARY> rs.conf()
{
    "_id" : "mmm",
    "version" : 9,
    "members" : [
        {
            "_id" : 1,
            "host" : "192.168.200.252:27017"
        },
        {
            "_id" : 2,
            "host" : "192.168.200.245:27017",
            "priority" : 2
        },
        {
            "_id" : 3,
            "host" : "192.168.200.25:27017",
            "arbiterOnly" : true
        }
    ]
}
技術分享

上面說明已經讓25服務器成為仲裁節點。副本集要求參與選舉投票(vote)的節點數為奇數,當我們實際環境中因為機器等原因限制只有兩個(或偶數)的節點,這時為了實現 Automatic Failover引入另一類節點:仲裁者(arbiter),仲裁者只參與投票不擁有實際的數據,並且不提供任何服務,因此它對物理資源要求不嚴格。

通過實際測試發現,當整個副本集集群中達到50%的節點(包括仲裁節點)不可用的時候,剩下的節點只能成為secondary節點,整個集群只能讀不能 寫。比如集群中有1個primary節點,2個secondary節點,加1個arbit節點時:當兩個secondary節點掛掉了,那麽剩下的原來的 primary節點也只能降級為secondary節點;當集群中有1個primary節點,1個secondary節點和1個arbit節點,這時即使 primary節點掛了,剩下的secondary節點也會自動成為primary節點。因為仲裁節點不復制數據,因此利用仲裁節點可以實現最少的機器開 銷達到兩個節點熱備的效果。

3:添加備份節點

hidden(成員用於支持專用功能):這樣設置後此機器在讀寫中都不可見,並且不會被選舉為Primary,但是可以投票,一般用於備份數據。

把25節點刪除,重啟。再添加讓其為hidden節點:

技術分享
mmm:PRIMARY> rs.add({"_id":3,"host":"192.168.200.25:27017","priority":0,"hidden":true})
{ "down" : [ "192.168.200.25:27017" ], "ok" : 1 }
mmm:PRIMARY> rs.conf()
{
    "_id" : "mmm",
    "version" : 17,
    "members" : [
        {
            "_id" : 1,
            "host" : "192.168.200.252:27017"
        },
        {
            "_id" : 2,
            "host" : "192.168.200.245:27017"
        },
        {
            "_id" : 3,
            "host" : "192.168.200.25:27017",
            "priority" : 0,
            "hidden" : true
        }
    ]
}
技術分享

測試其能否參與投票:關閉當前的Primary,查看是否自動轉移Primary

技術分享
關閉Primary(252):
mmm:PRIMARY> use admin
switched to db admin
mmm:PRIMARY> db.shutdownServer()

連另一個鏈接察看:
mmm:PRIMARY> rs.status()
{
    "set" : "mmm",
    "date" : ISODate("2014-02-19T09:11:45Z"),
    "myState" : 1,
    "members" : [
        {
            "_id" : 1,
            "name" : "192.168.200.252:27017",
            "health" : 1,
            "state" : 1,
            "stateStr" :"(not reachable/healthy)",
            "uptime" : 4817,
            "optime" : Timestamp(1392801006, 1),
            "optimeDate" : ISODate("2014-02-19T09:10:06Z"),
            "self" : true
        },
        {
            "_id" : 2,
            "name" : "192.168.200.245:27017",
            "health" : 1,
            "state" : 2,
            "stateStr" : "PRIMARY",
            "uptime" : 401,
            "optime" : Timestamp(1392801006, 1),
            "optimeDate" : ISODate("2014-02-19T09:10:06Z"),
            "lastHeartbeat" : ISODate("2014-02-19T09:11:44Z"),
            "lastHeartbeatRecv" : ISODate("2014-02-19T09:11:43Z"),
            "pingMs" : 0,
            "syncingTo" : "192.168.200.252:27017"
        },
        {
            "_id" : 3,
            "name" : "192.168.200.25:27017",
            "health" : 1,
            "state" : 2,
            "stateStr" : "SECONDARY",
            "uptime" : 99,
            "optime" : Timestamp(1392801006, 1),
            "optimeDate" : ISODate("2014-02-19T09:10:06Z"),
            "lastHeartbeat" : ISODate("2014-02-19T09:11:44Z"),
            "lastHeartbeatRecv" : ISODate("2014-02-19T09:11:43Z"),
            "pingMs" : 0,
            "syncingTo" : "192.168.200.252:27017"
        }
    ],
    "ok" : 1
}
上面說明Primary已經轉移,說明hidden具有投票的權利,繼續查看是否有數據復制的功能。
#####
mmm:PRIMARY> db.test.count()
20210
mmm:PRIMARY> for(var i=0;i<90;i++){db.test.insert({"name":"test"+i,"age":123})}
mmm:PRIMARY> db.test.count()
20300

Secondady:
mmm:SECONDARY> db.test.count()
Wed Feb 19 17:18:19.469 count failed: { "note" : "from execCommand", "ok" : 0, "errmsg" : "not master" } at src/mongo/shell/query.js:180
mmm:SECONDARY> rs.slaveOk()
mmm:SECONDARY> db.test.count()
20300
上面說明hidden具有數據復制的功能
技術分享

後面大家可以在上面進行備份了,後一篇會介紹如何備份、還原以及一些日常維護需要的操作。

4:添加延遲節點

Delayed(成員用於支持專用功能):可以指定一個時間延遲從primary節點同步數據。主要用於處理誤刪除數據馬上同步到從節點導致的不一致問題。

把25節點刪除,重啟。再添加讓其為Delayed節點:

技術分享
mmm:PRIMARY> rs.add({"_id":3,"host":"192.168.200.25:27017","priority":0,"hidden":true,"slaveDelay":60})  #語法
{ "down" : [ "192.168.200.25:27017" ], "ok" : 1 }

mmm:PRIMARY> rs.conf()
{
    "_id" : "mmm",
    "version" : 19,
    "members" : [
        {
            "_id" : 1,
            "host" : "192.168.200.252:27017"
        },
        {
            "_id" : 2,
            "host" : "192.168.200.245:27017"
        },
        {
            "_id" : 3,
            "host" : "192.168.200.25:27017",
            "priority" : 0,
            "slaveDelay" : 60,   
            "hidden" : true
        }
    ]
}
技術分享

測試:操作Primary,看數據是否60s後同步到delayed節點。

技術分享
mmm:PRIMARY> db.test.count()
20300
mmm:PRIMARY> for(var i=0;i<200;i++){db.test.insert({"name":"test"+i,"age":123})}
mmm:PRIMARY> db.test.count()
20500

Delayed:
mmm:SECONDARY> db.test.count()
20300
#60秒之後
mmm:SECONDARY> db.test.count()
20500
技術分享

上面說明delayed能夠成功的把同步操作延遲60秒執行。除了上面的成員之外,還有:

Secondary-Only:不能成為primary節點,只能作為secondary副本節點,防止一些性能不高的節點成為主節點。

Non-Voting:沒有選舉權的secondary節點,純粹的備份數據節點。

具體成員信息如下:

成為primary

對客戶端可見

參與投票

延遲同步

復制數據

Default

Secondary-Only

Hidden

Delayed

Arbiters

Non-Voting

5:讀寫分離

MongoDB副本集對讀寫分離的支持是通過Read Preferences特性進行支持的,這個特性非常復雜和靈活。

應用程序驅動通過read reference來設定如何對副本集進行讀取操作,默認的,客戶端驅動所有的讀操作都是直接訪問primary節點的,從而保證了數據的嚴格一致性。

支持五種的read preference模式:官網說明

技術分享
primary
主節點,默認模式,讀操作只在主節點,如果主節點不可用,報錯或者拋出異常。
primaryPreferred
首選主節點,大多情況下讀操作在主節點,如果主節點不可用,如故障轉移,讀操作在從節點。
secondary
從節點,讀操作只在從節點, 如果從節點不可用,報錯或者拋出異常。
secondaryPreferred
首選從節點,大多情況下讀操作在從節點,特殊情況(如單主節點架構)讀操作在主節點。
nearest
最鄰近節點,讀操作在最鄰近的成員,可能是主節點或者從節點,關於最鄰近的成員請參考
技術分享

註意:2.2版本之前的MongoDB對Read Preference支持的還不完全,如果客戶端驅動采用primaryPreferred實際上讀取操作都會被路由到secondary節點。

因為讀寫分離是通過修改程序的driver的,故這裏就不做說明,具體的可以參考這篇文章或則可以在google上查閱

java驅動:

技術分享

參見:http://www.lanceyan.com/tech/mongodb/mongodb_cluster_1.html

   http://www.cnblogs.com/zhoujinyi/p/3554010.html

mongdb 副本集的原理、搭建、應用