MongoDB副本集搭建

对于互联网应用来说,我们要尽量保证服务的不可间断性,一旦出现故障需要尽快的恢复。MongoDB 的副本集模式主要解决了在其主从模式下故障无法自动转移的情况,自动实现高可用。三个成员副本集提供足够的冗余性,以承受大多数网络分区和其他系统故障,这些集合对于许多分布式读取操作也具有足够的容量。本文介绍如何从三个现有 mongod 实例创建三成员副本集,包括不启用访问控制的和启用访问控制的。

MongoDB副本集搭建

MongoDB Replica Set

三成员副本集部署模式

P-S-S

Primary with Two Secondary Members 是具有三个存储数据的成员的副本集部署模式,它包含:

  • 一个主节点 primary
  • 两个副节点 secondary (每个副节点都可以被选举为主节点)

P-S-S

除 primary 成员外,P-S-S 部署始终提供数据集的两个完整副本。这些副本集提供了额外的容错能力和高可用性。 如果主服务器不可用,则副本集将 secondary 成员选为 primary 并继续正常操作。原本的 primary 在可用时会重新加入副本集。

elect

环境准备

  • 系统系统:CentOS7 64位
Replica Set Member Hostname
rs0-0 127.0.0.1:27017
rs0-1 127.0.0.1:27018
rs0-2 127.0.0.1:27019

部署说明

下面将一个示例说明在单机部署一个 P-S-S 集群的流程。

前提

对于测试和开发环境,您可以在本地系统或虚拟实例中运行 mongod 实例。

在部署副本集之前,必须在将成为副本集一部分的每个系统上安装 MongoDB。

每个成员都必须能够连接到其他每个成员。

副本集名称

此过程中的示例创建一个名为 rs0 的新副本集。

如果您的应用程序连接到多个副本集,则每个副本集应具有不同的名称。某些驱动程序通过副本集名称对副本集连接进行分组。

基于命令行参数的部署

  1. 通过执行类似于下列示例的命令,为每个成员创建必要的数据目录,它们将存储 mongod 实例的数据库文件:

    1
    mkdir -p /srv/mongodb/rs0-0/data /srv/mongodb/rs0-1/data /srv/mongodb/rs0-2/data
  2. 通过下列命令,在终端窗口中启动 mongod 实例:

    启动第一个成员

    1
    nohup mongod --replSet rs0 --port 27017 --bind_ip 0.0.0.0 --dbpath /srv/mongodb/rs0-0/data --oplogSize 128 &

    启动第二个成员

    1
    nohup mongod --replSet rs0 --port 27018 --bind_ip 0.0.0.0 --dbpath /srv/mongodb/rs0-1/data --oplogSize 128 &

    启动第三个成员

    1
    nohup mongod --replSet rs0 --port 27019 --bind_ip 0.0.0.0 --dbpath /srv/mongodb/rs0-2/data --oplogSize 128 &

    通过上面的启动,将使每个实例作为名为 rs0 的副本集的成员,每个副本集都在不同的端口上运行,并使用 --dbpath 设置指定数据目录的路径。如果您已经在使用建议的端口,请选择其他端口。

    这些实例同时绑定到主机的 localhost 和 ip 地址。

    --oplogSize 设置减少了每个 mongod 实例使用的磁盘空间。这是测试和开发部署的理想选择,因为它可以防止计算机过载。有关此配置选项和其他配置选项的更多信息,请参阅 配置文件选项

  3. 通过 mongo shell 连接到您的 mongod 实例之一。您需要通过指定其端口号来指示哪个实例。为了简单明了,直接连接第一个,如以下命令所示:

    1
    mongo --port 27017
  4. 在 mongo shell 中,使用 rs.initiate() 初始化副本集。您可以在 mongo shell 环境中创建副本集配置对象,如以下示例所示:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    rsconf = {
    _id: "rs0",
    members: [
    {
    _id: 0,
    host: "127.0.0.1:27017"
    },
    {
    _id: 1,
    host: "127.0.0.1:27018"
    },
    {
    _id: 2,
    host: "127.0.0.1:27019"
    }
    ]
    }

    根据实际情况替换为系统的主机名和端口,然后将 rsconf 文件传递给 rs.initiate(),如下所示:

    1
    rs.initiate( rsconf )
  5. 通过以下命令来显示当前 副本配置

    1
    rs.conf()

    副本集配置对象类似于以下内容:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    {
    "_id" : "rs0",
    "version" : 1,
    "protocolVersion" : NumberLong(1),
    "members" : [
    {
    "_id" : 0,
    "host" : "<hostname>:27017",
    "arbiterOnly" : false,
    "buildIndexes" : true,
    "hidden" : false,
    "priority" : 1,
    "tags" : {

    },
    "slaveDelay" : NumberLong(0),
    "votes" : 1
    },
    {
    "_id" : 1,
    "host" : "<hostname>:27018",
    "arbiterOnly" : false,
    "buildIndexes" : true,
    "hidden" : false,
    "priority" : 1,
    "tags" : {

    },
    "slaveDelay" : NumberLong(0),
    "votes" : 1
    },
    {
    "_id" : 2,
    "host" : "<hostname>:27019",
    "arbiterOnly" : false,
    "buildIndexes" : true,
    "hidden" : false,
    "priority" : 1,
    "tags" : {

    },
    "slaveDelay" : NumberLong(0),
    "votes" : 1
    }
    ],
    "settings" : {
    "chainingAllowed" : true,
    "heartbeatIntervalMillis" : 2000,
    "heartbeatTimeoutSecs" : 10,
    "electionTimeoutMillis" : 10000,
    "catchUpTimeoutMillis" : -1,
    "getLastErrorModes" : {

    },
    "getLastErrorDefaults" : {
    "w" : 1,
    "wtimeout" : 0
    },
    "replicaSetId" : ObjectId("598f630adc9053c6ee6d5f38")
    }
    }
  6. 随时使用 rs.status() 检查副本集的状态。

    从运行该方法的成员的角度返回副本集状态。此方法为 replSetGetStatus 命令提供了包装。

    此输出使用从副本集其他成员发送的心跳数据包派生的数据反映副本集的当前状态。

