MongoDB副本集配置和数据迁移实战
环境:Ubuntu 16.04, MongoDB 3.6
基本概念
MongoDB 的副本集就是有自动故障恢复功能的 MongoDB 主从集群。由于 MongoDB 的主从复制功能不支持高可用,所以从 3.2 版本开始已经被废弃了,转而用副本集来代替。一个副本集总会有一个活跃节点(Primary)和若干个备份节点(Secondary),还有一个可选的一个仲裁者(Arbiter)节点来实现HA中的故障切换。
准备工作
准备三台服务器(虚拟机亦可),系统全部安装Ubuntu 16.04 例如:
- Primary 192.168.10.58
- Secondary 192.168.10.59
- Arbiter 192.168.10.57
三台服务器上都安装好 MongoDB 3.6
参考官方的安装文档 https://docs.mongodb.com/manual/tutorial/install-mongodb-on-ubuntu/
配置副本集 Primary 和 Secondary 节点
创建数据目录
$ mkdir -p /mnt/mongodb/replset
启动名为“my-repl”的副本集,端口为27017,绑定到任意IP(也可以指定IP)
$ mongod --dbpath /mnt/mongodb/replset --port 27017 --replSet "my-repl" --bind_ip_all
初始化副本集
用mongo客户端连接 Primary 节点:
$ mongo
执行初始化脚本来创建副本集:
rs.initiate({ _id:"my-repl", members:[ {_id:0, host:"192.168.10.58:27017"}, {_id:1, host:"192.168.10.59:27017"} ] });
输出结果:
{ "ok" : 1, "operationTime" : Timestamp(1523237919, 1), "$clusterTime" : { "clusterTime" : Timestamp(1523237919, 1), "signature" : { "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="), "keyId" : NumberLong(0) } } }
查看配置结果
rs.conf(); { "_id" : "my-repl", "version" : 1, "protocolVersion" : NumberLong(1), "members" : [ { "_id" : 0, "host" : "192.168.10.58:27017", "arbiterOnly" : false, "buildIndexes" : true, "hidden" : false, "priority" : 1, "tags" : {
}, "slaveDelay" : NumberLong(0), "votes" : 1 }, { "_id" : 1, "host" : "192.168.10.59:27017", "arbiterOnly" : false, "buildIndexes" : true, "hidden" : false, "priority" : 1, "tags" : { }, "slaveDelay" : NumberLong(0), "votes" : 1 } ], "settings" : { "chainingAllowed" : true, "heartbeatIntervalMillis" : 2000, "heartbeatTimeoutSecs" : 10, "electionTimeoutMillis" : 10000, "catchUpTimeoutMillis" : -1, "catchUpTakeoverDelayMillis" : 30000, "getLastErrorModes" : { }, "getLastErrorDefaults" : { "w" : 1, "wtimeout" : 0 }, "replicaSetId" : ObjectId("5acac41fded47067da446ddd") }
}
配置过程非常简单,可以看到在副本集成员中有0和1两个节点,此时主从两个服务器已经可以工作了,服务器0(Primary)有任何数据的变化都会同步到服务器1(Secondary)上。但是,此时的副本集只是提供了数据备份的功能,并不能达到高可用。如果要达到这一点,那么需要配置一个仲裁者节点(Arbiter)
配置仲裁者(Arbiter)
仲裁者在 Primary 节点发生故障时,参与副本集的选举投票决定哪个副本成为 Primary 节点。仲裁节点不保存数据也不会成为 Primary 节点。
仲裁者通常不部署在大磁盘空间的服务器上,因此为了最小化默认创建数据,修改配置:
$ vim /etc/mongod.conf storage.journal.enabled=false storage.mmapv1.smallFiles = true.
创建仲裁者目录并启动服务
$ mkdir /mnt/mongodb/arbiter $ mongod --port 27017 --dbpath /mnt/mongodb/arbiter --replSet 'my-repl' --bind_ip_all
把仲裁者添加到副本集中
连接至 Primary 服务器
$mongo --host 192.168.10.58 my-repl:PRIMARY> rs.addArb("192.168.10.57:27017") { "ok" : 1, "operationTime" : Timestamp(1523326877, 1), "$clusterTime" : { "clusterTime" : Timestamp(1523326877, 1), "signature" : { "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="), "keyId" : NumberLong(0) } } }
查看副本集的效果:
>rs.status(); my-repl:PRIMARY> rs.status(); { "set" : "my-repl", "date" : ISODate("2018-04-10T02:21:44.826Z"), "myState" : 1, "term" : NumberLong(2), "heartbeatIntervalMillis" : NumberLong(2000), "optimes" : { "lastCommittedOpTime" : { "ts" : Timestamp(1523326895, 1), "t" : NumberLong(2) }, "readConcernMajorityOpTime" : { "ts" : Timestamp(1523326895, 1), "t" : NumberLong(2) }, "appliedOpTime" : { "ts" : Timestamp(1523326895, 1), "t" : NumberLong(2) }, "durableOpTime" : { "ts" : Timestamp(1523326895, 1), "t" : NumberLong(2) } }, "members" : [ { "_id" : 0, "name" : "192.168.10.58:27017", "health" : 1, "state" : 1, "stateStr" : "PRIMARY", "uptime" : 2891, "optime" : { "ts" : Timestamp(1523326895, 1), "t" : NumberLong(2) }, "optimeDate" : ISODate("2018-04-10T02:21:35Z"), "electionTime" : Timestamp(1523324284, 1), "electionDate" : ISODate("2018-04-10T01:38:04Z"), "configVersion" : 2, "self" : true }, { "_id" : 1, "name" : "192.168.10.59:27017", "health" : 1, "state" : 2, "stateStr" : "SECONDARY", "uptime" : 2624, "optime" : { "ts" : Timestamp(1523326895, 1), "t" : NumberLong(2) }, "optimeDurable" : { "ts" : Timestamp(1523326895, 1), "t" : NumberLong(2) }, "optimeDate" : ISODate("2018-04-10T02:21:35Z"), "optimeDurableDate" : ISODate("2018-04-10T02:21:35Z"), "lastHeartbeat" : ISODate("2018-04-10T02:21:43.080Z"), "lastHeartbeatRecv" : ISODate("2018-04-10T02:21:43.083Z"), "pingMs" : NumberLong(0), "syncingTo" : "192.168.10.58:27017", "configVersion" : 2 }, { "_id" : 2, "name" : "192.168.10.57:27017", "health" : 1, "state" : 7, "stateStr" : "ARBITER", "uptime" : 27, "lastHeartbeat" : ISODate("2018-04-10T02:21:43.079Z"), "lastHeartbeatRecv" : ISODate("2018-04-10T02:21:42.088Z"), "pingMs" : NumberLong(0), "configVersion" : 2 } ], "ok" : 1, "operationTime" : Timestamp(1523326895, 1), "$clusterTime" : { "clusterTime" : Timestamp(1523326895, 1), "signature" : { "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="), "keyId" : NumberLong(0) } } }
可以看到状态显示:服务器0为 Primary, 服务器1为 Secondary, 服务器2为 Arbiter。
此时带有高可用的副本集已经配置完成,Arbiter 会监控 Primary 节点的运行情况,如果服务器0发生了宕机,那么仲裁者 Arbiter 节点会发起选举,最终选取多个 Secondary 中的某一个来作为 Primary 节点。我们测试的架构中只有一个 Secondary 节点,那么此服务器1就会成为 Primary。而当服务器0恢复工作时,它会被当成 Secondary 来运行。副本优先级
副本集会在 Primary 节点出现故障时把 Secondary 提升为 Primary,但是很多情况下 Secondary 只是作为备用节点,我们不希望它长期作为 Primary 节点运行。那么为了达到这个目的,我们修改副本的优先级。连接 Primary 节点,执行以下脚本:
cfg = rs.conf() cfg.members[0].priority = 10 cfg.members[1].priority = 5 rs.reconfig(cfg) { "ok" : 1, "operationTime" : Timestamp(1523411797, 2), "$clusterTime" : { "clusterTime" : Timestamp(1523411797, 2), "signature" : { "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="), "keyId" : NumberLong(0) } } }
如果在非 Prmiary 上比如Arbiter上运行,会报下面的错误:
{ "ok" : 0, "errmsg" : "replSetReconfig should only be run on PRIMARY, but my state is ARBITER; use the "force" argument to override", "code" : 10107, "codeName" : "NotMaster" }
以上操作让服务器0的优先级高于服务器1,那么当服务器0从故障中恢复时,它会重新成为 Primary 节点来提供服务。
数据迁移
在配置副本集之前,你可能已经存在了一个单独的 MongoDB 实例存储了一些数据,你需要把原来实例中的数据迁移到新的副本集中。(你也可以一开始就把原来的单个实例配置成副本集的 Primary 节点,然后新增副本来同步数据,此方法不在本文讨论范围之内)
登录原实例所在的服务器,导出数据:
$ mongoexport -h localhost -p 27017 -u xxx -p xxx -d MY_DB_NAME -c MY_COLLECTION_NAME -o MY_COLLECTION_NAME.dmp
在阿里云上测试了一个导出数据大约为1.2G大小的集合,导出性能如下:
- 时间:3分11秒
- 导出1728423条记录,每秒读取记录数=1728423/191=9050条/秒
- 导出1264441038字节,每秒处理字节数=1264441038/191=6.6M/秒
将导出的数据文件 MY_COLLECTION_NAME.dmp 已任何方式(比如scp)同步到 Primary 节点所在的服务器0上
导入数据至副本集 Primay 节点
mongoimport -h my-repl/192.168.10.58:27017 -d MY_DB_NAME -c MY_COLLECTION_NAME --file MY_COLLECTION_NAME.dmp
测试导入性能
- 时间:82秒
- 导入1728423条记录,每秒写入记录数=1728423/82=21078条/秒
- 导入1264441038字节,每秒处理字节数=1264441038/82=14.7M/秒
注意:由于不是相同的服务器,所以不能通过这个来比较导入和导出的性能差别,只能所谓一个参考:
总结:
- 在 Primary 节点导入数据后,登录 Secondary 节点查看,可以看到一百七十多万条数据全部复制过去了,主从复制成功。
- 从性能分析结果来看,MongoDB的读取和写入性能是比较好的,特别是导入的同时还要和副本之间同步数据。