MongoDB副本集初探

一、副本集简介

   副本集是一组服务器,其中有一个主节点(Primary),用于处理客户的请求;还有多个备份节点(Secondary),用于保存主节点的数据副本。如果主节点崩溃了,备份节点会自动将其中一个成员升级为新的主节点。所以,在生产中,可以使用副本集将数据保存到多台服务器上,即使其中一台或者多台服务器崩溃了,也能保证应用程序的正常运行和数据安全。

二、副本集复制原理

   1.数据写入主节点,同时向local.oplog.rs集合中写入一条oplog。
   2.备份节点第一次同步数据时,会先进行init sync,然后从主节点同步全量数据
   3.随后,备份节点不断通过tailable cursor从主节点的local.oplog.rs集合中查询最新的oplog并应用到自身

三、副本集的搭建

   本文为了方便使用单台服务器,指定不同的端口号来模拟副本集的搭建,进行测试学习。
1、环境准备

主节点:192.168.1.73:28000
备份节点1:192.168.1.73:28001
备份节点2:192.168.1.73:28002

2、安装mongodb
   1)下载安装包并解压

# curl -O https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-3.0.6.tgz    # 下载
# tar -zxvf mongodb-linux-x86_64-3.0.6.tgz                                   # 解压    
# mv  mongodb-linux-x86_64-3.0.6/ /usr/local/mongodb                         # 将解压包拷贝到指定目录

   2)将MongoDB 添加到 PATH 路径中:

# cat /root/.bash_profile 
PATH=$PATH:$HOME/bin:/usr/local/mongodb/bin
# source /root/.bash_profile

3、运行三个MongoDB服务
   1)配置文件

dbpath=/usr/local/repl0/                  #数据目录
logpath=/var/log/mongodb/repl0.log        #日志文件
logappend=true                            #日志追加
port = 28000                              #端口
maxConns = 50                             #最大连接数
pidfilepath = /var/run/repl0.pid
journal = true                        
journalCommitInterval = 200               #刷写提交机制
fork = true                               #守护进程模式
syncdelay = 60                            #刷写数据到日志的频率
oplogSize = 1000                          #操作日志,单位M
nssize = 16                               #命名空间的文件大小,默认16M,最大2G。
noauth = true
unixSocketPrefix = /tmp
replSet=rs0                               #副本集名称,同一个副本集,名称必须一致

   需要注意的是:
      1.数据目录和日志文件目录需要提前创建,否则启动MongoDB找不到路径会启动失败
      2.由于搭建在同一台服务器上,三台MongoDB服务之间的dbpath、logpath、port需要不同,且端口无占用。
   2)运行MongoDB服务
      运行MongoDB服务可以直接使用命令行启动,也可以根据配置文件来启动,本文选择后者。

# mongod -f mongod_28000.conf 
# mongod -f mongod_28001.conf 
# mongod -f mongod_28002.conf

   3)查看进程是否运行

# ps -ef | grep mongo

3、配置副本集
   1)登录master服务器

# mongo 192.168.1.73:28000

   2)初始化副本集

> cnf = {"_id":"rs0","members":[
... {"_id":1,"host":"192.168.1.73:28000","priority":1 },
... {"_id":2, "host":"192.168.1.73:28001","priority":1},
... {"_id":3, "host":"192.168.1.73:28002","priority":1}]
... }
{
    "_id" : "rs0",                                #副本集名称
    "members" : [                                 #指定副本集成员
        {
            "_id" : 1,                            #服务器的唯一id
            "host" : "192.168.1.73:28000",        #服务器主机
            "priority" : 1                        #优先级,默认为1,可设着为0-100,值越高优先级越大。优先级0为被动节点,不能成为活跃节点。
        },
        {
            "_id" : 2,
            "host" : "192.168.1.73:28001",
            "priority" : 1
        },
        {
            "_id" : 3,
            "host" : "192.168.1.73:28002",
            "priority" : 1
        }
    ]
}

> rs.initiate(cnf)                                 #初始化副本集
{ "ok" : 1 }

   3)查看副本集是否配置成功

