MongoDB 副本集(Replica Set)

By | 2021年12月31日

1 简介

官方3.4手册里明确说明,主从复制将被副本集全部替代,后续版本应该不在支持主从复制,因此建议不要主从复制了,直接用副本集。
IMPORTANT
Replica sets replace master-slave replication for most use cases. If possible, use replica sets rather than master-slave replication for all new production deployments. This documentation remains to support legacy deployments and for archival purposes only.

MongoDB的replica set架构是通过一个日志来存储写操作的,这个日志就叫做 oplog 。oplog.rs 是一个固定长度的 Capped Collection,它存在于local数据库中,用于记录replicaSets操作日志。在默认情况下,对于64位的MongoDB,oplog是比较大的,可以达到5%的磁盘空间,oplog的大小是可以通过mongod的
-oplogSize (单位:MB)来改变oplog的日志大小。
–smallfiles 声明使用更小的默认文件。

经测试,mongo3 shell无法连接到mongo4。

在docker中部署副本集需要考虑容器的ip问题,可以使用命令 “docker exec 6bc cat /etc/hosts” 得到容器IP,
再用 “rs.initiate({_id: “rs”, members: [{_id: 0, host: “x.x.x.x:27017″}] })” 初始化副本集。
如果使用的是 driver: overlay 网路,那么可以不去获取容器IP,直接用 “falmongo” 名字就好了。

2 Docker 镜像副本集

部署副本集

#1. 启动三个 mongo 服务,端口分别是 27011、27012、27013
docker run -it --rm -p 27011:27011 -v /home/mongo/rsdata27011:/data/db mongo:4.4  bash -c 'mongod --dbpath=/data/db --bind_ip_all --port 27011 --replSet rs0'
docker run -it --rm -p 27012:27012 -v /home/mongo/rsdata27012:/data/db mongo:4.4  bash -c 'mongod --dbpath=/data/db --bind_ip_all --port 27012 --replSet rs0'
docker run -it --rm -p 27013:27013 -v /home/mongo/rsdata27013:/data/db mongo:4.4  bash -c 'mongod --dbpath=/data/db --bind_ip_all --port 27013 --replSet rs0'

#2. 用客户端连任意一台 mongo 服务,准备创建副本集
docker run -it --rm mongo:4.4 bash -c 'mongo --host 192.168.3.222 --port 27011'

#3. 连上后,创建副本集
rs.initiate({_id: 'rs0', members: [{_id: 0, host: '192.168.3.222:27011'},{_id: 1, host: '192.168.3.222:27012'},{_id: 2, host: '192.168.3.222:27013'}] })

#4. 查看副本集状态,确认 master 主节点是哪一台(找 members 下的 stateStr 值,PRIMARY 的为主节点),我这里 27011 这台是主节点 
rs.status()

#5. 验证副本集
#(1)主节点 Primary 插入数据
docker run -it --rm mongo:4.4 bash -c 'mongo --host 192.168.3.222 --port 27011'
db.test.insert({ a:1 })
#(2)从节点 Secondary 读取数据,默认是不允许读,因此会报错:not master and slaveOk=false
docker run -it --rm mongo:4.4 bash -c 'mongo --host 192.168.3.222 --port 27012'
db.test.find({})

#6. 关闭 27011 这台主节点,27012 上查看副本集状态,发现 27012 变成 Primary 了,并且可以查询数据
rs.status()
db.test.find({})

#7. 再次恢复 27011 这台,发现变成 SECONDARY 了。

测试效果

三个mongo服务 kill掉 primary,选举产生了新primary,policy仍工作正常;再kill掉primary剩一台,此时无法选举产生新primary,policy 也不工作;快速重新启动两台kill掉的,policy恢复工作;policy mongo Timeout后再重启两台kill掉的,即使选举出primary policy也不工作了,需重启policy才恢复。

默认超时时间是30秒,可以改成10分钟,只需添加一个连接字符串选项 serverSelectionTimeoutMS=600000

注意点

开启副本集后,若启动2台mongo进程都一直报 Heartbeat 错误,因为没有足够的裁判员来选举出 PRIMARY 节点。
启动第一台时一直报错:Heartbeat failed after max retries,客户端连上看到是 SECONDARY 节点。
启动第二台时一直报错:Heartbeat failed after max retries,客户端连上看到是 SECONDARY 节点。
启动第三台后前面两台都不报错了,客户端连上看到的也是 SECONDARY 节点,此时再去看第二台,发现已变成 PRIMARY 节点。

