MongoDB基础可参考https://blog.51cto.com/kaliarch/2044423

一、概述

1.1 MongoDB副本集

通俗来讲,mongodb的副本集相当于具有自动故障恢复的主从集群,主从集群和副本集最明显的特征为副本集没有固定的“主节点”,整个集群会通过一定的算法选举出主节点,目前MongoDB官方已经不建议使用主从模式了,在主从模式下,如果主数据库宕机,从数据库无法自动接管主数据库,从而无法接入数据,取而代之的就是MongoDB副本集模式,主服务器负责整个副本集的读写,副本集定期同步数据备份,副本集中的副本节点在主节点挂掉后通过心跳机制检测到后副本节点就会选举一个新的主服务器,这一切对于应用服务器无需关心。

1.2 架构图

图片.png

1.3 复制原理

  • mongodb的复制至少需要两个节点。其中一个是主节点,负责处理客户端请求,其余的都是从节点,负责复制主节点上的数据。

  • mongodb各个节点常见的搭配方式为:一主一从、一主多从。

  • 主节点记录在其上的所有操作oplog,从节点定期轮询主节点获取这些操作,然后对自己的数据副本执行这些操作,从而保证从节点的数据与主节点一致。

1.4 副本集特征:

  • N 个节点的集群

  • 任何节点可作为主节点

  • 所有写入操作都在主节点上

  • 自动故障转移

  • 自动恢复

1.5 Bully算法

如果副本集中主节点宕掉后,需要使用bully算法进行选举主节点,其主要思想为每个成员均可以声明自己为主节点并通知其他节点,别的节点可以选择接受这个声明或是拒绝并进入主节点竞争,只有被其他节点接受的节点才可以当主节点,

节点按照一些属性来判断谁应该胜出。这个属性可以是一个静态ID,也可以是更新的度量像最近一次事务ID(最新的节点会胜出)

官方描述:

  1. 得到每个服务器节点的最后操作时间戳。每个mongodb都有oplog机制会记录本机的操作,方便和主服务器进行对比数据是否同步还可以用于错误恢复。

  2. 如果集群中大部分服务器down机了,保留活着的节点都为 secondary状态并停止,不选举了。

  3. 如果集群中选举出来的主节点或者所有从节点最后一次同步时间看起来很旧了,停止选举等待人来操作。

  4. 如果上面都没有问题就选择最后操作时间戳最新(保证数据是最新的)的服务器节点作为主节点。

1.6 Replica Set成员

一个Replica Set中的成员角色有三种:Primary,SecondaryArbiter

  • Primary:接收来自客户端的所有的写操作,一个Replica Set中有且只有一个Primary。Primary如果宕掉,Replica Set会自动选举一个Secondary成为Primary。Primary将它data sets的所有操作都记录到oplog中。

  • Secondary:Secondary从Primary复制oplog,然后将oplog中的操作应用到自己的data sets。Secondary和Primary之间是异步复制,也就是Secondary中的数据可能不是最新的。默认情况下,Secondary不可读不可写,但是可以通过设置运行客户端从Secondary读。

        Secondary配置的三种用途:

        1.在选举中阻止其成为Primary,只用作备份数据。通过设置优先级priority为0来实现

        2.阻止应用程序从它读,通过设置优先级priority为0和设置hidden为true来实现。(一个隐藏的成员同样复制Primary的数据,但是对于客户端应用程序来讲,它不可见。)

        3.保留历史镜像数据用于数据回档,比如如果误删除数据,可以使用Delayed Replica Set成员中的数据恢复。

  • Arbiter:Arbiter不需要维护自己的data sets,只是当Primary挂掉之后参与投票选择哪个Secondary可以升级为Primary

Replica Set中的成员个数为偶数个时,就需要添加一个Arbiter用于投票选举哪个可以升级为Primary,不能在Primary或者Secondary主机上运行Arbiter

一个Replica Set可以最多拥有12个成员,但是只有7个成员可以同时参与投票选举成为Primary,如果成员数量超过12,就需要使用Master-Slave主从复制方式。

部署一个Replica Set至少需要三个成员,一个Arbiter,一个Secondary和一个Primary或者一个Primary,两个Secondary。

二、搭建部署

2.1 基础环境

主机名
IP地址
系统
mongodb-1
172.20.6.10CentOS release 6.9
mongodb-2172.20.6.11CentOS release 6.9
mongodb-3172.20.6.10CentOS release 6.9