rs0:OTHER> rs.status()
{
    "set" : "rs0",                                        #副本集名称
    "date" : ISODate("2018-06-28T16:55:50.130Z"),
    "myState" : 1,
    "members" : [
        {
            "_id" : 1,
            "name" : "192.168.1.73:28000",
            "health" : 1,
            "state" : 1,
            "stateStr" : "PRIMARY",                    #主服务器
            "uptime" : 669,
            "optime" : Timestamp(1530204929, 1),
            "optimeDate" : ISODate("2018-06-28T16:55:29Z"),
            "electionTime" : Timestamp(1530204931, 1),
            "electionDate" : ISODate("2018-06-28T16:55:31Z"),
            "configVersion" : 1,                        #配置版本,每次更新配置会自动+1
            "self" : true
        },
        {
            "_id" : 2,
            "name" : "192.168.1.73:28001",
            "health" : 1,
            "state" : 2,
            "stateStr" : "SECONDARY",                    #备份服务器
            "uptime" : 20,
            "optime" : Timestamp(1530204929, 1),
            "optimeDate" : ISODate("2018-06-28T16:55:29Z"),
            "lastHeartbeat" : ISODate("2018-06-28T16:55:49.612Z"),
            "lastHeartbeatRecv" : ISODate("2018-06-28T16:55:49.625Z"),
            "pingMs" : 0,
            "configVersion" : 1
        },
        {
            "_id" : 3,
            "name" : "192.168.1.73:28002",
            "health" : 1,
            "state" : 2,
            "stateStr" : "SECONDARY",
            "uptime" : 20,
            "optime" : Timestamp(1530204929, 1),
            "optimeDate" : ISODate("2018-06-28T16:55:29Z"),
            "lastHeartbeat" : ISODate("2018-06-28T16:55:49.612Z"),
            "lastHeartbeatRecv" : ISODate("2018-06-28T16:55:49.625Z"),
            "pingMs" : 0,
            "configVersion" : 1
        }
    ],
    "ok" : 1
}

四、功能测试

1、复制功能
   1)登录主节点,并在主节点中插入测试数据

rs0:PRIMARY> use test
switched to db test
rs0:PRIMARY> db.test.insert({"name":"aa",age:123})              #插入测试数据
WriteResult({ "nInserted" : 1 })
rs0:PRIMARY> db.test.insert({"name":"bb",age:132})
WriteResult({ "nInserted" : 1 })
rs0:PRIMARY> db.test.insert({"name":"cc",age:143})
WriteResult({ "nInserted" : 1 })
rs0:PRIMARY> db.test.find().pretty()                            #查看数据插入成功
{ "_id" : ObjectId("5b351827a131286ef6528c33"), "name" : "aa", "age" : 123 }
{ "_id" : ObjectId("5b351830a131286ef6528c34"), "name" : "bb", "age" : 132 }
{ "_id" : ObjectId("5b351836a131286ef6528c35"), "name" : "cc", "age" : 143 }

   2)登录备份节点、检查数据是否同步

# mongo 192.168.1.73:28001                        #登录备份节点
rs0:SECONDARY> show dbs                           #查看当前节点所有数据库
2018-06-28T13:18:45.565-0400 E QUERY    Error: listDatabases failed:{ "note" : "from execCommand", "ok" : 0, "errmsg" : "not master" }
    at Error (<anonymous>)
    at Mongo.getDBs (src/mongo/shell/mongo.js:47:15)
    at shellHelper.show (src/mongo/shell/utils.js:630:33)
    at shellHelper (src/mongo/shell/utils.js:524:36)
    at (shellhelp2):1:1 at src/mongo/shell/mongo.js:47

rs0:SECONDARY> use test
switched to db test
rs0:SECONDARY> db.test.find().pretty()            #查看test数据库下所有数据
Error: error: { "$err" : "not master and slaveOk=false", "code" : 13435 }

在备份节点中执行查看数据库的命令发现以上报错,这是为什么呢?
这是因为备份节点默认是不对外提供读写功能的,如果想在备份节点提供读操作的话,需要执行rs.slaveOk() 。

rs0:SECONDARY> rs.slaveOk() 
rs0:SECONDARY> use test
switched to db test
rs0:SECONDARY> db.test.find().pretty()                                #数据同步成功
{ "_id" : ObjectId("5b351827a131286ef6528c33"), "name" : "aa", "age" : 123 }
{ "_id" : ObjectId("5b351830a131286ef6528c34"), "name" : "bb", "age" : 132 }
{ "_id" : ObjectId("5b351836a131286ef6528c35"), "name" : "cc", "age" : 143 }

