简介
MongoDB 复制集提供了数据副本,当发生硬件故障或服务中断时,可以从副本恢复数据,并能自动进行故障转移,即实现了 MongoDB 的高可用。
本文讲述了如何部署一个用于开发和测试的复制集环境,其中使用了3个 mongod 实例来建立一个由3个节点组成的复制集。需要事先安装好 MongoDB 数据库,可以参考MongoDB 的安装和使用进行安装。我们所使用的环境是: 美团云主机: 2CPU、2G内存、50G磁盘 OS: CentOS 6.5 DB: MongoDB 3.0.7
部署复制集
下文部署了一个名为 rs0 的复制集。
第一步:为每个复制集实例建立数据目录。下列命令将建立3个目录,分别存储各个实例的数据文件。
mkdir -p /srv/mongodb/rs0-0 /srv/mongodb/rs0-1 /srv/mongodb/rs0-2
第二步:启动 mongo 实例。
启动第一个实例:
mongod --port 27018 --dbpath /srv/mongodb/rs0-0 --replSet rs0
启动第二个实例:
mongod --port 27019 --dbpath /srv/mongodb/rs0-1 --replSet rs0
启动第三个实例:
mongod --port 27020 --dbpath /srv/mongodb/rs0-2 --replSet rs0
其中,--port 选项指定了端口号,每个节点有不同的端口号;--dbpath 选项指定了数据目录,每个节点也有不同的数据目录;--replSet 选项指定了复制集名称,所有节点有共同的复制集名称。
第三部:设置主机名。下列命令中更改了主机名为 mongodb-server。
hostname # 查看主机名
hostname mongodb-server # 修改主机名
hostname # 确认主机名为 mongodb-server
编辑文件 /etc/hosts,添加一行:
127.0.0.1 mongodb-server
第四步:通过 mongo 命令连接到某个 mongod 实例,注意需要指定所需连接的端口。
mongo --port 27018
第五步:在 mongo 中执行 rs.initiate() 命令初始化复制集。
> rs.initiate()
{
"info2" : "no configuration explicitly specified -- making one",
"me" : "mongodb-server:27018",
"ok" : 1
}
稍等片刻,复制集初始化完成,回车后提示符从 >
变成 rs0:PRIMARY>
,表示该实例已经成为一个 Primary 节点。
第六步:执行 rs.conf() 命令可以查看复制集配置。
rs0:PRIMARY> rs.conf()
{
"_id" : "rs0",
"version" : 1,
"members" : [
{
"_id" : 0,
"host" : "mongodb-server:27018",
"arbiterOnly" : false,
"buildIndexes" : true,
"hidden" : false,
"priority" : 1,
"tags" : {
},
"slaveDelay" : 0,
"votes" : 1
}
],
"settings" : {
"chainingAllowed" : true,
"heartbeatTimeoutSecs" : 10,
"getLastErrorModes" : {
},
"getLastErrorDefaults" : {
"w" : 1,
"wtimeout" : 0
}
}
}
第七步:执行 rs.add() 命令添加第二个和第三个 mongod 实例为复制集的节点。
rs0:PRIMARY> rs.add("mongodb-server:27019")
{ "ok" : 1 }
rs0:PRIMARY> rs.add("mongodb-server:27020")
{ "ok" : 1 }
第八步:执行 rs.status() 命令来检查复制集的状态。
rs0:PRIMARY> rs.status()
{
"set" : "rs0",
"date" : ISODate("2015-12-04T06:12:07.664Z"),
"myState" : 1,
"members" : [
{
"_id" : 0,
"name" : "mongodb-server:27018",
"health" : 1,
"state" : 1,
"stateStr" : "PRIMARY",
"uptime" : 3611,
"optime" : Timestamp(1449209487, 1),
"optimeDate" : ISODate("2015-12-04T06:11:27Z"),
"electionTime" : Timestamp(1449208829, 2),
"electionDate" : ISODate("2015-12-04T06:00:29Z"),
"configVersion" : 3,
"self" : true
},
{
"_id" : 1,
"name" : "mongodb-server:27019",
"health" : 1,
"state" : 2,
"stateStr" : "SECONDARY",
"uptime" : 41,
"optime" : Timestamp(1449209487, 1),
"optimeDate" : ISODate("2015-12-04T06:11:27Z"),
"lastHeartbeat" : ISODate("2015-12-04T06:12:07.338Z"),
"lastHeartbeatRecv" : ISODate("2015-12-04T06:12:06.005Z"),
"pingMs" : 0,
"configVersion" : 3
},
{
"_id" : 2,
"name" : "mongodb-server:27020",
"health" : 1,
"state" : 2,
"stateStr" : "SECONDARY",
"uptime" : 40,
"optime" : Timestamp(1449209487, 1),
"optimeDate" : ISODate("2015-12-04T06:11:27Z"),
"lastHeartbeat" : ISODate("2015-12-04T06:12:07.339Z"),
"lastHeartbeatRecv" : ISODate("2015-12-04T06:12:07.339Z"),
"pingMs" : 0,
"configVersion" : 3
}
],
"ok" : 1
}
可见27018节点是 Primary 身份,而27019和27020节点则是 Secondary 身份。至此一个拥有3个实例的 MongoDB 复制集就建立完成了。
测试复制集
测试数据同步
连接一个 Secondary 节点,例如连接27020节点。
[root@mongodb ~]# mongo --port 27020
MongoDB shell version: 3.0.7
connecting to: 127.0.0.1:27020/test
...
rs0:SECONDARY>
Secondary 节点默认不可读。要解决这个问题,只需要简单的一步。
rs0:SECONDARY> rs.slaveOk()
rs0:SECONDARY> show dbs
local 1.078GB
rs0:SECONDARY> show collections
rs0:SECONDARY>
在 Primary 节点插入测试数据。注意以下命令在 Primary 节点的命令行客户端执行。
rs0:PRIMARY> use testdb
switched to db testdb
rs0:PRIMARY> db.tc.insert({ name : "mongo" })
WriteResult({ "nInserted" : 1 })
rs0:PRIMARY> db.tc.insert({ x : 3 })
WriteResult({ "nInserted" : 1 })
rs0:PRIMARY> db.tc.find()
{ "_id" : ObjectId("5661345929d3c35bcbe64803"), "name" : "mongo" }
{ "_id" : ObjectId("5661346b29d3c35bcbe64804"), "x" : 3 }
rs0:PRIMARY>
确认 Primary 节点上新建的数据库 testdb、集合 tc 及其文档已经同步到 Secondary 节点。注意以下命令在 Secondary 节点的命令行客户端执行。
rs0:SECONDARY> rs.slaveOk()
rs0:SECONDARY> show dbs
local 1.078GB
testdb 0.078GB
rs0:SECONDARY> use testdb
switched to db testdb
rs0:SECONDARY> show collections
system.indexes
tc
rs0:SECONDARY> db.tc.find()
{ "_id" : ObjectId("5661345929d3c35bcbe64803"), "name" : "mongo" }
{ "_id" : ObjectId("5661346b29d3c35bcbe64804"), "x" : 3 }
rs0:SECONDARY>
测试故障转移
在 Primary 节点的服务器端按下 “Ctrl + C” 键模拟 Primary 节点故障,然后在 Primary 节点的命令行客户端按下回车键,提示符从 rs0:PRIMARY>
变成 >
,表示该实例已经不是 Primary 节点。而在 Secondary 节点客户端按下回车键,则可能会有如下表现:
rs0:SECONDARY>
rs0:PRIMARY>
再次查看复制集的状态,27018节点的 health 值由1变为0,表示节点故障;27020节点的 stateStr 值由 Secondary 变为 Primary,表示该节点已经被提升为 Primary 节点。
rs0:PRIMARY> rs.status()
{
"set" : "rs0",
"date" : ISODate("2015-12-04T06:52:09.191Z"),
"myState" : 1,
"members" : [
{
"_id" : 0,
"name" : "mongodb-server:27018",
"health" : 0,
"state" : 8,
"stateStr" : "(not reachable/healthy)",
"uptime" : 0,
"optime" : Timestamp(0, 0),
"optimeDate" : ISODate("1970-01-01T00:00:00Z"),
"lastHeartbeat" : ISODate("2015-12-04T06:52:08.039Z"),
"lastHeartbeatRecv" : ISODate("2015-12-04T06:45:11.755Z"),
"pingMs" : 0,
"lastHeartbeatMessage" : "Failed attempt to connect to mongodb-server:27018; couldn't connect to server mongodb-server:27018 (127.0.0.1), connection attempt failed",
"configVersion" : -1
},
{
"_id" : 1,
"name" : "mongodb-server:27019",
"health" : 1,
"state" : 2,
"stateStr" : "SECONDARY",
"uptime" : 2441,
"optime" : Timestamp(1449210987, 1),
"optimeDate" : ISODate("2015-12-04T06:36:27Z"),
"lastHeartbeat" : ISODate("2015-12-04T06:52:07.857Z"),
"lastHeartbeatRecv" : ISODate("2015-12-04T06:52:08.567Z"),
"pingMs" : 0,
"configVersion" : 3
},
{
"_id" : 2,
"name" : "mongodb-server:27020",
"health" : 1,
"state" : 1,
"stateStr" : "PRIMARY",
"uptime" : 5984,
"optime" : Timestamp(1449210987, 1),
"optimeDate" : ISODate("2015-12-04T06:36:27Z"),
"electionTime" : Timestamp(1449211514, 1),
"electionDate" : ISODate("2015-12-04T06:45:14Z"),
"configVersion" : 3,
"self" : true
}
],
"ok" : 1
}
在第三个节点(此处为27019节点)的服务器端按下 “Ctrl + C” 键模拟节点故障,然后在当前 Primary 节点的命令行客户端按下回车键,表现如下,即只有一个节点存活时,该节点一定是 Secondary 节点,可以通过执行命令 rs.status() 验证这一点。
rs0:PRIMARY>
...
rs0:SECONDARY>
依次启动之前强制关闭的两个节点,先启动27018节点。
mongod --port 27018 --dbpath /srv/mongodb/rs0-0 --replSet rs0
然后启动27019节点。
mongod --port 27019 --dbpath /srv/mongodb/rs0-1 --replSet rs0
最后查看复制集的状态,一切恢复了到了最初状态:27018节点为 Primary 身份,27019和27020节点为 Secondary 身份,它们的 health 值均为1。
rs0:SECONDARY> rs.status()
{
"set" : "rs0",
"date" : ISODate("2015-12-04T07:07:01.418Z"),
"myState" : 2,
"members" : [
{
"_id" : 0,
"name" : "mongodb-server:27018",
"health" : 1,
"state" : 1,
"stateStr" : "PRIMARY",
"uptime" : 180,
"optime" : Timestamp(1449210987, 1),
"optimeDate" : ISODate("2015-12-04T06:36:27Z"),
"lastHeartbeat" : ISODate("2015-12-04T07:07:00.574Z"),
"lastHeartbeatRecv" : ISODate("2015-12-04T07:07:00.903Z"),
"pingMs" : 0,
"electionTime" : Timestamp(1449212640, 1),
"electionDate" : ISODate("2015-12-04T07:04:00Z"),
"configVersion" : 3
},
{
"_id" : 1,
"name" : "mongodb-server:27019",
"health" : 1,
"state" : 2,
"stateStr" : "SECONDARY",
"uptime" : 172,
"optime" : Timestamp(1449210987, 1),
"optimeDate" : ISODate("2015-12-04T06:36:27Z"),
"lastHeartbeat" : ISODate("2015-12-04T07:07:00.700Z"),
"lastHeartbeatRecv" : ISODate("2015-12-04T07:07:00.762Z"),
"pingMs" : 0,
"configVersion" : 3
},
{
"_id" : 2,
"name" : "mongodb-server:27020",
"health" : 1,
"state" : 2,
"stateStr" : "SECONDARY",
"uptime" : 6876,
"optime" : Timestamp(1449210987, 1),
"optimeDate" : ISODate("2015-12-04T06:36:27Z"),
"configVersion" : 3,
"self" : true
}
],
"ok" : 1
}
总结
本文讲述了在一台云主机上部署 MongoDB 复制集的测试开发环境,如果需要在生产环境中部署,只需要将复制集节点部署在不同的云主机上,并且保证各个节点之间能够进行网络通信。
在 MongoDB 复制集中,节点提升到 Primary 是一个很严格的过程,一旦出现两个 Primary 节点,复制集就废了。当复制集中仅有的两个实例挂掉一个,剩下的一个并不能判断自己的存活状况,因为也有可能是自己跟网络断开了连接造成的,而另外一个实例还活着。所以暂时让自己成为 Secondary 。一旦另外一个实例恢复生存,两个实例就可以互相证明对方存活,然后就会有实例扮演 Primary 身份。