2.2 软件安装

在三台服务器上依次安装mongodb

wget -c https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-rhel62-3.4.10.tgz
tar -zxvf mongodb-linux-x86_64-rhel62-3.4.10.tgz
ln -sv mongodb-linux-x86_64-rhel62-3.4.10 mongodb
mkdir /usr/local/mongodb/{conf,mongoData,mongoLog}
touch /usr/local/mongodb/mongoLog/mongodb.log
echo "export PATH=$PAHT:/usr/local/mongodb/bin">/etc/profile.d/mongodb.sh
source etc/profile.d/mongodb.sh

定义配置文件

cat >/usr/local/mongodb/conf/mongodb.conf<<EOF
dbpath=/usr/local/mongodb/mongoData
logpath=/usr/local/mongodb/mongoLog/mongodb.log
logappend=true 
journal=true
quiet=true
port=27017
replSet=RS                            #副本集名称
maxConns=20000
httpinterface=true
fork=true
EOF

依次启动三个mongodb

mongodb -f /usr/local/mongodb/conf/mongodb.conf

图片.png

2.3 副本集部署

挑选任意一台mongodb进行登录

use admin                #切换到admin数据库
config = {_id:"RS",members:[                #定义副本集配置
{_id:0,host:"172.20.6.10:27017"},
{_id:1,host:"172.20.6.11:27017"},
{_id:2,host:"172.20.6.12:27017"},]
}
rs.initiate(config);        #初始化副本集配置

图片.png

RS:PRIMARY> rs.status();                #查看副本集状态
{
    "set" : "RS",
    "date" : ISODate("2017-11-26T14:09:00.054Z"),
    "myState" : 1,
    "term" : NumberLong(1),
    "heartbeatIntervalMillis" : NumberLong(2000),
    "optimes" : {
        "lastCommittedOpTime" : {
            "ts" : Timestamp(1511705333, 1),
            "t" : NumberLong(1)
        },
        "appliedOpTime" : {
            "ts" : Timestamp(1511705333, 1),
            "t" : NumberLong(1)
        },
        "durableOpTime" : {
            "ts" : Timestamp(1511705333, 1),
            "t" : NumberLong(1)
        }
    },
    "members" : [
        {
            "_id" : 0,
            "name" : "172.20.6.10:27017",
            "health" : 1,
            "state" : 1,
            "stateStr" : "PRIMARY",                        #主节点
            "uptime" : 377,
            "optime" : {
                "ts" : Timestamp(1511705333, 1),
                "t" : NumberLong(1)
            },
            "optimeDate" : ISODate("2017-11-26T14:08:53Z"),
            "infoMessage" : "could not find member to sync from",
            "electionTime" : Timestamp(1511705241, 1),
            "electionDate" : ISODate("2017-11-26T14:07:21Z"),
            "configVersion" : 1,
            "self" : true
        },
        {
            "_id" : 1,
            "name" : "172.20.6.11:27017",
            "health" : 1,
            "state" : 2,
            "stateStr" : "SECONDARY",                #secondary节点
            "uptime" : 109,
            "optime" : {
                "ts" : Timestamp(1511705333, 1),
                "t" : NumberLong(1)
            },
            "optimeDurable" : {
                "ts" : Timestamp(1511705333, 1),
                "t" : NumberLong(1)
            },
            "optimeDate" : ISODate("2017-11-26T14:08:53Z"),
            "optimeDurableDate" : ISODate("2017-11-26T14:08:53Z"),
            "lastHeartbeat" : ISODate("2017-11-26T14:09:00.053Z"),
            "lastHeartbeatRecv" : ISODate("2017-11-26T14:08:59.072Z"),
            "pingMs" : NumberLong(0),
            "syncingTo" : "172.20.6.10:27017",
            "configVersion" : 1
        },
        {
            "_id" : 2,
            "name" : "172.20.6.12:27017",                
            "health" : 1,
            "state" : 2,
            "stateStr" : "SECONDARY",                    #secondary节点
            "uptime" : 109,
            "optime" : {
                "ts" : Timestamp(1511705333, 1),
                "t" : NumberLong(1)
            },
            "optimeDurable" : {
                "ts" : Timestamp(1511705333, 1),
                "t" : NumberLong(1)
            },
            "optimeDate" : ISODate("2017-11-26T14:08:53Z"),
            "optimeDurableDate" : ISODate("2017-11-26T14:08:53Z"),
            "lastHeartbeat" : ISODate("2017-11-26T14:09:00.053Z"),
            "lastHeartbeatRecv" : ISODate("2017-11-26T14:08:59.054Z"),
            "pingMs" : NumberLong(0),
            "syncingTo" : "172.20.6.10:27017",
            "configVersion" : 1
        }
    ],
    "ok" : 1
}