2、主从故障切换
对于配置在不同服务器上MongoDB在联网状态下可以通过在主机器添加防火墙规则,模拟主服务器宕机。对于本文,在同一服务器上部署了三个MongoDB的情况,我们选择杀死指令杀死主节点进程来模拟主节点的宕机。

   1)模拟主节点宕机

# ps -ef | grep mongod_28000
root     48971     1  0 12:44 ?        00:00:07 mongod -f mongod_28000.conf
root     49086     1  0 12:45 ?        00:00:06 mongod -f mongod_28001.conf
root     49369     1  0 12:46 ?        00:00:06 mongod -f mongod_28002.conf
root     60690 33319  0 13:34 pts/0    00:00:00 grep --color=auto mongo
# kill 48971
# ps -ef | grep mongo
root     49086     1  0 12:45 ?        00:00:06 mongod -f mongod_28001.conf
root     49369     1  0 12:46 ?        00:00:06 mongod -f mongod_28002.conf
root     60739 33319  0 13:34 pts/0    00:00:00 grep --color=auto mongo

   2)登录备份节点,查看其状态是否被提升为主节点

# mongo 192.168.1.73:28001
rs0:SECONDARY> quit()                #该服务器仍然为备份节点
# mongo 192.168.1.73:28002
rs0:PRIMARY>                         #发现28002端口的备份节点被提升为主节点

   3)查看当前副本集状态

rs0:PRIMARY> rs.status()
{
    "set" : "rs0",
    "date" : ISODate("2018-06-28T17:37:39.897Z"),
    "myState" : 1,
    "members" : [
        {
            "_id" : 1,
            "name" : "192.168.1.73:28000",
            "health" : 0,
            "state" : 8,
            "stateStr" : "(not reachable/healthy)",                    #状态不健康
            "uptime" : 0,
            "optime" : Timestamp(0, 0),
            "optimeDate" : ISODate("1970-01-01T00:00:00Z"),
            "lastHeartbeat" : ISODate("2018-06-28T17:37:39.102Z"),
            "lastHeartbeatRecv" : ISODate("2018-06-28T17:34:24.858Z"),
            "pingMs" : 0,
            "lastHeartbeatMessage" : "Failed attempt to connect to 192.168.1.73:28000; couldn't connect to server 192.168.1.73:28000 (192.168.1.73), connection attempt failed",
            "configVersion" : -1
        },
        {
            "_id" : 2,
            "name" : "192.168.1.73:28001",
            "health" : 1,
            "state" : 2,
            "stateStr" : "SECONDARY",
            "uptime" : 2530,
            "optime" : Timestamp(1530206262, 1),
            "optimeDate" : ISODate("2018-06-28T17:17:42Z"),
            "lastHeartbeat" : ISODate("2018-06-28T17:37:39.050Z"),
            "lastHeartbeatRecv" : ISODate("2018-06-28T17:37:38.968Z"),
            "pingMs" : 0,
            "configVersion" : 1                                        #配置文件版本+1
        },
        {
            "_id" : 3,
            "name" : "192.168.1.73:28002",
            "health" : 1,
            "state" : 1,
            "stateStr" : "PRIMARY",                                #新的主节点
            "uptime" : 3065,
            "optime" : Timestamp(1530206262, 1),
            "optimeDate" : ISODate("2018-06-28T17:17:42Z"),
            "electionTime" : Timestamp(1530207267, 1),
            "electionDate" : ISODate("2018-06-28T17:34:27Z"),
            "configVersion" : 1,
            "self" : true
        }
    ],
    "ok" : 1
}

   4)重新启动宕机的主节点192.168.1.73:28000

