MongoDB 的高可用

文/Xie 2016-03-24 18:26:00

简介

MongoDB 复制集提供了数据副本,当发生硬件故障或服务中断时,可以从副本恢复数据,并能自动进行故障转移,即实现了 MongoDB 的高可用。

本文讲述了如何部署一个用于开发和测试的复制集环境,其中使用了3个 mongod 实例来建立一个由3个节点组成的复制集。需要事先安装好 MongoDB 数据库,可以参考MongoDB 的安装和使用进行安装。我们所使用的环境是: 美团云主机: 2CPU、2G内存、50G磁盘 OS: CentOS 6.5 DB: MongoDB 3.0.7

部署复制集

下文部署了一个名为 rs0 的复制集。

第一步:为每个复制集实例建立数据目录。下列命令将建立3个目录,分别存储各个实例的数据文件。

mkdir -p /srv/mongodb/rs0-0 /srv/mongodb/rs0-1 /srv/mongodb/rs0-2

第二步:启动 mongo 实例。

启动第一个实例:

mongod --port 27018 --dbpath /srv/mongodb/rs0-0 --replSet rs0

启动第二个实例:

mongod --port 27019 --dbpath /srv/mongodb/rs0-1 --replSet rs0

启动第三个实例:

mongod --port 27020 --dbpath /srv/mongodb/rs0-2 --replSet rs0

其中,--port 选项指定了端口号,每个节点有不同的端口号;--dbpath 选项指定了数据目录,每个节点也有不同的数据目录;--replSet 选项指定了复制集名称,所有节点有共同的复制集名称。

第三部:设置主机名。下列命令中更改了主机名为 mongodb-server。

hostname                    # 查看主机名
hostname mongodb-server     # 修改主机名
hostname                    # 确认主机名为 mongodb-server

编辑文件 /etc/hosts,添加一行:

127.0.0.1       mongodb-server

第四步:通过 mongo 命令连接到某个 mongod 实例,注意需要指定所需连接的端口。

mongo --port 27018

第五步:在 mongo 中执行 rs.initiate() 命令初始化复制集。

> rs.initiate()
{
"info2" : "no configuration explicitly specified -- making one",
"me" : "mongodb-server:27018",
"ok" : 1
}

稍等片刻,复制集初始化完成,回车后提示符从 > 变成 rs0:PRIMARY> ,表示该实例已经成为一个 Primary 节点。

第六步:执行 rs.conf() 命令可以查看复制集配置。

rs0:PRIMARY> rs.conf()
{
    "_id" : "rs0",
    "version" : 1,
    "members" : [
        {
            "_id" : 0,
            "host" : "mongodb-server:27018",
            "arbiterOnly" : false,
            "buildIndexes" : true,
            "hidden" : false,
            "priority" : 1,
            "tags" : {

            },
            "slaveDelay" : 0,
            "votes" : 1
        }
    ],
    "settings" : {
        "chainingAllowed" : true,
        "heartbeatTimeoutSecs" : 10,
        "getLastErrorModes" : {

        },
        "getLastErrorDefaults" : {
        "w" : 1,
        "wtimeout" : 0
        }
    }
}

第七步:执行 rs.add() 命令添加第二个和第三个 mongod 实例为复制集的节点。

rs0:PRIMARY> rs.add("mongodb-server:27019")
{ "ok" : 1 }
rs0:PRIMARY> rs.add("mongodb-server:27020")
{ "ok" : 1 }

第八步:执行 rs.status() 命令来检查复制集的状态。

rs0:PRIMARY> rs.status()
{
    "set" : "rs0",
    "date" : ISODate("2015-12-04T06:12:07.664Z"),
    "myState" : 1,
    "members" : [
    {
        "_id" : 0,
        "name" : "mongodb-server:27018",
        "health" : 1,
        "state" : 1,
        "stateStr" : "PRIMARY",
        "uptime" : 3611,
        "optime" : Timestamp(1449209487, 1),
        "optimeDate" : ISODate("2015-12-04T06:11:27Z"),
        "electionTime" : Timestamp(1449208829, 2),
        "electionDate" : ISODate("2015-12-04T06:00:29Z"),
        "configVersion" : 3,
        "self" : true
    },
    {
        "_id" : 1,
        "name" : "mongodb-server:27019",
        "health" : 1,
        "state" : 2,
        "stateStr" : "SECONDARY",
        "uptime" : 41,
        "optime" : Timestamp(1449209487, 1),
        "optimeDate" : ISODate("2015-12-04T06:11:27Z"),
        "lastHeartbeat" : ISODate("2015-12-04T06:12:07.338Z"),
        "lastHeartbeatRecv" : ISODate("2015-12-04T06:12:06.005Z"),
        "pingMs" : 0,
        "configVersion" : 3
    },
    {
        "_id" : 2,
        "name" : "mongodb-server:27020",
        "health" : 1,
        "state" : 2,
        "stateStr" : "SECONDARY",
        "uptime" : 40,
        "optime" : Timestamp(1449209487, 1),
        "optimeDate" : ISODate("2015-12-04T06:11:27Z"),
        "lastHeartbeat" : ISODate("2015-12-04T06:12:07.339Z"),
        "lastHeartbeatRecv" : ISODate("2015-12-04T06:12:07.339Z"),
        "pingMs" : 0,
        "configVersion" : 3
    }
    ],
    "ok" : 1
}

