MongoDB的复制和故障转移配置

由于有机会在生产环境中使用MongoDB,所以想要引入复制和故障转移机制,并记录了当时所采取的对策的备忘录。

概述

此次验证是为了确认以下内容而进行。
本次检验的目的是为了确认以下内容。
本次验证的目的是为了确认以下内容。

    1. MongoDB复制和故障切换设置

 

    1. MongoDB故障切换操作确认

 

    1. 故障切换时程序端的处理和设置

 

    连接数的管理

在这篇文章中,将介绍有关MongoDB设置和操作的第1部分和第2部分。关于程序方面的设置和处理,请参考下面的文章。

    • 参考

MongoDBのフェイルオーバー時のNode.jsのプログラム制御と動作確認

环境

为了验证,本次在本地环境进行了测试。

ミドルウェアバージョンPC OSMacVirtual Box5.1.26Vagrant1.9.7仮想OSCentOS 6.7MongoDBv3.4.8

形成

为了设置MongoDB的复制(Replica Set),至少需要3台服务器。为了本次验证,我们将在一台服务器中分配端口,并运行三台数据库服务器。

DB名ポート役割備考DB0150000Primaryレプリケーションの親 DB0250001Secondaryレプリケーションの子 DB0350002Arbiterデータは保持せず、Primaryへの昇格投票のみを行う

每个解释都在下面的文章中有详细说明。

    • 参考

Replication — MongoDB Manual 3.4
俺でもわかるシリーズ: MongoDBのレプリケーション

建构步骤

安装MongoDB

参考《在CentOS6.5上安装MongoDB》一文,进行《添加MongoDB仓库》和《安装》操作。

添加MongoDB仓库。

$ sudo vi /etc/yum.repos.d/mongodb.repo
[mongodb]
name=MongoDB Repository
baseurl=http://downloads-distro.mongodb.org/repo/redhat/os/x86_64/
gpgcheck=0
enabled=1

安装

$ sudo yum install -y mongodb-org

构建MongoDB的副本集

创建数据存储区域

首先,在一台服务器上运行3个MongoDB实例,需要创建MongoDB的数据存储空间。(注意:接下来的步骤将使用root用户执行)

mkdir -p /var/lib/mongodb/db01/
mkdir -p /var/lib/mongodb/db02/
mkdir -p /var/lib/mongodb/db03/

启动3个MongoDB进程。

# db01をport: 50000で起動する
$ mongod --port=50000 --dbpath=/var/lib/mongodb/db01 --logpath=/var/log/mongodb/db01.log --replSet=LocalRep --fork

# db02をport: 50001で起動する
$ mongod --port=50001 --dbpath=/var/lib/mongodb/db02 --logpath=/var/log/mongodb/db02.log --replSet=LocalRep --fork

# db03をport: 50002で起動する
$ mongod --port=50002 --dbpath=/var/lib/mongodb/db03 --logpath=/var/log/mongodb/db03.log --replSet=LocalRep --fork

确认流程

$ ps aux | grep mongod
root      8475  2.4  7.1 1061980 45048 ?       Sl   10:42   0:00 mongod --port=50000 --dbpath=/var/lib/mongodb/db01 --logpath=/var/log/mongodb/db01.log --replSet=LocalRep --fork
root      8502  2.4  5.9 1061980 37504 ?       Sl   10:43   0:00 mongod --port=50001 --dbpath=/var/lib/mongodb/db02 --logpath=/var/log/mongodb/db02.log --replSet=LocalRep --fork
root      8529  3.7  6.2 1061984 39528 ?       Sl   10:43   0:00 mongod --port=50002 --dbpath=/var/lib/mongodb/db03 --logpath=/var/log/mongodb/db03.log --replSet=LocalRep --fork
root      8555  0.0  0.1 103304   884 pts/2    R+   10:43   0:00 grep mongod

进行复制套装的设置

目前只是启动了,副本集的设置尚未完成,因此需要在主数据库上进行副本集的配置。

首先,登录到主数据库(DB01服务器)后,检查状态。

$ mongo --port 50000
> rs.status()
{
    "info" : "run rs.initiate(...) if not yet done for the set",
    "ok" : 0,
    "errmsg" : "no replset config has been received",
    "code" : 94,
    "codeName" : "NotYetInitialized"
}

因为会显示未进行初始化的消息,所以执行命令“rs.initiate()”来进行初始化。

> rs.initiate()  
{
    "info2" : "no configuration specified. Using a default configuration for the set",
    "me" : "localhost.localdomain:50000",
    "ok" : 1
}
LocalRep:OTHER>

在默认设置下,虽然作为副本集的主节点初始化完成。一段时间后使用 rs.status() 命令,可以看到名字为 “localhost.localdomain:50000” 的服务器已经成为”Primary”。

