0%

MongoDB Replica Set 副本集配置

为了保障数据库的高可用,往往单节点是不够的,生产环境下通常会采用集群部署,既可以为数据提供冗余,同时保证了数据库的高可用性。在 MongoDB 中主要有两种集群,副本集和分片集群,前者提供数据冗余和可用性,后者能够提高系统的吞吐量。本文主要介绍一下有关于副本集的概念和配置。

Replication 副本集描述

副本集成员

副本集相关的内容可以去阅读一下 官方的文档

副本集(Replication )是一组维护相同数据集合的 mongod 实例。副本集包含了若干个数据节点和仲裁节点(Arbiter )。在数据节点中,有且仅有一个成员为主节点(Primary),其他节点为从节点(Secondary)。

The minimum recommended configuration for a replica set is a three member replica set with three data-bearing members: one primary and two secondary members. In some circumstances (such as you have a primary and a secondary but cost constraints prohibit adding another secondary), you may choose to include an arbiter. An arbiter participates in elections but does not hold data (i.e. does not provide data redundancy).

下图是最经典的一个 3 节点架构(有资源可部署 2 个从节点,没有的话可将其中一个转为仲裁节点)。

  • 主节点(Primary)

副本集中唯一的节点,支持读写操作。写入操作一般会记录在 oplog 中。

  • 从节点(Secondary)

数据节点,拥有和主节点一样的数据副本,一般情况下只读。从节点会异步的从主节点的 oplog 中同步数据。从节点可不参与投票(priority: 0)

  • 仲裁节点(Arbiter )

一个仅用于投票的节点,不存储数据副本,在资源紧张的情况下可部署,资源充足下不建议。

副本集架构

  • 官方推荐的生产环境最小配置为 3 个节点,1 个 Primary,2 个 Secondary,资源紧张情况下可将 1 个 Secondary 转为 Arbiter 。
  • 副本集最多可以有 50 名成员,但只有 7 名投票成员。
  • 当多于 2 个节点时。推荐副本集成员为奇数个成员,而不使用仲裁(防止异地数据中心网络异常导致脑裂)。
  • 投票节点数量超过总节点数的半数才能进行投票,官方提供的节点故障容忍个数。
节点数 选举新节点需要个 可容忍故障节点数
3 2 1
4 3 1
5 3 2
6 4 2

自动故障转移(HA)

MongoDB 运行在副本集架构下,当主节点出现故障时,副本集会进行故障转移,余下节点会进行新的主节点选举。选举出的主节点继续提供服务,原故障的主节点修复后,自动重新加入副本集作为从节点。

  • 主节点故障后(electionTimeoutMillis 内无法和其他节点通行,默认 10s),在满足选举要求的情况下,在 leaseTime 内完成节点选举 (默认 30s)。

  • MongoDB r3.2.0 以前选举协议是基于 Bully 算法,从 r3.2.0 开始默认使用基于 Raft 算法的选举策略。

  • 参与选举的节点数量要大于副本集中节点总数的一半。

  • 副本集在选举成功前是无法处理写操作的。如果读请求被配置运行在 从节点 上,则当主节点下线时,副本集可以继续处理这些请求。

节点读操作

  • 主节点可进行读写操作,从节点只能读取数据。
  • 默认情况下,当主节点正常工作时,MongoDB 客户端只能从主节点读取数据。
  • 如果需要从从节点读取数据,可通过 slaveOkReadPreference 进行配置。
    • 在从节点设置 rs.slaveOk() 后,客户端可从从节点读取数据。
    • 更细粒度的数据读取配置,可通过配置 ReadPreference。mongo shell 可执行命令 db.getMongo().setReadPref('secondary') 设置。
模式 描述
primary 只从 primary 节点读数据,这个是默认设置
primaryPreferred 优先从 primary 读取,primary 不可服务,从 secondary 读
secondary 只从 scondary 节点读数据
secondaryPreferred 优先从 secondary 读取,没有 secondary 成员时,从 primary 读取
nearest 根据网络距离就近读取

Docker 环境下搭建 Replica Set

基本环境

  • Ubuntu16.04
  • Docker19.03
  • Docker-Compose1.25.1-rc1
  • MongoDB4.2.8