可见27018节点是 Primary 身份,而27019和27020节点则是 Secondary 身份。至此一个拥有3个实例的 MongoDB 复制集就建立完成了。

测试复制集

测试数据同步

连接一个 Secondary 节点,例如连接27020节点。

[root@mongodb ~]# mongo --port 27020
MongoDB shell version: 3.0.7
connecting to: 127.0.0.1:27020/test
...
rs0:SECONDARY>

Secondary 节点默认不可读。要解决这个问题,只需要简单的一步。

rs0:SECONDARY> rs.slaveOk()
rs0:SECONDARY> show dbs
local  1.078GB
rs0:SECONDARY> show collections
rs0:SECONDARY>

在 Primary 节点插入测试数据。注意以下命令在 Primary 节点的命令行客户端执行。

rs0:PRIMARY> use testdb
switched to db testdb
rs0:PRIMARY> db.tc.insert({ name : "mongo" })
WriteResult({ "nInserted" : 1 })
rs0:PRIMARY> db.tc.insert({ x : 3 })
WriteResult({ "nInserted" : 1 })
rs0:PRIMARY> db.tc.find()
{ "_id" : ObjectId("5661345929d3c35bcbe64803"), "name" : "mongo" }
{ "_id" : ObjectId("5661346b29d3c35bcbe64804"), "x" : 3 }
rs0:PRIMARY>

确认 Primary 节点上新建的数据库 testdb、集合 tc 及其文档已经同步到 Secondary 节点。注意以下命令在 Secondary 节点的命令行客户端执行。

rs0:SECONDARY> rs.slaveOk()
rs0:SECONDARY> show dbs
local   1.078GB
testdb  0.078GB
rs0:SECONDARY> use testdb
switched to db testdb
rs0:SECONDARY> show collections
system.indexes
tc
rs0:SECONDARY> db.tc.find()
{ "_id" : ObjectId("5661345929d3c35bcbe64803"), "name" : "mongo" }
{ "_id" : ObjectId("5661346b29d3c35bcbe64804"), "x" : 3 }
rs0:SECONDARY>

测试故障转移

在 Primary 节点的服务器端按下 “Ctrl + C” 键模拟 Primary 节点故障,然后在 Primary 节点的命令行客户端按下回车键,提示符从 rs0:PRIMARY> 变成 >,表示该实例已经不是 Primary 节点。而在 Secondary 节点客户端按下回车键,则可能会有如下表现:

rs0:SECONDARY>
rs0:PRIMARY>

再次查看复制集的状态,27018节点的 health 值由1变为0,表示节点故障;27020节点的 stateStr 值由 Secondary 变为 Primary,表示该节点已经被提升为 Primary 节点。

