MongoDB的复制和故障转移配置
由于有机会在生产环境中使用MongoDB,所以想要引入复制和故障转移机制,并记录了当时所采取的对策的备忘录。
概述
此次验证是为了确认以下内容而进行。
本次检验的目的是为了确认以下内容。
本次验证的目的是为了确认以下内容。
-
- MongoDB复制和故障切换设置
-
- MongoDB故障切换操作确认
-
- 故障切换时程序端的处理和设置
- 连接数的管理
在这篇文章中,将介绍有关MongoDB设置和操作的第1部分和第2部分。关于程序方面的设置和处理,请参考下面的文章。
-
- 参考
MongoDBのフェイルオーバー時のNode.jsのプログラム制御と動作確認
环境
为了验证,本次在本地环境进行了测试。
形成
为了设置MongoDB的复制(Replica Set),至少需要3台服务器。为了本次验证,我们将在一台服务器中分配端口,并运行三台数据库服务器。
每个解释都在下面的文章中有详细说明。
-
- 参考
Replication — MongoDB Manual 3.4
俺でもわかるシリーズ: MongoDBのレプリケーション
建构步骤
安装MongoDB
参考《在CentOS6.5上安装MongoDB》一文,进行《添加MongoDB仓库》和《安装》操作。
添加MongoDB仓库。
$ sudo vi /etc/yum.repos.d/mongodb.repo
[mongodb]
name=MongoDB Repository
baseurl=http://downloads-distro.mongodb.org/repo/redhat/os/x86_64/
gpgcheck=0
enabled=1
安装
$ sudo yum install -y mongodb-org
构建MongoDB的副本集
创建数据存储区域
首先,在一台服务器上运行3个MongoDB实例,需要创建MongoDB的数据存储空间。(注意:接下来的步骤将使用root用户执行)
mkdir -p /var/lib/mongodb/db01/
mkdir -p /var/lib/mongodb/db02/
mkdir -p /var/lib/mongodb/db03/
启动3个MongoDB进程。
# db01をport: 50000で起動する
$ mongod --port=50000 --dbpath=/var/lib/mongodb/db01 --logpath=/var/log/mongodb/db01.log --replSet=LocalRep --fork
# db02をport: 50001で起動する
$ mongod --port=50001 --dbpath=/var/lib/mongodb/db02 --logpath=/var/log/mongodb/db02.log --replSet=LocalRep --fork
# db03をport: 50002で起動する
$ mongod --port=50002 --dbpath=/var/lib/mongodb/db03 --logpath=/var/log/mongodb/db03.log --replSet=LocalRep --fork
确认流程
$ ps aux | grep mongod
root 8475 2.4 7.1 1061980 45048 ? Sl 10:42 0:00 mongod --port=50000 --dbpath=/var/lib/mongodb/db01 --logpath=/var/log/mongodb/db01.log --replSet=LocalRep --fork
root 8502 2.4 5.9 1061980 37504 ? Sl 10:43 0:00 mongod --port=50001 --dbpath=/var/lib/mongodb/db02 --logpath=/var/log/mongodb/db02.log --replSet=LocalRep --fork
root 8529 3.7 6.2 1061984 39528 ? Sl 10:43 0:00 mongod --port=50002 --dbpath=/var/lib/mongodb/db03 --logpath=/var/log/mongodb/db03.log --replSet=LocalRep --fork
root 8555 0.0 0.1 103304 884 pts/2 R+ 10:43 0:00 grep mongod
进行复制套装的设置
目前只是启动了,副本集的设置尚未完成,因此需要在主数据库上进行副本集的配置。
首先,登录到主数据库(DB01服务器)后,检查状态。
$ mongo --port 50000
> rs.status()
{
"info" : "run rs.initiate(...) if not yet done for the set",
"ok" : 0,
"errmsg" : "no replset config has been received",
"code" : 94,
"codeName" : "NotYetInitialized"
}
因为会显示未进行初始化的消息,所以执行命令“rs.initiate()”来进行初始化。
> rs.initiate()
{
"info2" : "no configuration specified. Using a default configuration for the set",
"me" : "localhost.localdomain:50000",
"ok" : 1
}
LocalRep:OTHER>
在默认设置下,虽然作为副本集的主节点初始化完成。一段时间后使用 rs.status() 命令,可以看到名字为 “localhost.localdomain:50000” 的服务器已经成为”Primary”。
LocalRep:OTHER> rs.status()
{
"set" : "LocalRep",
"date" : ISODate("2017-09-08T09:44:50.159Z"),
"myState" : 1,
"term" : NumberLong(1),
"heartbeatIntervalMillis" : NumberLong(2000),
"optimes" : {
"lastCommittedOpTime" : {
"ts" : Timestamp(1504863885, 1),
"t" : NumberLong(1)
},
"appliedOpTime" : {
"ts" : Timestamp(1504863885, 1),
"t" : NumberLong(1)
},
"durableOpTime" : {
"ts" : Timestamp(1504863885, 1),
"t" : NumberLong(1)
}
},
"members" : [
{
"_id" : 0,
"name" : "localhost.localdomain:50000",
"health" : 1,
"state" : 1,
"stateStr" : "PRIMARY",
"uptime" : 111,
"optime" : {
"ts" : Timestamp(1504863885, 1),
"t" : NumberLong(1)
},
"optimeDate" : ISODate("2017-09-08T09:44:45Z"),
"infoMessage" : "could not find member to sync from",
"electionTime" : Timestamp(1504863883, 2),
"electionDate" : ISODate("2017-09-08T09:44:43Z"),
"configVersion" : 1,
"self" : true
}
],
"ok" : 1
}
LocalRep:PRIMARY>
Secondary和Arbiter的增加
接下来,将DB02作为Secondary添加进去。在PRIMARY服务器上执行rs.add()命令。
LocalRep:PRIMARY> rs.add('localhost.localdomain:50001')
{ "ok" : 1 }
再加一项Arbiter角色,将DB03加入。
LocalRep:PRIMARY> rs.addArb('localhost.localdomain:50002');
{ "ok" : 1 }
以下命令亦可写作如下形式:
LocalRep:PRIMARY> rs.add({host: 'localhost.localdomain:50002', arbiterOnly: true);
执行rs.status()命令可以看到在这种状态下已经成功配置了副本集。
LocalRep:PRIMARY> rs.status()
{
"set" : "LocalRep",
"date" : ISODate("2017-09-08T09:49:10.158Z"),
"myState" : 1,
"term" : NumberLong(1),
"heartbeatIntervalMillis" : NumberLong(2000),
"optimes" : {
"lastCommittedOpTime" : {
"ts" : Timestamp(1504864145, 1),
"t" : NumberLong(1)
},
"appliedOpTime" : {
"ts" : Timestamp(1504864145, 1),
"t" : NumberLong(1)
},
"durableOpTime" : {
"ts" : Timestamp(1504864145, 1),
"t" : NumberLong(1)
}
},
"members" : [
{
"_id" : 0,
"name" : "localhost.localdomain:50000",
"health" : 1,
"state" : 1,
"stateStr" : "PRIMARY",
"uptime" : 371,
"optime" : {
"ts" : Timestamp(1504864145, 1),
"t" : NumberLong(1)
},
"optimeDate" : ISODate("2017-09-08T09:49:05Z"),
"electionTime" : Timestamp(1504863883, 2),
"electionDate" : ISODate("2017-09-08T09:44:43Z"),
"configVersion" : 3,
"self" : true
},
{
"_id" : 1,
"name" : "localhost.localdomain:50001",
"health" : 1,
"state" : 2,
"stateStr" : "SECONDARY",
"uptime" : 112,
"optime" : {
"ts" : Timestamp(1504864145, 1),
"t" : NumberLong(1)
},
"optimeDurable" : {
"ts" : Timestamp(1504864145, 1),
"t" : NumberLong(1)
},
"optimeDate" : ISODate("2017-09-08T09:49:05Z"),
"optimeDurableDate" : ISODate("2017-09-08T09:49:05Z"),
"lastHeartbeat" : ISODate("2017-09-08T09:49:09.258Z"),
"lastHeartbeatRecv" : ISODate("2017-09-08T09:49:09.249Z"),
"pingMs" : NumberLong(0),
"syncingTo" : "localhost.localdomain:50000",
"configVersion" : 3
},
{
"_id" : 2,
"name" : "localhost.localdomain:50002",
"health" : 1,
"state" : 7,
"stateStr" : "ARBITER",
"uptime" : 74,
"lastHeartbeat" : ISODate("2017-09-08T09:49:09.258Z"),
"lastHeartbeatRecv" : ISODate("2017-09-08T09:49:05.277Z"),
"pingMs" : NumberLong(0),
"configVersion" : 3
}
],
"ok" : 1
}
设置故障切换时的优先级
当DB01崩溃并发生故障转移时,DB02将成为Primary。
然而,当DB01恢复并再次成为Primary时,只需在每台服务器上设置优先级即可。
本次设置中,将DB01的优先级设置为100,将DB02的优先级设置为10,
以确保在DB01运行时,它将始终作为Primary运行。
LocalRep:PRIMARY> var conf = rs.conf();
LocalRep:PRIMARY> conf.members[0].priority = 100;
100
LocalRep:PRIMARY> conf.members[1].priority = 10;
10
LocalRep:PRIMARY> rs.reconfig(conf);
{ "ok" : 1 }
members指的是执行rs.status()时的members键的数组。
db01对应index:0,db02对应index:1,因此使用上述指定的方式。
各种服务器的设置已完成。
检查 MongoDB 的状态
登陆各台服务器以验证情况。
- DB01の状態
$ mongo --port 50000
LocalRep:PRIMARY>
- DB02の状態
$ mongo --port 50001
LocalRep:SECONDARY>
- DB03の状態
$ mongo --port 50002
LocalRep:ARBITER>
请确认已根据各自的角色进行了相应的设置。
执行故障转移
我试着关闭DB01。
暫時先確認故障切換是否正確執行。
首先,試著停止DB01的進程。
$ sudo ps aux | grep db01 | awk '{print $2}' | xargs kill -9
确认他已经不在了
$ ps aux | grep mongod
root 8502 1.7 7.2 1561832 45804 ? Sl 10:43 0:13 mongod --port=50001 --dbpath=/var/lib/mongodb/db02 --logpath=/var/log/mongodb/db02.log --replSet=LocalRep --fork
root 8529 1.6 7.0 1078444 44748 ? Sl 10:43 0:12 mongod --port=50002 --dbpath=/var/lib/mongodb/db03 --logpath=/var/log/mongodb/db03.log --replSet=LocalRep --fork
root 8714 0.0 0.1 103304 888 pts/1 R+ 10:56 0:00 grep mongod
当查看Arbiter的DB03的状态时,我们可以看到DB01显示为连接被拒绝的状态,因此显示为”stateStr”: “(not reachable/healthy)”,同时我们可以看到DB02正作为主服务器运行,显示为”stateStr”: “PRIMARY”。
LocalRep:ARBITER> rs.status()
{
"set" : "LocalRep",
"date" : ISODate("2017-09-08T09:56:34.742Z"),
"myState" : 7,
"term" : NumberLong(2),
"heartbeatIntervalMillis" : NumberLong(2000),
"optimes" : {
"lastCommittedOpTime" : {
"ts" : Timestamp(1504864535, 1),
"t" : NumberLong(1)
},
"appliedOpTime" : {
"ts" : Timestamp(1504864535, 1),
"t" : NumberLong(1)
},
"durableOpTime" : {
"ts" : Timestamp(0, 0),
"t" : NumberLong(-1)
}
},
"members" : [
{
"_id" : 0,
"name" : "localhost.localdomain:50000",
"health" : 0,
"state" : 8,
"stateStr" : "(not reachable/healthy)",
"uptime" : 0,
"optime" : {
"ts" : Timestamp(0, 0),
"t" : NumberLong(-1)
},
"optimeDurable" : {
"ts" : Timestamp(0, 0),
"t" : NumberLong(-1)
},
"optimeDate" : ISODate("1970-01-01T00:00:00Z"),
"optimeDurableDate" : ISODate("1970-01-01T00:00:00Z"),
"lastHeartbeat" : ISODate("2017-09-08T09:56:34.275Z"),
"lastHeartbeatRecv" : ISODate("2017-09-08T09:55:43.336Z"),
"pingMs" : NumberLong(0),
"lastHeartbeatMessage" : "Connection refused",
"configVersion" : -1
},
{
"_id" : 1,
"name" : "localhost.localdomain:50001",
"health" : 1,
"state" : 1,
"stateStr" : "PRIMARY",
"uptime" : 519,
"optime" : {
"ts" : Timestamp(1504864584, 1),
"t" : NumberLong(2)
},
"optimeDurable" : {
"ts" : Timestamp(1504864584, 1),
"t" : NumberLong(2)
},
"optimeDate" : ISODate("2017-09-08T09:56:24Z"),
"optimeDurableDate" : ISODate("2017-09-08T09:56:24Z"),
"lastHeartbeat" : ISODate("2017-09-08T09:56:34.261Z"),
"lastHeartbeatRecv" : ISODate("2017-09-08T09:56:32.791Z"),
"pingMs" : NumberLong(0),
"electionTime" : Timestamp(1504864552, 1),
"electionDate" : ISODate("2017-09-08T09:55:52Z"),
"configVersion" : 4
},
{
"_id" : 2,
"name" : "localhost.localdomain:50002",
"health" : 1,
"state" : 7,
"stateStr" : "ARBITER",
"uptime" : 806,
"configVersion" : 4,
"self" : true
}
],
"ok" : 1
}
重新启动DB01。
$ mongod --port=50000 --dbpath=/var/lib/mongodb/db01 --logpath=/var/log/mongodb/db01.log --replSet=LocalRep --fork
仅需一种选项即可在中文中将以下句子进行翻译:几秒钟后
- DB01の状態
$ mongo --port 50000
LocalRep:SECONDARY>
LocalRep:PRIMARY>
- DB02の状態
$ mongo --port 50001
LocalRep:PRIMARY>
2017-09-08T11:00:03.401+0100 I NETWORK [thread1] trying reconnect to 127.0.0.1:50001 (127.0.0.1) failed
2017-09-08T11:00:03.402+0100 I NETWORK [thread1] reconnect 127.0.0.1:50001 (127.0.0.1) ok
LocalRep:SECONDARY>
- DB03でステータスを確認
LocalRep:ARBITER> rs.status()
{
"set" : "LocalRep",
"date" : ISODate("2017-09-08T10:05:11.992Z"),
"myState" : 7,
"term" : NumberLong(3),
"heartbeatIntervalMillis" : NumberLong(2000),
"optimes" : {
"lastCommittedOpTime" : {
"ts" : Timestamp(1504865102, 1),
"t" : NumberLong(3)
},
"appliedOpTime" : {
"ts" : Timestamp(1504865102, 1),
"t" : NumberLong(3)
},
"durableOpTime" : {
"ts" : Timestamp(0, 0),
"t" : NumberLong(-1)
}
},
"members" : [
{
"_id" : 0,
"name" : "localhost.localdomain:50000",
"health" : 1,
"state" : 1,
"stateStr" : "PRIMARY",
"uptime" : 317,
"optime" : {
"ts" : Timestamp(1504865102, 1),
"t" : NumberLong(3)
},
"optimeDurable" : {
"ts" : Timestamp(1504865102, 1),
"t" : NumberLong(3)
},
"optimeDate" : ISODate("2017-09-08T10:05:02Z"),
"optimeDurableDate" : ISODate("2017-09-08T10:05:02Z"),
"lastHeartbeat" : ISODate("2017-09-08T10:05:09.553Z"),
"lastHeartbeatRecv" : ISODate("2017-09-08T10:05:11.725Z"),
"pingMs" : NumberLong(0),
"electionTime" : Timestamp(1504864801, 1),
"electionDate" : ISODate("2017-09-08T10:00:01Z"),
"configVersion" : 4
},
{
"_id" : 1,
"name" : "localhost.localdomain:50001",
"health" : 1,
"state" : 2,
"stateStr" : "SECONDARY",
"uptime" : 1036,
"optime" : {
"ts" : Timestamp(1504865102, 1),
"t" : NumberLong(3)
},
"optimeDurable" : {
"ts" : Timestamp(1504865102, 1),
"t" : NumberLong(3)
},
"optimeDate" : ISODate("2017-09-08T10:05:02Z"),
"optimeDurableDate" : ISODate("2017-09-08T10:05:02Z"),
"lastHeartbeat" : ISODate("2017-09-08T10:05:09.524Z"),
"lastHeartbeatRecv" : ISODate("2017-09-08T10:05:10.254Z"),
"pingMs" : NumberLong(0),
"syncingTo" : "localhost.localdomain:50000",
"configVersion" : 4
},
{
"_id" : 2,
"name" : "localhost.localdomain:50002",
"health" : 1,
"state" : 7,
"stateStr" : "ARBITER",
"uptime" : 1323,
"configVersion" : 4,
"self" : true
}
],
"ok" : 1
}
确认DB01复活后,数秒后被正确识别为主节点并开始运行,
DB02也正常地以从节点的形式处于稳定运行状态,
成功实现故障转移!
下一步是对程序设置和连接进行调查和验证,更多详细信息,请参阅《MongoDB故障转移时 Node.js 程序控制和操作确认》一文。
总结
使用非常简单的设置,可以在”主服务器”、”备用服务器”和”仲裁者”配置中进行复制和故障转移,这非常令人感激! 不过,在实际运营中,还需要根据服务的性质来确定配置和运营策略,比如”主服务器”、”备用服务器”、”备用服务器”配置或禁止”备用服务器”升级等。如果深入追求,会变得更加复杂。下一步,我想在实际的故障转移中测试负载时间和复制延迟!