基本架构
MongoDB 复制集基本构成是 1 主 2 从的结构,自带互相监控投票机制(通过 Raft 实现,mysql MGR 用的是 Paxos 的变种)。
如果主库宕机了,复制集内部会进行投票选举,选择一个新的主库替代原有主库对外提供服务。同时复制集会自动通知客户端程序,主库已经发生切换了。应用就会连接到新的主库。
一个三成员的复制集的基本架构有如下两种类型:
- 三个有数据;
- 两个有数据,一个仅参与仲裁,这个参与仲裁的节点被称为 arbiter 节点;
下面对这两种类型的架构做一下简单的说明。
三个数据节点
一个主库,两个从库,主库宕机时,这两个从库都可以被选为主库。
当主库宕机后,两个从库都会进行竞选,其中一个变为主库,当原主库恢复后,作为从库加入当前的复制集群即可。
存在 arbiter 节点
一个主库,一个从库,一个 aribiter 节点,如果主库挂了,该从库就会被选举成为主库, 在选举中,aribiter 节点仅参与仲裁投票,不能成为主库。
由于 arbiter 节点没有复制数据,因此这个架构中仅提供一个完整的数据副本。arbiter 节点只需要更少的资源,代价是更有限的冗余和容错。
当主库宕机时,将会选择从库成为主,主库修复后,将其加入到现有的复制集群中即可。
环境规划
所以对于 MongoDB 复制集架构的部署我们需要三个以上的节点(实验环境可使用单机多实例),我这里就使用单机多实例的方式演示了。
我这里实验环境大致如下:
- 系统:CentOS 7.8;
- 内存大小:2G;
- 规划实例端口:28017、28018、28019、28020;
做如下操作之前请按上一篇文章将 MongoDB 二进制包解压到指定路径并配置好环境变量。
复制集应用
多实例配置
1、切换到 mongod
用户:
$ su - mongod
2、为每个实例准备一套目录:
$ mkdir -p /mongodb/28017/{conf,data,log}
$ mkdir -p /mongodb/28018/{conf,data,log}
$ mkdir -p /mongodb/28019/{conf,data,log}
$ mkdir -p /mongodb/28020/{conf,data,log}
3、添加配置文件:
$ cat > /mongodb/28017/conf/mongod.conf <<EOF
systemLog:
destination: file
path: /mongodb/28017/log/mongodb.log
logAppend: true
storage:
journal:
enabled: true
dbPath: /mongodb/28017/data
directoryPerDB: true
# 指定使用 wiredTiger 引擎,类似 MySQL 的 InnoDB,支持事务
wiredTiger:
engineConfig:
# 缓冲区大小,支持数据和索引的缓冲,类似 MySQL 的 InnoDB Buffer Pool
cacheSizeGB: 1
directoryForIndexes: true
collectionConfig:
blockCompressor: zlib
indexConfig:
prefixCompression: true
processManagement:
fork: true
net:
bindIp: 10.0.1.51,127.0.0.1
port: 28017
# 复制集配置
replication:
# 最多存储日志的大小,MongoDB 的日志直接存储在集合中,即该大小也是日志集合的最大大小,如果该项不设置,则默认为磁盘总空间的 5% 大小
oplogSizeMB: 2048
# 复制集名称
replSetName: my_repl
EOF
# 拷贝
$ \cp /mongodb/28017/conf/mongod.conf /mongodb/28018/conf/
$ \cp /mongodb/28017/conf/mongod.conf /mongodb/28019/conf/
$ \cp /mongodb/28017/conf/mongod.conf /mongodb/28020/conf/
# 修改端口
$ sed 's#28017#28018#g' /mongodb/28018/conf/mongod.conf -i
$ sed 's#28017#28019#g' /mongodb/28019/conf/mongod.conf -i
$ sed 's#28017#28020#g' /mongodb/28020/conf/mongod.conf -i
4、配置每个实例被 systemd 管理:
$ cat > /etc/systemd/system/mongod28017.service <<EOF
[Unit]
Description=mongodb
After=network.target remote-fs.target nss-lookup.target
[Service]
User=mongod
Type=forking
ExecStart=/mongodb/bin/mongod --config /mongodb/28017/conf/mongod.conf
ExecReload=/bin/kill -s HUP $MAINPID
ExecStop=/mongodb/bin/mongod --config /mongodb/28017/conf/mongod.conf --shutdown
PrivateTmp=true
[Install]
WantedBy=multi-user.target
EOF
# 拷贝
$ \cp /etc/systemd/system/mongod28017.service /etc/systemd/system/mongod28018.service
$ \cp /etc/systemd/system/mongod28017.service /etc/systemd/system/mongod28019.service
$ \cp /etc/systemd/system/mongod28017.service /etc/systemd/system/mongod28020.service
# 替换使用的配置文件
$ sed 's#28017#28018#g' /etc/systemd/system/mongod28018.service -i
$ sed 's#28017#28019#g' /etc/systemd/system/mongod28019.service -i
$ sed 's#28017#28020#g' /etc/systemd/system/mongod28020.service -i
5、启动多个实例并检查端口是否已占用:
# 启动
$ systemctl start mongod28017.service mongod28018.service mongod28019.service mongod28020.service
# 检查端口
$ ss -tanl | grep 280
LISTEN 0 128 127.0.0.1:28017 *:*
LISTEN 0 128 10.0.1.51:28017 *:*
LISTEN 0 128 127.0.0.1:28018 *:*
LISTEN 0 128 10.0.1.51:28018 *:*
LISTEN 0 128 127.0.0.1:28019 *:*
LISTEN 0 128 10.0.1.51:28019 *:*
LISTEN 0 128 127.0.0.1:28020 *:*
LISTEN 0 128 10.0.1.51:28020 *:*
至此,MongoDB 的 4 个实例就已经正常启动了。
普通复制集
1、以配置 1 主 2 从为例,随便连入一个实例,我这里以连入 28017 为例:
$ mongo --port 28017 admin
MongoDB shell version v3.6.12
connecting to: mongodb://127.0.0.1:28017/admin?gssapiServiceName=mongodb
...
> config = {_id: 'my_repl', members: [
{_id: 0, host: '10.0.1.51:28017'},
{_id: 1, host: '10.0.1.51:28018'},
{_id: 2, host: '10.0.1.51:28019'}]
}
> rs.initiate(config)
{
"ok" : 1,
"operationTime" : Timestamp(1592398822, 1),
"$clusterTime" : {
"clusterTime" : Timestamp(1592398822, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
}
}
my_repl:SECONDARY>
# 回车,当看到命令提示符显示 RPIMARY 则说明当前实例选为了主库
my_repl:PRIMARY>
# 可查看复制集状态
my_repl:PRIMARY> rs.status()
2、再查看另外两个节点,会发现它们是从库了:
$ mongo --port 28018 admin
MongoDB shell version v3.6.12
connecting to: mongodb://127.0.0.1:28018/admin?gssapiServiceName=mongodb
...
my_repl:SECONDARY>
$ mongo --port 28019 admin
MongoDB shell version v3.6.12
connecting to: mongodb://127.0.0.1:28019/admin?gssapiServiceName=mongodb
...
my_repl:SECONDARY>
3、测试关闭主库,即 28017 实例,检查新主库:
# 关闭主库
$ systemctl stop mongod28017.service
# 检查 28018 实例
$ mongo --port 28018 admin
MongoDB shell version v3.6.12
connecting to: mongodb://127.0.0.1:28018/admin?gssapiServiceName=mongodb
...
# 可以看到 28018 实例已经切换为主库了
my_repl:PRIMARY>
# 检查 28019 实例
$ mongo --port 28019 admin
MongoDB shell version v3.6.12
connecting to: mongodb://127.0.0.1:28019/admin?gssapiServiceName=mongodb
...
# 可以看到 28019 实例依旧是从库
my_repl:SECONDARY>
4、启动 28017 实例,然后检查该实例状态:
# 启动
$ systemctl start mongod28017.service
# 可以看到 28017 实例成为了从库,使用 rs.status() 查看更明显
$ mongo --port 28017 admin
MongoDB shell version v3.6.12
connecting to: mongodb://127.0.0.1:28017/admin?gssapiServiceName=mongodb
...
my_repl:SECONDARY>
存在 arbiter 节点的复制集
以一主一从一 arbiter 节点的复制集为例,配置很简单,仅需要在初始化复制集时设定要作为 arbiter 的节点的 arbiterOnly
属性为 true
,如下:
# 以设定 28019 实例为 arbiter 为例
$ mongo --port 28017 admin
MongoDB shell version v3.6.12
connecting to: mongodb://127.0.0.1:28017/admin?gssapiServiceName=mongodb
...
> config = {_id: 'my_repl', members: [
{_id: 0, host: '10.0.1.51:28017'},
{_id: 1, host: '10.0.1.51:28018'},
{_id: 2, host: '10.0.1.51:28019',"arbiterOnly":true}]
}
rs.initiate(config)
除了在初始化复制集时指定 arbiter 节点,还可通过下面的复制集管理操作来动态添加新的 arbiter 节点到复制集中,继续往下看吧~~~
复制集管理
常用的复制集管理操作有如下:
$ mongo --port 28018 admin
MongoDB shell version v3.6.12
connecting to: mongodb://127.0.0.1:28017/admin?gssapiServiceName=mongodb
...
# 查看复制集状态
my_repl:PRIMARY> rs.status()
# 查看当前节点是否是主节点
my_repl:PRIMARY> rs.isMaster()
# 查看当前复制集的配置信息
my_repl:PRIMARY> rs.conf()
# 删除节点,以删除 28017 节点为例
my_repl:PRIMARY> rs.remove("10.0.1.51:28017")
# 重新将 28017 节点添加为 arbiter 节点
my_repl:PRIMARY> rs.addArb("10.0.1.51:28017")
# 也可新添加一个数据节点,以添加 28020 节点为新的数据节点为例
my_repl:PRIMARY> rs.add("10.0.1.51:28020")
# 将当前主库实例降级,在指定时间内这个实例不会把自己选为主库,不建议人为操作
my_repl:PRIMARY> rs.stepDown(30)
# 设置当前从节点可读
my_repl:SECONDARY> rs.slaveOk()
# 锁定当前实例在指定时间内不会被选举从库,单位为秒
my_repl:SECONDARY> rs.freeze(300)
# 查看从库的同步状态
my_repl:SECONDARY> rs.printSlaveReplicationInfo()
# 默认复制集的从节点是不提供读写操作,可通过执行 rs.slaveOk() 以设定从节点可读,该种方式是临时生效
# 还可将该命令写入启动文件让其永久生效 echo "rs.slaveOk()" > ~/.mongorc.js
my_repl:SECONDARY> rs.slaveOk()
特殊复制集
除了普通的数据节点外,MongoDB 复制集中可存在如下三类特殊的节点:
- arbiter 节点:主要负责选主过程中的投票,但是不存储任何数据,也不提供任何服务;
- hidden 节点:隐藏节点,不参与选主,也不对外提供服务;
- delay 节点:延时节点,数据落后于主库一段时间,因为数据是延时的,也不应该提供服务或参与选主,所以通常会配合 hidden(隐藏)一起使用;
下面就以配置 28020 节点为延时节点为例,即需要同时配置它为隐藏节点,随便选择复制集中的一个实例登入,执行如下操作:
$ mongo --port 28018 admin
MongoDB shell version v3.6.12
connecting to: mongodb://127.0.0.1:28017/admin?gssapiServiceName=mongodb
...
# 首先通过 rs.conf() 查看 28020 节点在 members 列表的索引(索引起始值为 0),内容太长我就省略啦
my_repl:PRIMARY> rs.conf()
# 这里我的环境中 28020 节点处于列表的第四个节点,所以它对应的索引值为 3
my_repl:PRIMARY> cfg=rs.conf()
# 设置优先级为 0
my_repl:PRIMARY> cfg.members[3].priority=0
# 设置为隐藏节点
my_repl:PRIMARY> cfg.members[3].hidden=true
# 设置延时时长为 120 秒
my_repl:PRIMARY> cfg.members[3].slaveDelay=120
# 应用配置
my_repl:PRIMARY> rs.reconfig(cfg)
做完上述操作,28020 节点就成为了一个延时节点。
查看从库的同步状态:
$ mongo --port 28017 admin
MongoDB shell version v3.6.12
connecting to: mongodb://127.0.0.1:28017/admin?gssapiServiceName=mongodb
...
my_repl:PRIMARY> rs.printSlaveReplicationInfo()
source: 10.0.1.51:28019
syncedTo: Wed Jun 17 2020 22:12:23 GMT+0800 (CST)
4 secs (0 hrs) behind the primary
source: 10.0.1.51:28017
no replication info, yet. State: (not reachable/healthy)
source: 10.0.1.51:28020
syncedTo: Wed Jun 17 2020 22:10:23 GMT+0800 (CST)
124 secs (0.03 hrs) behind the primary
可以看到,28020 节点当前落后于主库 124 秒。
评论区