rs0:PRIMARY> rs.status()
{
    "set" : "rs0",
    "date" : ISODate("2015-12-04T06:52:09.191Z"),
    "myState" : 1,
    "members" : [
    {
        "_id" : 0,
        "name" : "mongodb-server:27018",
        "health" : 0,
        "state" : 8,
        "stateStr" : "(not reachable/healthy)",
        "uptime" : 0,
        "optime" : Timestamp(0, 0),
        "optimeDate" : ISODate("1970-01-01T00:00:00Z"),
        "lastHeartbeat" : ISODate("2015-12-04T06:52:08.039Z"),
        "lastHeartbeatRecv" : ISODate("2015-12-04T06:45:11.755Z"),
        "pingMs" : 0,
        "lastHeartbeatMessage" : "Failed attempt to connect to mongodb-server:27018; couldn't connect to server mongodb-server:27018 (127.0.0.1), connection attempt failed",
        "configVersion" : -1
    },
    {
        "_id" : 1,
        "name" : "mongodb-server:27019",
        "health" : 1,
        "state" : 2,
        "stateStr" : "SECONDARY",
        "uptime" : 2441,
        "optime" : Timestamp(1449210987, 1),
        "optimeDate" : ISODate("2015-12-04T06:36:27Z"),
        "lastHeartbeat" : ISODate("2015-12-04T06:52:07.857Z"),
        "lastHeartbeatRecv" : ISODate("2015-12-04T06:52:08.567Z"),
        "pingMs" : 0,
        "configVersion" : 3
    },
    {
        "_id" : 2,
        "name" : "mongodb-server:27020",
        "health" : 1,
        "state" : 1,
        "stateStr" : "PRIMARY",
        "uptime" : 5984,
        "optime" : Timestamp(1449210987, 1),
        "optimeDate" : ISODate("2015-12-04T06:36:27Z"),
        "electionTime" : Timestamp(1449211514, 1),
        "electionDate" : ISODate("2015-12-04T06:45:14Z"),
        "configVersion" : 3,
        "self" : true
    }
    ],
    "ok" : 1
}

在第三个节点(此处为27019节点)的服务器端按下 “Ctrl + C” 键模拟节点故障,然后在当前 Primary 节点的命令行客户端按下回车键,表现如下,即只有一个节点存活时,该节点一定是 Secondary 节点,可以通过执行命令 rs.status() 验证这一点。

rs0:PRIMARY>
...
rs0:SECONDARY>

依次启动之前强制关闭的两个节点,先启动27018节点。

mongod --port 27018 --dbpath /srv/mongodb/rs0-0 --replSet rs0

然后启动27019节点。

mongod --port 27019 --dbpath /srv/mongodb/rs0-1 --replSet rs0

最后查看复制集的状态,一切恢复了到了最初状态:27018节点为 Primary 身份,27019和27020节点为 Secondary 身份,它们的 health 值均为1。

rs0:SECONDARY> rs.status()
{
    "set" : "rs0",
    "date" : ISODate("2015-12-04T07:07:01.418Z"),
    "myState" : 2,
    "members" : [
    {
        "_id" : 0,
        "name" : "mongodb-server:27018",
        "health" : 1,
        "state" : 1,
        "stateStr" : "PRIMARY",
        "uptime" : 180,
        "optime" : Timestamp(1449210987, 1),
        "optimeDate" : ISODate("2015-12-04T06:36:27Z"),
        "lastHeartbeat" : ISODate("2015-12-04T07:07:00.574Z"),
        "lastHeartbeatRecv" : ISODate("2015-12-04T07:07:00.903Z"),
        "pingMs" : 0,
        "electionTime" : Timestamp(1449212640, 1),
        "electionDate" : ISODate("2015-12-04T07:04:00Z"),
        "configVersion" : 3
    },
    {
        "_id" : 1,
        "name" : "mongodb-server:27019",
        "health" : 1,
        "state" : 2,
        "stateStr" : "SECONDARY",
        "uptime" : 172,
        "optime" : Timestamp(1449210987, 1),
        "optimeDate" : ISODate("2015-12-04T06:36:27Z"),
        "lastHeartbeat" : ISODate("2015-12-04T07:07:00.700Z"),
        "lastHeartbeatRecv" : ISODate("2015-12-04T07:07:00.762Z"),
        "pingMs" : 0,
        "configVersion" : 3
    },
    {
        "_id" : 2,
        "name" : "mongodb-server:27020",
        "health" : 1,
        "state" : 2,
        "stateStr" : "SECONDARY",
        "uptime" : 6876,
        "optime" : Timestamp(1449210987, 1),
        "optimeDate" : ISODate("2015-12-04T06:36:27Z"),
        "configVersion" : 3,
        "self" : true
    }
    ],
    "ok" : 1
}

总结

本文讲述了在一台云主机上部署 MongoDB 复制集的测试开发环境,如果需要在生产环境中部署,只需要将复制集节点部署在不同的云主机上,并且保证各个节点之间能够进行网络通信。

在 MongoDB 复制集中,节点提升到 Primary 是一个很严格的过程,一旦出现两个 Primary 节点,复制集就废了。当复制集中仅有的两个实例挂掉一个,剩下的一个并不能判断自己的存活状况,因为也有可能是自己跟网络断开了连接造成的,而另外一个实例还活着。所以暂时让自己成为 Secondary 。一旦另外一个实例恢复生存,两个实例就可以互相证明对方存活,然后就会有实例扮演 Primary 身份。

知识共享许可协议
本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。

最新文章 全部