rs0:PRIMARY> rs.status()
{
    "set" : "rs0",
    "date" : ISODate("2018-07-01T16:12:15.964Z"),
    "myState" : 1,
    "members" : [
        {
            "_id" : 1,
            "name" : "192.168.1.73:28000",
            "health" : 1,
            "state" : 2,
            "stateStr" : "SECONDARY",                            #重新启动宕机的主节点,自动将其将为备份节点
            "uptime" : 21,
            "optime" : Timestamp(1530461212, 1),
            "optimeDate" : ISODate("2018-07-01T16:06:52Z"),
            "lastHeartbeat" : ISODate("2018-07-01T16:12:14.297Z"),
            "lastHeartbeatRecv" : ISODate("2018-07-01T16:12:15.467Z"),
            "pingMs" : 0,
            "configVersion" : 1                                  #配置文件版本为1
        },
        {
            "_id" : 2,
            "name" : "192.168.1.73:28001",
            "health" : 1,
            "state" : 2,
            "stateStr" : "SECONDARY",
            "uptime" : 359,
            "optime" : Timestamp(1530461212, 1),
            "optimeDate" : ISODate("2018-07-01T16:06:52Z"),
            "lastHeartbeat" : ISODate("2018-07-01T16:12:14.289Z"),
            "lastHeartbeatRecv" : ISODate("2018-07-01T16:12:14.264Z"),
            "pingMs" : 0,
            "configVersion" : 1
        },
        {
            "_id" : 3,
            "name" : "192.168.1.73:28002",
            "health" : 1,
            "state" : 1,
            "stateStr" : "PRIMARY",
            "uptime" : 421,
            "optime" : Timestamp(1530461212, 1),
            "optimeDate" : ISODate("2018-07-01T16:06:52Z"),
            "electionTime" : Timestamp(1530461444, 1),
            "electionDate" : ISODate("2018-07-01T16:10:44Z"),
            "configVersion" : 1,
            "self" : true
        }
    ],
    "ok" : 1
}

3、主从手动切换
   手动切换主节点为192.168.1.73:28000
   1)重新定义配置文件
      1.将192.168.1.73:28000优先级的优先级调大

rs0:PRIMARY> cfg=rs.conf()
rs0:PRIMARY> cfg.members[0].priority=2                #将192.168.1.73:28000节点的优先级设置为2

      2.重新加载配置文件

rs0:PRIMARY> rs.reconfig(cfg)

      3.查看当前配置文件是否修改成功

rs0:SECONDARY> rs.conf()                            #可以看到当前节点已经由rs0:PRIMARY变为rs0:SECONDARY

      4.登录192.168.1.73:28000节点,发现其变为主节点

五、如何设计副本集

   副本集遵循“大多数”原则,“大多数”即一半以上的成员,副本集在选择主节点的时候需要进行选举投票,只有大多数成员支持的情况下,该节点才能成为主节点。
1、怎么才算大多数
   假设复制集内投票成员数量为N,则大多数为 N/2 + 1,当复制集内存活成员数量不足大多数时,整个复制集将无法选举出Primary,复制集将无法提供写服务,处于只读状态。
2、配置方式一
   将“大多数”成员放在同一个数据中心,并将副本集的主节点放置在该数据中心,该数据节点的优先级大于另一数据节点。
   如5个节点,3个节点放置待数据中心A,两个节点放置在数据中心B。
   情况1:主节点在数据中心A,主节点宕机,A、B两数据中心可以联通,4个节点进行投票选举,“大多数”支持的节点被提升为主节点。
   情况2:主节点在数据中心A,A、B两数据中心因为网络原因不可以联通,A数据区域正常运行,B数据区域因为只有2个节点,不能通过选举提升其中一个备份节点为主节点,保证不会同时出现两个主节点
   情况3:主节点在数据中心A,主节点宕机且A、B两数据中心因为网络原因导致不可以联通,所有成员无法提升为主节点

3、配置方式二
   将两个数据中心放置相同数量的成员,同时增加一个成员(选举仲裁者)放置在其他服务器上,该成员可以不需要复制数据,只是用来进行投票选举。
   如:共5个节点,数据A、B区域各放置2个节点,另外一台服务器上放置选举仲裁者。
   情况1:主节点在数据中心A,A、B两数据中心可以联通,主节点宕机后,4个节点进行投票选举,“大多数”支持的节点被提升为主节点,仲裁者不能成为主节点,只参与投票选举。
   情况2:主节点在数据中心A,主节点宕机且A、B两数据中心可以联通,这时A数据中心2个节点都变为备份节点,B数据中心2个节点+仲裁者进行投票选举提升一个节点为主节点,保证主节点的唯一性。
   情况3:主节点在数据中心A,主节点宕机且A、B两数据中心因为网路原因导致不可以联通,这时A数据中心2个节点都变为备份节点,B数据中心2个节点+仲裁者进行投票选举提升一个节点为主节点,保证主节点的唯一性。

上一篇:睿云智合荣获“2017中国信息技术年度云计算最佳产品奖”


下一篇:睿云智合(Wise2C)谈论docker