基于配置文件的部署

https://docs.mongodb.com/manual/reference/configuration-options/

  1. 创建日志文件目录

    1
    mkdir -p /srv/mongodb/rs0-0/log /srv/mongodb/rs0-1/log /srv/mongodb/rs0-2/log
  2. 创建配置文件目录

    1
    mkdir -p /srv/mongodb/conf
  3. 创建每个成员的配置文件

    第一个成员的配置文件:

    1
    vim /srv/mongodb/conf/rs0-0.conf
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    systemLog:
    destination: file
    path: /srv/mongodb/rs0-0/log/mongod.log
    logAppend: true
    storage:
    dbPath: /srv/mongodb/rs0-0/data
    journal:
    enabled: true
    processManagement:
    fork: true
    pidFilePath: /srv/mongodb/rs0-0/mongod.pid
    replication:
    replSetName: rs0
    oplogSizeMB: 128
    net:
    bindIp: 127.0.0.1
    port: 27017
    # security:
    # keyFile: /srv/mongodb/mongodb.key
    # authorization: disabled

    第二个成员的配置文件:

    1
    vim /srv/mongodb/conf/rs0-1.conf
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    systemLog:
    destination: file
    path: /srv/mongodb/rs0-1/log/mongod.log
    logAppend: true
    storage:
    dbPath: /srv/mongodb/rs0-1/data
    journal:
    enabled: true
    processManagement:
    fork: true
    pidFilePath: /srv/mongodb/rs0-1/mongod.pid
    replication:
    replSetName: rs0
    oplogSizeMB: 128
    net:
    bindIp: 127.0.0.1
    port: 27018
    # security:
    # keyFile: /srv/mongodb/mongodb.key
    # authorization: disabled

    第三个成员的配置文件:

    1
    vim /srv/mongodb/conf/rs0-2.conf
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    systemLog:
    destination: file
    path: /srv/mongodb/rs0-2/log/mongod.log
    logAppend: true
    storage:
    dbPath: /srv/mongodb/rs0-2/data
    journal:
    enabled: true
    processManagement:
    fork: true
    pidFilePath: /srv/mongodb/rs0-2/mongod.pid
    replication:
    replSetName: rs0
    oplogSizeMB: 128
    net:
    bindIp: 127.0.0.1
    port: 27019
    # security:
    # keyFile: /srv/mongodb/mongodb.key
    # authorization: disabled
  4. 启动副本集成员

    1
    2
    3
    mongod -f /srv/mongodb/conf/rs0-0.conf
    mongod -f /srv/mongodb/conf/rs0-1.conf
    mongod -f /srv/mongodb/conf/rs0-2.conf
  5. 查看 mongod 进程

    1
    ps aux | grep mongod

    剩余初始副本集的步骤与上面命令行方式部署的操作相同。

启用身份验证

默认情况下,MongoDB 不会进行身份验证,也没有账号,只要能连接上服务就可以对数据库进行各种操作,出于安全角度考虑,我们应当为其添加认证访问。

通过 --auth 选项可以开启身份认证,此外,副本集设置身份验证与单机不同,需要增加一个 keyFile 以便副本集成员相互认证。

创建一个用户

首先创建一个用于身份验证的用户,首先使用 mongo shell 连接到集群的 primary 节点,如:

1
mongo --port 27017

切换到 admin 数据库:

1
use admin

在 admin 下创建用户 root:

1
2
3
4
5
6
7
8
9
db.createUser({
user: "root",
pwd: "toor",
roles: [
{role: "userAdminAnyDatabase", db:"admin"},
{role: "readWriteAnyDatabase", db: "admin"},
{role:"clusterAdmin", db:"admin"}
]
})

创建 KeyFile

这个文件需要满足下面几点要求:

  • 文本长度需要在 6 和 1024 之间
  • 认证时候不考虑文件中空白字符
  • 连接到副本集的成员和 mongos 进程的 keyfile 文件内容必须一样
  • 必须是 base64 编码,但是不能有等号
  • 文件权限必须是 X00,也就是说,不能分配任何权限给 group 成员和 other 成员

