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"} ] });
输出结果:
1{ 2 "ok" : 1, 3 "operationTime" : Timestamp(1523237919, 1), 4 "$clusterTime" : { 5 "clusterTime" : Timestamp(1523237919, 1), 6 "signature" : { 7 "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="), 8 "keyId" : NumberLong(0) 9 } 10 } 11}-
查看配置结果
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" : {
1 }, 2 "slaveDelay" : NumberLong(0), 3 "votes" : 1 4 }, 5 { 6 "_id" : 1, 7 "host" : "192.168.10.59:27017", 8 "arbiterOnly" : false, 9 "buildIndexes" : true, 10 "hidden" : false, 11 "priority" : 1, 12 "tags" : { 13 14 }, 15 "slaveDelay" : NumberLong(0), 16 "votes" : 1 17 } 18], 19"settings" : { 20 "chainingAllowed" : true, 21 "heartbeatIntervalMillis" : 2000, 22 "heartbeatTimeoutSecs" : 10, 23 "electionTimeoutMillis" : 10000, 24 "catchUpTimeoutMillis" : -1, 25 "catchUpTakeoverDelayMillis" : 30000, 26 "getLastErrorModes" : { 27 28 }, 29 "getLastErrorDefaults" : { 30 "w" : 1, 31 "wtimeout" : 0 32 }, 33 "replicaSetId" : ObjectId("5acac41fded47067da446ddd") 34}}
配置过程非常简单,可以看到在副本集成员中有0和1两个节点,此时主从两个服务器已经可以工作了,服务器0(Primary)有任何数据的变化都会同步到服务器1(Secondary)上。但是,此时的副本集只是提供了数据备份的功能,并不能达到高可用。如果要达到这一点,那么需要配置一个仲裁者节点(Arbiter)
-
配置仲裁者(Arbiter)
仲裁者在 Primary 节点发生故障时,参与副本集的选举投票决定哪个副本成为 Primary 节点。仲裁节点不保存数据也不会成为 Primary 节点。
-
仲裁者通常不部署在大磁盘空间的服务器上,因此为了最小化默认创建数据,修改配置:
1$ vim /etc/mongod.conf 2 3 4storage.journal.enabled=false 5storage.mmapv1.smallFiles = true. -
创建仲裁者目录并启动服务
1$ mkdir /mnt/mongodb/arbiter 2$ mongod --port 27017 --dbpath /mnt/mongodb/arbiter --replSet 'my-repl' --bind_ip_all -
把仲裁者添加到副本集中
-
连接至 Primary 服务器
1$mongo --host 192.168.10.58 2 3 4my-repl:PRIMARY> rs.addArb("192.168.10.57:27017") 5{ 6 "ok" : 1, 7 "operationTime" : Timestamp(1523326877, 1), 8 "$clusterTime" : { 9 "clusterTime" : Timestamp(1523326877, 1), 10 "signature" : { 11 "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="), 12 "keyId" : NumberLong(0) 13 } 14 } 15}
-
-
查看副本集的效果:
1>rs.status(); 2my-repl:PRIMARY> rs.status(); 3{ 4 "set" : "my-repl", 5 "date" : ISODate("2018-04-10T02:21:44.826Z"), 6 "myState" : 1, 7 "term" : NumberLong(2), 8 "heartbeatIntervalMillis" : NumberLong(2000), 9 "optimes" : { 10 "lastCommittedOpTime" : { 11 "ts" : Timestamp(1523326895, 1), 12 "t" : NumberLong(2) 13 }, 14 "readConcernMajorityOpTime" : { 15 "ts" : Timestamp(1523326895, 1), 16 "t" : NumberLong(2) 17 }, 18 "appliedOpTime" : { 19 "ts" : Timestamp(1523326895, 1), 20 "t" : NumberLong(2) 21 }, 22 "durableOpTime" : { 23 "ts" : Timestamp(1523326895, 1), 24 "t" : NumberLong(2) 25 } 26 }, 27 "members" : [ 28 { 29 "_id" : 0, 30 "name" : "192.168.10.58:27017", 31 "health" : 1, 32 "state" : 1, 33 "stateStr" : "PRIMARY", 34 "uptime" : 2891, 35 "optime" : { 36 "ts" : Timestamp(1523326895, 1), 37 "t" : NumberLong(2) 38 }, 39 "optimeDate" : ISODate("2018-04-10T02:21:35Z"), 40 "electionTime" : Timestamp(1523324284, 1), 41 "electionDate" : ISODate("2018-04-10T01:38:04Z"), 42 "configVersion" : 2, 43 "self" : true 44 }, 45 { 46 "_id" : 1, 47 "name" : "192.168.10.59:27017", 48 "health" : 1, 49 "state" : 2, 50 "stateStr" : "SECONDARY", 51 "uptime" : 2624, 52 "optime" : { 53 "ts" : Timestamp(1523326895, 1), 54 "t" : NumberLong(2) 55 }, 56 "optimeDurable" : { 57 "ts" : Timestamp(1523326895, 1), 58 "t" : NumberLong(2) 59 }, 60 "optimeDate" : ISODate("2018-04-10T02:21:35Z"), 61 "optimeDurableDate" : ISODate("2018-04-10T02:21:35Z"), 62 "lastHeartbeat" : ISODate("2018-04-10T02:21:43.080Z"), 63 "lastHeartbeatRecv" : ISODate("2018-04-10T02:21:43.083Z"), 64 "pingMs" : NumberLong(0), 65 "syncingTo" : "192.168.10.58:27017", 66 "configVersion" : 2 67 }, 68 { 69 "_id" : 2, 70 "name" : "192.168.10.57:27017", 71 "health" : 1, 72 "state" : 7, 73 "stateStr" : "ARBITER", 74 "uptime" : 27, 75 "lastHeartbeat" : ISODate("2018-04-10T02:21:43.079Z"), 76 "lastHeartbeatRecv" : ISODate("2018-04-10T02:21:42.088Z"), 77 "pingMs" : NumberLong(0), 78 "configVersion" : 2 79 } 80 ], 81 "ok" : 1, 82 "operationTime" : Timestamp(1523326895, 1), 83 "$clusterTime" : { 84 "clusterTime" : Timestamp(1523326895, 1), 85 "signature" : { 86 "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="), 87 "keyId" : NumberLong(0) 88 } 89 } 90}可以看到状态显示:服务器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的读取和写入性能是比较好的,特别是导入的同时还要和副本之间同步数据。