此时replica set集群已结搭建成功

三、副本集测试

3.1 数据复制测试

在主节点创建数据库,并创建集合,插入文档,在secondary查看文档

图片.png

此时已经完成在主节点创建数据,接下来在secondary查看数据是否已经同步过去。

mongodb默认是从主节点读写数据的,副本节点上不允许读,需要设置副本节点可以读。

db.getMongo().setSlaveOk();            #设置副本节点可读

图片.png

此时我们可以测试得到数据,数据已经同步到secondary上,但是无法在secondary上进行数据的增删改操作。

3.2 故障转移测试

目前mongodb-1为主节点,mongdb-2、mongodb-3为副本集节点,此时停掉主节点的mongod服务,进行故障转移测试。

图片.png

宕掉主节点mongodb-1的服务后,我们登录mongodb-2,查看副本集状态:

RS:PRIMARY> rs.status()
{
    "set" : "RS",
    "date" : ISODate("2017-11-26T14:35:03.422Z"),
    "myState" : 1,
    "term" : NumberLong(2),
    "heartbeatIntervalMillis" : NumberLong(2000),
    "optimes" : {
        "lastCommittedOpTime" : {
            "ts" : Timestamp(1511706901, 1),
            "t" : NumberLong(2)
        },
        "appliedOpTime" : {
            "ts" : Timestamp(1511706901, 1),
            "t" : NumberLong(2)
        },
        "durableOpTime" : {
            "ts" : Timestamp(1511706901, 1),
            "t" : NumberLong(2)
        }
    },
    "members" : [
        {
            "_id" : 0,
            "name" : "172.20.6.10:27017",
            "health" : 0,
            "state" : 8,
            "stateStr" : "(not reachable/healthy)",                #mongodb-1已经失去连接
            "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-11-26T14:35:02.502Z"),
            "lastHeartbeatRecv" : ISODate("2017-11-26T14:32:20.434Z"),
            "pingMs" : NumberLong(0),
            "lastHeartbeatMessage" : "Connection refused",
            "configVersion" : -1
        },
        {
            "_id" : 1,
            "name" : "172.20.6.11:27017",
            "health" : 1,
            "state" : 1,
            "stateStr" : "PRIMARY",                        #mongodb-2为新的主节点
            "uptime" : 1842,
            "optime" : {
                "ts" : Timestamp(1511706901, 1),
                "t" : NumberLong(2)
            },
            "optimeDate" : ISODate("2017-11-26T14:35:01Z"),
            "electionTime" : Timestamp(1511706750, 1),
            "electionDate" : ISODate("2017-11-26T14:32:30Z"),
            "configVersion" : 1,
            "self" : true
        },
        {
            "_id" : 2,
            "name" : "172.20.6.12:27017",
            "health" : 1,
            "state" : 2,
            "stateStr" : "SECONDARY",                    #mongodb-3为secondary节点
            "uptime" : 1671,
            "optime" : {
                "ts" : Timestamp(1511706901, 1),
                "t" : NumberLong(2)
            },
            "optimeDurable" : {
                "ts" : Timestamp(1511706901, 1),
                "t" : NumberLong(2)
            },
            "optimeDate" : ISODate("2017-11-26T14:35:01Z"),
            "optimeDurableDate" : ISODate("2017-11-26T14:35:01Z"),
            "lastHeartbeat" : ISODate("2017-11-26T14:35:02.354Z"),
            "lastHeartbeatRecv" : ISODate("2017-11-26T14:35:02.730Z"),
            "pingMs" : NumberLong(0),
            "syncingTo" : "172.20.6.11:27017",
            "configVersion" : 1
        }
    ],
    "ok" : 1
}

查看mongodb-2的日志,发现mongodb-1心跳检查已经失去连接,重新进行了主节点选举

图片.png

此时在新节点mongodb-2进行文档插入操作

图片.png

此时上线mongodb-1,查看集群状态与数据是否正常同步到mongodb-1上。