启用副本集认证

  1. 登陆主节点,创建账号
use admin
db.dropUser('faasec')
db.createUser({user: 'faa', pwd: 'admin.0512#', roles: [{role: 'clusterAdmin', db: 'admin'},{role: "userAdminAnyDatabase", db: "admin"},"readWriteAnyDatabase"]})

#有时候客户那要求用最小权限,如:
# admin用于认证、local用于读取oplog,faapolicy是业务数据库
db.createUser({user: 'faa', pwd: 'admin.0512#', roles: [{role: 'readWrite', db: 'admin'},{role: 'readWrite', db: 'faapolicy'},{role: "readWrite", db: "local"}]})

2. 登陆主节点,查看创建的账号,即使主节点发生了IP漂移也没关系

use admin
db.system.users.find()

3. 创建 keyfile

openssl rand -base64 666 > /home/mongo/keyfile

4. 使用 keyfile 启用副本集认证(请先关闭三个mongo服务)

docker run -it --rm -p 27011:27011 -v /home/mongo/rsdata27011:/data/db -v /home/mongo/keyfile:/home/mongo/keyfile mongo:4.4  bash -c 'chown 600 /home/mongo/keyfile && mongod --dbpath=/data/db --keyFile /home/mongo/keyfile --bind_ip_all --port 27011 --replSet rs0'

docker run -it --rm -p 27012:27012 -v /home/mongo/rsdata27012:/data/db -v /home/mongo/keyfile:/home/mongo/keyfile mongo:4.4  bash -c 'chown 600 /home/mongo/keyfile && mongod --dbpath=/data/db --keyFile /home/mongo/keyfile  --bind_ip_all --port 27012 --replSet rs0'

docker run -it --rm -p 27013:27013 -v /home/mongo/rsdata27013:/data/db -v /home/mongo/keyfile:/home/mongo/keyfile mongo:4.4  bash -c 'chown 600 /home/mongo/keyfile && mongod --dbpath=/data/db --keyFile /home/mongo/keyfile  --bind_ip_all --port 27013 --replSet rs0'

5. 登陆主节点,查询数据直接报错:command find requires authentication

docker run -it --rm mongo:4.4 bash -c 'mongo --host 192.168.3.222 --port 27011'
use admin
db.system.users.find()

6. 登陆主节点,验证后查询数据不报错了

use admin
db.auth('faasec','admin.0512#')
db.system.users.find()

写成脚本

mongo-entrypoint.sh:

#!/bin/bash

_term() {
  # 测试请执行 docker stop <containerId>,会发送 SIGTERM 信号
  echo "> mongod --shutdown --dbpath /data/db"
  mongod --shutdown --dbpath /data/db
}
trap _term HUP INT QUIT TERM

set -e

set -- $@ --dbpath /data/db --bind_ip 0.0.0.0 --port 27017 --replSet rs --oplogSize 128

echo ">>> starting mongo..."
exec "$@" &
child1=$!

# 这里使用 wait 让 sleep 后台运行,以便在 sleep 时可以接收到 SIGTERM 信号。sleep 前台运行,调用 docker stop 是接收不到的。
# 运行速度慢的机器需要更久的 sleep
sleep 30s &
child2=$!
wait $child2

echo ">>> set docker ip to replica set ..."

#ipAddr=$(cat /etc/hosts | grep `hostname` | awk '{print $1}')
#echo '>>> hostname:' $ipAddr

echo ">>> initiate replica set..."
mongo ${USR_LOCAL_BIN}/script.js
echo ">>> initialization is finished."

wait $child1

script.js:

rs.initiate({_id: "rs", members: [{_id: 0, host: "falmongo:27017"}] });

数据是如何复制的?

当一个修改操作,无论是插入、更新或删除,到达主节点时,它对数据的操作将被记录下来(经过一些必要的转换),这些记录称为 oplog。从节点通过在主节点上打开一个 tailable 游标不断获取新进入主节点的 oplog,并在自己的数据上回放,以此保持跟主节点的数据一致。无论 Primary、Secondary都是有 oplog 的。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注