LocalRep:OTHER> rs.status()
{
    "set" : "LocalRep",
    "date" : ISODate("2017-09-08T09:44:50.159Z"),
    "myState" : 1,
    "term" : NumberLong(1),
    "heartbeatIntervalMillis" : NumberLong(2000),
    "optimes" : {
        "lastCommittedOpTime" : {
            "ts" : Timestamp(1504863885, 1),
            "t" : NumberLong(1)
        },
        "appliedOpTime" : {
            "ts" : Timestamp(1504863885, 1),
            "t" : NumberLong(1)
        },
        "durableOpTime" : {
            "ts" : Timestamp(1504863885, 1),
            "t" : NumberLong(1)
        }
    },
    "members" : [
        {
            "_id" : 0,
            "name" : "localhost.localdomain:50000",
            "health" : 1,
            "state" : 1,
            "stateStr" : "PRIMARY",
            "uptime" : 111,
            "optime" : {
                "ts" : Timestamp(1504863885, 1),
                "t" : NumberLong(1)
            },
            "optimeDate" : ISODate("2017-09-08T09:44:45Z"),
            "infoMessage" : "could not find member to sync from",
            "electionTime" : Timestamp(1504863883, 2),
            "electionDate" : ISODate("2017-09-08T09:44:43Z"),
            "configVersion" : 1,
            "self" : true
        }
    ],
    "ok" : 1
}
LocalRep:PRIMARY> 

Secondary和Arbiter的增加

接下来,将DB02作为Secondary添加进去。在PRIMARY服务器上执行rs.add()命令。

LocalRep:PRIMARY> rs.add('localhost.localdomain:50001')
{ "ok" : 1 }

再加一项Arbiter角色,将DB03加入。

LocalRep:PRIMARY> rs.addArb('localhost.localdomain:50002');
{ "ok" : 1 }

以下命令亦可写作如下形式:

LocalRep:PRIMARY> rs.add({host: 'localhost.localdomain:50002', arbiterOnly: true);

执行rs.status()命令可以看到在这种状态下已经成功配置了副本集。

LocalRep:PRIMARY> rs.status()
{
    "set" : "LocalRep",
    "date" : ISODate("2017-09-08T09:49:10.158Z"),
    "myState" : 1,
    "term" : NumberLong(1),
    "heartbeatIntervalMillis" : NumberLong(2000),
    "optimes" : {
        "lastCommittedOpTime" : {
            "ts" : Timestamp(1504864145, 1),
            "t" : NumberLong(1)
        },
        "appliedOpTime" : {
            "ts" : Timestamp(1504864145, 1),
            "t" : NumberLong(1)
        },
        "durableOpTime" : {
            "ts" : Timestamp(1504864145, 1),
            "t" : NumberLong(1)
        }
    },
    "members" : [
        {
            "_id" : 0,
            "name" : "localhost.localdomain:50000",
            "health" : 1,
            "state" : 1,
            "stateStr" : "PRIMARY",
            "uptime" : 371,
            "optime" : {
                "ts" : Timestamp(1504864145, 1),
                "t" : NumberLong(1)
            },
            "optimeDate" : ISODate("2017-09-08T09:49:05Z"),
            "electionTime" : Timestamp(1504863883, 2),
            "electionDate" : ISODate("2017-09-08T09:44:43Z"),
            "configVersion" : 3,
            "self" : true
        },
        {
            "_id" : 1,
            "name" : "localhost.localdomain:50001",
            "health" : 1,
            "state" : 2,
            "stateStr" : "SECONDARY",
            "uptime" : 112,
            "optime" : {
                "ts" : Timestamp(1504864145, 1),
                "t" : NumberLong(1)
            },
            "optimeDurable" : {
                "ts" : Timestamp(1504864145, 1),
                "t" : NumberLong(1)
            },
            "optimeDate" : ISODate("2017-09-08T09:49:05Z"),
            "optimeDurableDate" : ISODate("2017-09-08T09:49:05Z"),
            "lastHeartbeat" : ISODate("2017-09-08T09:49:09.258Z"),
            "lastHeartbeatRecv" : ISODate("2017-09-08T09:49:09.249Z"),
            "pingMs" : NumberLong(0),
            "syncingTo" : "localhost.localdomain:50000",
            "configVersion" : 3
        },
        {
            "_id" : 2,
            "name" : "localhost.localdomain:50002",
            "health" : 1,
            "state" : 7,
            "stateStr" : "ARBITER",
            "uptime" : 74,
            "lastHeartbeat" : ISODate("2017-09-08T09:49:09.258Z"),
            "lastHeartbeatRecv" : ISODate("2017-09-08T09:49:05.277Z"),
            "pingMs" : NumberLong(0),
            "configVersion" : 3
        }
    ],
    "ok" : 1
}

设置故障切换时的优先级

当DB01崩溃并发生故障转移时,DB02将成为Primary。
然而,当DB01恢复并再次成为Primary时,只需在每台服务器上设置优先级即可。
本次设置中,将DB01的优先级设置为100,将DB02的优先级设置为10,
以确保在DB01运行时,它将始终作为Primary运行。

LocalRep:PRIMARY> var conf = rs.conf();
LocalRep:PRIMARY> conf.members[0].priority = 100;
100
LocalRep:PRIMARY> conf.members[1].priority = 10;
10
LocalRep:PRIMARY> rs.reconfig(conf);
{ "ok" : 1 }

members指的是执行rs.status()时的members键的数组。
db01对应index:0,db02对应index:1,因此使用上述指定的方式。

各种服务器的设置已完成。

检查 MongoDB 的状态

登陆各台服务器以验证情况。

    DB01の状態
$ mongo --port 50000
LocalRep:PRIMARY> 
    DB02の状態
$ mongo --port 50001
LocalRep:SECONDARY>  
    DB03の状態
$ mongo --port 50002
LocalRep:ARBITER> 

请确认已根据各自的角色进行了相应的设置。

执行故障转移

我试着关闭DB01。

暫時先確認故障切換是否正確執行。
首先,試著停止DB01的進程。

$ sudo ps aux | grep db01 | awk '{print $2}' | xargs kill -9

确认他已经不在了

$ ps aux | grep mongod
root      8502  1.7  7.2 1561832 45804 ?       Sl   10:43   0:13 mongod --port=50001 --dbpath=/var/lib/mongodb/db02 --logpath=/var/log/mongodb/db02.log --replSet=LocalRep --fork
root      8529  1.6  7.0 1078444 44748 ?       Sl   10:43   0:12 mongod --port=50002 --dbpath=/var/lib/mongodb/db03 --logpath=/var/log/mongodb/db03.log --replSet=LocalRep --fork
root      8714  0.0  0.1 103304   888 pts/1    R+   10:56   0:00 grep mongod

当查看Arbiter的DB03的状态时,我们可以看到DB01显示为连接被拒绝的状态,因此显示为”stateStr”: “(not reachable/healthy)”,同时我们可以看到DB02正作为主服务器运行,显示为”stateStr”: “PRIMARY”。

LocalRep:ARBITER> rs.status()
{
    "set" : "LocalRep",
    "date" : ISODate("2017-09-08T09:56:34.742Z"),
    "myState" : 7,
    "term" : NumberLong(2),
    "heartbeatIntervalMillis" : NumberLong(2000),
    "optimes" : {
        "lastCommittedOpTime" : {
            "ts" : Timestamp(1504864535, 1),
            "t" : NumberLong(1)
        },
        "appliedOpTime" : {
            "ts" : Timestamp(1504864535, 1),
            "t" : NumberLong(1)
        },
        "durableOpTime" : {
            "ts" : Timestamp(0, 0),
            "t" : NumberLong(-1)
        }
    },
    "members" : [
        {
            "_id" : 0,
            "name" : "localhost.localdomain:50000",
            "health" : 0,
            "state" : 8,
            "stateStr" : "(not reachable/healthy)",
            "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-09-08T09:56:34.275Z"),
            "lastHeartbeatRecv" : ISODate("2017-09-08T09:55:43.336Z"),
            "pingMs" : NumberLong(0),
            "lastHeartbeatMessage" : "Connection refused",
            "configVersion" : -1
        },
        {
            "_id" : 1,
            "name" : "localhost.localdomain:50001",
            "health" : 1,
            "state" : 1,
            "stateStr" : "PRIMARY",
            "uptime" : 519,
            "optime" : {
                "ts" : Timestamp(1504864584, 1),
                "t" : NumberLong(2)
            },
            "optimeDurable" : {
                "ts" : Timestamp(1504864584, 1),
                "t" : NumberLong(2)
            },
            "optimeDate" : ISODate("2017-09-08T09:56:24Z"),
            "optimeDurableDate" : ISODate("2017-09-08T09:56:24Z"),
            "lastHeartbeat" : ISODate("2017-09-08T09:56:34.261Z"),
            "lastHeartbeatRecv" : ISODate("2017-09-08T09:56:32.791Z"),
            "pingMs" : NumberLong(0),
            "electionTime" : Timestamp(1504864552, 1),
            "electionDate" : ISODate("2017-09-08T09:55:52Z"),
            "configVersion" : 4
        },
        {
            "_id" : 2,
            "name" : "localhost.localdomain:50002",
            "health" : 1,
            "state" : 7,
            "stateStr" : "ARBITER",
            "uptime" : 806,
            "configVersion" : 4,
            "self" : true
        }
    ],
    "ok" : 1
}