启动mongodb-1的服务,查看集群状态,此时mongodb-1已结成为新的secondary节点。

RS:PRIMARY> rs.status()
{
    "set" : "RS",
    "date" : ISODate("2017-11-27T02:13:41.683Z"),
    "myState" : 1,
    "term" : NumberLong(2),
    "heartbeatIntervalMillis" : NumberLong(2000),
    "optimes" : {
        "lastCommittedOpTime" : {
            "ts" : Timestamp(1511748812, 1),
            "t" : NumberLong(2)
        },
        "appliedOpTime" : {
            "ts" : Timestamp(1511748812, 1),
            "t" : NumberLong(2)
        },
        "durableOpTime" : {
            "ts" : Timestamp(1511748812, 1),
            "t" : NumberLong(2)
        }
    },
    "members" : [
        {
            "_id" : 0,
            "name" : "172.20.6.10:27017",
            "health" : 1,
            "state" : 2,
            "stateStr" : "SECONDARY",                        #mongodb-1为secondary节点
            "uptime" : 1945,
            "optime" : {
                "ts" : Timestamp(1511748812, 1),
                "t" : NumberLong(2)
            },
            "optimeDurable" : {
                "ts" : Timestamp(1511748812, 1),
                "t" : NumberLong(2)
            },
            "optimeDate" : ISODate("2017-11-27T02:13:32Z"),
            "optimeDurableDate" : ISODate("2017-11-27T02:13:32Z"),
            "lastHeartbeat" : ISODate("2017-11-27T02:13:41.373Z"),
            "lastHeartbeatRecv" : ISODate("2017-11-27T02:13:40.854Z"),
            "pingMs" : NumberLong(0),
            "syncingTo" : "172.20.6.12:27017",
            "configVersion" : 1
        },
        {
            "_id" : 1,
            "name" : "172.20.6.11:27017",
            "health" : 1,
            "state" : 1,
            "stateStr" : "PRIMARY",                        #mongodb-2为主节点
            "uptime" : 43760,
            "optime" : {
                "ts" : Timestamp(1511748812, 1),
                "t" : NumberLong(2)
            },
            "optimeDate" : ISODate("2017-11-27T02:13:32Z"),
            "electionTime" : Timestamp(1511706750, 1),
            "electionDate" : ISODate("2017-11-26T14:32:30Z"),
            "configVersion" : 1,
            "self" : true
        },
        {
            "_id" : 2,
            "name" : "172.20.6.12:27017",                    #mongodb-3为secondary节点
            "health" : 1,
            "state" : 2,
            "stateStr" : "SECONDARY",
            "uptime" : 43589,
            "optime" : {
                "ts" : Timestamp(1511748812, 1),
                "t" : NumberLong(2)
            },
            "optimeDurable" : {
                "ts" : Timestamp(1511748812, 1),
                "t" : NumberLong(2)
            },
            "optimeDate" : ISODate("2017-11-27T02:13:32Z"),
            "optimeDurableDate" : ISODate("2017-11-27T02:13:32Z"),
            "lastHeartbeat" : ISODate("2017-11-27T02:13:41.220Z"),
            "lastHeartbeatRecv" : ISODate("2017-11-27T02:13:41.209Z"),
            "pingMs" : NumberLong(0),
            "syncingTo" : "172.20.6.11:27017",
            "configVersion" : 1
        }
    ],
    "ok" : 1
}

查看mongodb-1数据已经正常同步。

图片.png

四、其他

如果考虑到主服务器的复制压力过大,可以制作仲裁节点,其中的仲裁节点不存储数据,只是负责故障转移的群体投票,这样就少了数据复制的压力。

删除节点:

rs.remove("172.20.6.12:27017")            #删除节点

添加节点

rs.add("172.20.6.12:27017")                #添加节点
rs.addArb("172.20.6.12:27017")               #添加arbiter节点
        {
            "_id" : 2,
            "name" : "172.20.6.12:27017",
            "health" : 1,
            "state" : 7,
            "stateStr" : "ARBITER",                #arbiter节点
            "uptime" : 4,
            "lastHeartbeat" : ISODate("2017-11-27T02:35:01.634Z"),
            "lastHeartbeatRecv" : ISODate("2017-11-27T02:35:00.637Z"),
            "pingMs" : NumberLong(0),
            "syncingTo" : "172.20.6.11:27017",
            "configVersion" : 9
        }