由于副本集的配置需要多个节点,所以这边采用 Docker 进行快速安装。除了安装的方式外,别的有关 MongoDB 的配置和本地安装是一样的。本地安装可参考 官方的文档

配置流程

  1. 通过 docker 命令启动 3 个 MongoDB 的实例。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
docker run -d \
--name mongo1 \
-p 37017:27017 \
-v /home/mongodb/replset/mongo1/mongod.conf:/data/configdb/mongod.conf \
-v /home/mongodb/replset/mongo1/data:/data/db \
mongo:4.2.8 --config /data/configdb/mongod.conf

docker run -d \
--name mongo2 \
-p 37018:27017 \
-v /home/mongodb/replset/mongo2/mongod.conf:/data/configdb/mongod.conf \
-v /home/mongodb/replset/mongo2/data:/data/db \
mongo:4.2.8 --config /data/configdb/mongod.conf

docker run -d \
--name mongo3 \
-p 37019:27017 \
-v /homer/mongodb/replset/mongo3/mongod.conf:/data/configdb/mongod.conf \
-v /home/mongodb/replset/mongo3/data:/data/db \
mongo:4.2.8 --config /data/configdb/mongod.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
# mongod.conf

# for documentation of all options, see:
# http://docs.mongodb.org/manual/reference/configuration-options/

# Where and how to store data.
storage:
dbPath: /data/db # 配置数据存放的位置
journal:
enabled: true
# engine:
# mmapv1:
# wiredTiger:

# where to write logging data.
systemLog:
destination: file
logAppend: true
path: /var/log/mongodb/mongod.log # 日志文件

# network interfaces
net:
port: 27017
bindIp: 0.0.0.0 # 配置网络

# how the process runs
processManagement:
timeZoneInfo: /usr/share/zoneinfo

#security:
#keyFile:
#operationProfiling:

#replication:
replication:
replSetName: rs # 定义副本集的名称

#sharding:

## Enterprise-Only Options:

#auditLog:

#snmp:
  1. 假设 mongo1 为主节点,进入主节点,执行 mongo,进入 mongo shell。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
rs.initiate({
"_id": "rs",
"members": [
{
"_id": 1,
"host": "172.17.218.94:37017"
},
{
"_id": 2,
"host": "172.17.218.94:37018"
},
{
"_id": 3,
"host": "172.17.218.94:37019"
}
]
})

若返回如内容,证明配置成功。