我们可以在 Linux 上直接使用 openssl 创建一个这样的文件,然后上传至其他副本集成员服务器:

1
2
openssl rand -base64 512 > mongodb.key
chmod 400 mongodb.key

修改配置文件

如果你是通过配置文件形式部署的副本集,那么需要修改对应成员的配置:

(假设 Key File 文件在 /srv/mongodb 目录下)

1
2
keyFile: /srv/mongodb/mongodb.key
authorization: enabled

如果你是直接通过命令行参数形式启动的副本集,那么需要使用 --auth 选项并指定 --keyFile,如:

1
nohup mongod --replSet rs0 --port 27017 --bind_ip 0.0.0.0 --dbpath /srv/mongodb/rs0-0 --auth --keyFile=/srv/mongodb/mongodb.key --oplogSize 128 &

测试密码认证

关闭三个 mongod 服务实例:

1
killall mongod

添加 --auth--keyFile 选项重新启动 mongod:

1
2
3
4
5
6
# 启动第一个成员
nohup mongod --replSet rs0 --port 27017 --bind_ip 0.0.0.0 --dbpath /srv/mongodb/rs0-0 --auth --keyFile=/srv/mongodb/mongodb.key --oplogSize 128 &
# 启动第二个成员
nohup mongod --replSet rs0 --port 27018 --bind_ip 0.0.0.0 --dbpath /srv/mongodb/rs0-1 --auth --keyFile=/srv/mongodb/mongodb.key --oplogSize 128 &
# 启动第三个成员
nohup mongod --replSet rs0 --port 27019 --bind_ip 0.0.0.0 --dbpath /srv/mongodb/rs0-2 --auth --keyFile=/srv/mongodb/mongodb.key --oplogSize 128 &

重启 mongodb ,第一次登陆时,直接查询会报权限错误,使用 db.auth() 认证后就可以成功读取数据库了。

1
2
3
4
5
6
7
8
9
10
rs0:PRIMARY> use admin
switched to db admin
rs0:PRIMARY> db.auth('root', 'toor')
1
rs0:PRIMARY> show dbs
admin 0.000GB
config 0.000GB
local 0.000GB
test 0.000GB
rs0:PRIMARY>

问题

初始化失败

在初始化一个 MongoDB 副本集的过程中,出现了初始化失败,报 "No host described in new configuration 1 for replica set mongotest maps to this node"。具体报错信息如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
> rsconf = {
... _id: "rs0",
... members: [
... {
... _id: 0,
... host: "0.0.0.0:27017"
... },
... {
... _id: 1,
... host: "0.0.0.0:27018"
... },
... {
... _id: 2,
... host: "0.0.0.0:27019"
... }
... ]
... }
> rs.initiate( rsconf )
{
"operationTime" : Timestamp(0, 0),
"ok" : 0,
"errmsg" : "No host described in new configuration 1 for replica set rs0 maps to this node",
"code" : 93,
"codeName" : "InvalidReplicaSetConfig",
"$clusterTime" : {
"clusterTime" : Timestamp(0, 0),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
}
}

解决方法,将 rsconf 中的 IP 替换为真正的 IP,如:127.0.0.1。

开启认证后 SECONDARY 读取失败

"errmsg" : "not master and slaveOk=false" 的解决办法参考:

https://stackoverflow.com/questions/8990158/mongodb-replicates-and-error-err-not-master-and-slaveok-false-code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
rs0:SECONDARY> use admin
switched to db admin
rs0:SECONDARY> db.auth('root', 'toor')
1
rs0:SECONDARY> show dbs
2020-03-30T12:03:51.218+0800 E QUERY [js] uncaught exception: Error: listDatabases failed:{
"operationTime" : Timestamp(1585541025, 1),
"ok" : 0,
"errmsg" : "not master and slaveOk=false",
"code" : 13435,
"codeName" : "NotMasterNoSlaveOk",
"$clusterTime" : {
"clusterTime" : Timestamp(1585541025, 1),
"signature" : {
"hash" : BinData(0,"DHLkVZz2fe4aeAjIxEmkHu9FhBE="),
"keyId" : NumberLong("6809843876723949571")
}
}
} :
_getErrorWithCode@src/mongo/shell/utils.js:25:13
Mongo.prototype.getDBs/<@src/mongo/shell/mongo.js:135:19
Mongo.prototype.getDBs@src/mongo/shell/mongo.js:87:12
shellHelper.show@src/mongo/shell/utils.js:906:13
shellHelper@src/mongo/shell/utils.js:790:15
@(shellhelp2):1:1
rs0:SECONDARY> rs.slaveOk()
rs0:SECONDARY> show dbs
admin 0.000GB
config 0.000GB
local 0.000GB
rs0:SECONDARY>

References

https://docs.mongodb.com/manual/reference/configuration-options/

https://www.ibm.com/developerworks/cn/opensource/os-mongodb-replication/index.html

https://docs.mongodb.com/manual/core/retryable-writes/#retryable-writes

Configuration File Settings and Command-Line Options Mapping