重新启动DB01。

$ mongod --port=50000 --dbpath=/var/lib/mongodb/db01 --logpath=/var/log/mongodb/db01.log --replSet=LocalRep --fork

仅需一种选项即可在中文中将以下句子进行翻译:几秒钟后

    DB01の状態
$ mongo --port 50000
LocalRep:SECONDARY> 
LocalRep:PRIMARY> 

    DB02の状態
$ mongo --port 50001
LocalRep:PRIMARY> 
2017-09-08T11:00:03.401+0100 I NETWORK  [thread1] trying reconnect to 127.0.0.1:50001 (127.0.0.1) failed
2017-09-08T11:00:03.402+0100 I NETWORK  [thread1] reconnect 127.0.0.1:50001 (127.0.0.1) ok
LocalRep:SECONDARY> 

    DB03でステータスを確認
LocalRep:ARBITER> rs.status()
{
    "set" : "LocalRep",
    "date" : ISODate("2017-09-08T10:05:11.992Z"),
    "myState" : 7,
    "term" : NumberLong(3),
    "heartbeatIntervalMillis" : NumberLong(2000),
    "optimes" : {
        "lastCommittedOpTime" : {
            "ts" : Timestamp(1504865102, 1),
            "t" : NumberLong(3)
        },
        "appliedOpTime" : {
            "ts" : Timestamp(1504865102, 1),
            "t" : NumberLong(3)
        },
        "durableOpTime" : {
            "ts" : Timestamp(0, 0),
            "t" : NumberLong(-1)
        }
    },
    "members" : [
        {
            "_id" : 0,
            "name" : "localhost.localdomain:50000",
            "health" : 1,
            "state" : 1,
            "stateStr" : "PRIMARY",
            "uptime" : 317,
            "optime" : {
                "ts" : Timestamp(1504865102, 1),
                "t" : NumberLong(3)
            },
            "optimeDurable" : {
                "ts" : Timestamp(1504865102, 1),
                "t" : NumberLong(3)
            },
            "optimeDate" : ISODate("2017-09-08T10:05:02Z"),
            "optimeDurableDate" : ISODate("2017-09-08T10:05:02Z"),
            "lastHeartbeat" : ISODate("2017-09-08T10:05:09.553Z"),
            "lastHeartbeatRecv" : ISODate("2017-09-08T10:05:11.725Z"),
            "pingMs" : NumberLong(0),
            "electionTime" : Timestamp(1504864801, 1),
            "electionDate" : ISODate("2017-09-08T10:00:01Z"),
            "configVersion" : 4
        },
        {
            "_id" : 1,
            "name" : "localhost.localdomain:50001",
            "health" : 1,
            "state" : 2,
            "stateStr" : "SECONDARY",
            "uptime" : 1036,
            "optime" : {
                "ts" : Timestamp(1504865102, 1),
                "t" : NumberLong(3)
            },
            "optimeDurable" : {
                "ts" : Timestamp(1504865102, 1),
                "t" : NumberLong(3)
            },
            "optimeDate" : ISODate("2017-09-08T10:05:02Z"),
            "optimeDurableDate" : ISODate("2017-09-08T10:05:02Z"),
            "lastHeartbeat" : ISODate("2017-09-08T10:05:09.524Z"),
            "lastHeartbeatRecv" : ISODate("2017-09-08T10:05:10.254Z"),
            "pingMs" : NumberLong(0),
            "syncingTo" : "localhost.localdomain:50000",
            "configVersion" : 4
        },
        {
            "_id" : 2,
            "name" : "localhost.localdomain:50002",
            "health" : 1,
            "state" : 7,
            "stateStr" : "ARBITER",
            "uptime" : 1323,
            "configVersion" : 4,
            "self" : true
        }
    ],
    "ok" : 1
}

确认DB01复活后,数秒后被正确识别为主节点并开始运行,
DB02也正常地以从节点的形式处于稳定运行状态,
成功实现故障转移!

下一步是对程序设置和连接进行调查和验证,更多详细信息,请参阅《MongoDB故障转移时 Node.js 程序控制和操作确认》一文。

总结

使用非常简单的设置,可以在”主服务器”、”备用服务器”和”仲裁者”配置中进行复制和故障转移,这非常令人感激! 不过,在实际运营中,还需要根据服务的性质来确定配置和运营策略,比如”主服务器”、”备用服务器”、”备用服务器”配置或禁止”备用服务器”升级等。如果深入追求,会变得更加复杂。下一步,我想在实际的故障转移中测试负载时间和复制延迟!

广告
将在 10 秒后关闭
bannerAds