1
2
3
4
5
6
7
8
9
10
11
{
"ok" : 1,
"$clusterTime" : {
"clusterTime" : Timestamp(1596456560, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
},
"operationTime" : Timestamp(1596456560, 1)
}

等待十几秒钟后,可发现终端已经发生了变化:

1
rs:PRIMARY>

执行 rs.status() 可查看当前复制集的状态:

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
# 忽略部分参数
{
"set": "rs",
"members": [
{
"_id": 1,
"name": "172.17.218.94:37017",
"health": 1,
"state": 1,
"stateStr": "PRIMARY"
},
{
"_id": 2,
"name": "172.17.218.94:37018",
"health": 1,
"state": 2,
"stateStr": "SECONDARY"
},
{
"_id": 3,
"name": "172.17.218.94:37019",
"health": 1,
"state": 2,
"stateStr": "SECONDARY"
}
],
"ok": 1
}

这样就 ok 了。

副本集验证

  1. 验证从节点的数据同步

在主节点插入数据

1
2
3
4
rs:PRIMARY> db.col.insert({"name":"test"})
WriteResult({"nInserted" : 1})
rs:PRIMARY> db.col.find()
{"_id" : ObjectId("5f290145f350f0ccb3e03622"), "name" : "test" }

在从节点查询,会提示非 master 节点,数据不可读,设置 SlaveOk 数据刻可读。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
rs:SECONDARY> db.col.find()
Error: error: {
"operationTime" : Timestamp(1596522843, 1),
"ok" : 0,
"errmsg" : "not master and slaveOk=false",
"code" : 13435,
"codeName" : "NotMasterNoSlaveOk",
"$clusterTime" : {
"clusterTime" : Timestamp(1596522843, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
}
}
rs:SECONDARY> db.setSlaveOk()
rs:SECONDARY> db.col.find()
{"_id" : ObjectId("5f290145f350f0ccb3e03622"), "name" : "test" }
  1. 验证故障转移

手动停止 mongo1,进入 mongo2 容器查看节点状态,此时 mongo2 已经被选举成主节点。

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
rs:PRIMARY> rs.status()
{
"members": [
{
"_id": 1,
"name": "172.17.218.94:37017",
"health": 0,
"state": 8,
"stateStr": "(not reachable/healthy)",
"uptime": 0
},
{
"_id": 2,
"name": "172.17.218.94:37018",
"health": 1,
"state": 1,
"stateStr": "PRIMARY"
},
{
"_id": 3,
"name": "172.17.218.94:37019",
"health": 1,
"state": 2,
"stateStr": "SECONDARY"
}
]
}

更进一步:使用 Compose

显然,直接使用 Docker 部署配置,需要手动打很多的命令,适应 Compose 就能简单不少。

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
version: '3'
services:
mongo-1:
image: mongo:4.2.8
hostname: mongo-1
restart: always
ports:
- 37017:27017
volumes:
- /home/mongodb/replset/mongo1/mongod.conf:/data/configdb/mongod.conf
- /home/mongodb/replset/mongo1/data:/data/db
command:
- --config
- /data/configdb/mongod.conf
mongo-2:
image: mongo:4.2.8
hostname: mongo-2
restart: always
ports:
- 37018:27017
volumes:
- /home/mongodb/replset/mongo2/mongod.conf:/data/configdb/mongod.conf
- /home/mongodb/replset/mongo2/data:/data/db
command:
- --config
- /data/configdb/mongod.conf
mongo-3:
image: mongo:4.2.8
hostname: mongo-3
restart: always
ports:
- 37019:27017
volumes:
- /home/mongodb/replset/mongo3/mongod.conf:/data/configdb/mongod.conf
- /home/mongodb/replset/mongo3/data:/data/db
command:
- --config
- /data/configdb/mongod.conf
mongo-init:
image: mongo:4.2.8
depends_on:
- mongo-1
- mongo-2
- mongo-3
restart: on-failure:5
command:
- mongo
- mongodb://@mongo-1:37017/admin
- --eval
- 'rs.initiate({_id:"rs", members: [{_id:1,host:"172.17.218.94:37017"},{_id:2,host:"172.17.218.94:37018"},{_id:3,host:"172.17.218.94:37019",}]});cfg = rs.conf();cfg.members[0].priority=2;rs.reconfig(cfg)'

在使用 Docker 命令的基础上,我们讲副本集配置的代码也写入了 Compose 配置文件中,并且默认设置了 mongo-1 作为主节点(priority=2)。

那么,执行:

1
[email protected]:# docker-compose up -d

进入 mongo-1 后,输入 rs.status() 就可以看到副本集已经配置完成了,开箱即用。

进一步配置

  1. 数据库用户创建

进入 mongo shell 执行以下命令。

1
2
3
4
5
6
7
8
9
use admin
db.createUser({
user: 'root',
pwd: '123456',
roles:[{
role: 'root',
db: 'admin'
}]
})

修改配置文件,开启强制认证

1
2
security:
authorization: enabled

重启 mongod 服务。docker 内可能无法重启 mongod 服务,直接修改配置文件后重启容器即可。

再次进入 mongo shell,执行 rs.conf() 会提示未授权,此时我们在 admin 数据库下执行 db.auth("root","123456") 即可。

  1. 节点间访问控制

keyFile 的配置,可参考 官方文档

总结

本文详细介绍了 MongoDB 副本集相关的内容及搭建方式。副本集的搭建方式较单点而言能够提高数据库的可用性,为数据提供冗余,生产环境下优先选择。对于一些更复杂的情况,可以在副本集的基础上配置读写分离。如果数据量大,单点无法存下,可进一步配置分片集群。

附录:常用命令

1
2
3
4
5
6
7
8
9
show dbs
show collections
show users
db.stats()
db.getUser("root")
rs.conf()
rs.status()
db.adminCommand({getParameter:"*"})
db._adminCommand({getCmdLineOpts: 1})

参考文献