今天又经历了一次修复Mongo副本集的过程,我觉得得写下来。
大约在3年前搭建了Mongo集群(Mongo5),然后我使用ip代理的方式让集群的机器可以公网访问。ip代理机器一般只能租3年,所以到期就会更换,此时就会出现问题:集群的机器不能通信,访问失败
首先,肯定是要登录到终端。
docker exec -it Mongo_Rep_Container
use admin
db.auth("user","password")
在查询db.isMaster()
大概会是这样的: Does not have a valid replica set config
{
"topologyVersion" : {
"processId" : ObjectId("68a282f5dd03af7c246cded7"),
"counter" : NumberLong(1)
},
"ismaster" : false,
"secondary" : false,
"info" : "Does not have a valid replica set config",
...
}
此时整个集群已经属于不可用状态, node1, node2和node3都已经失联了。
Step1 将某个节点升级为PRIMARY
此时,先选一个节点重新初始化集群,副本集已经初始化过,所以直接 rs.initiate() 会报 “AlreadyInitialized”
rs.initiate(
{
_id : "xxx",
members: [
{ _id : 0, host : "YOURIP:THE_PORT",priority:1 }
]
})
强制执行又会报一个错,这是因为如果节点存在过,即使重新初始化也不可以改变NodeID
mymeta:SECONDARY> rs.reconfig(cfg, {force: true})
{
"ok" : 0,
"errmsg" : "New and old configurations both have members with host of IP:PORT but in the new configuration the _id field is MemberId(0) and in the old configuration it is MemberId(12) for replica set mymeta",
"code" : 103,
"codeName" : "NewReplicaSetConfigurationIncompatible"
}
保持NodeID不变,重新强制初始化
cfg = rs.conf()
cfg.members = [
{
_id: 12, // 保持原来的 _id
host: "IP:PORT",
priority: 1,
votes: 1
}
]
rs.reconfig(cfg, {force:true})
此时节点会完成变化,正式转为PRIMARY节点
rs.status()
{
"set" : "REPLICA NAME",
...
},
"members" : [
{
"_id" : 12,
"name" : "IP:PORT",
"health" : 1,
"state" : 1,
"stateStr" : "PRIMARY",
mymeta:PRIMARY>
Step2 将其他节点加入
我之前的三个节点,其中一个已经重新成为主节点,且网络中只有一个节点。一个节点被我弄坏了(被大模型骗的),另一个除了失联,别的没问题。
处理弄坏的节点
先把对应的数据部分删除(/data/...replica
)
重新初始化项目:在opt下方配置和秘钥,在data下放文件夹
# 1 在data目录创建文件夹
proc_path=/opt/MongoReplica/
data_path=/data/MongoReplica/
sh ${proc_path}/create_shard_folder.sh ${data_path}/replica
# 修改秘钥文件
chown 999 ${proc_path}replica/mongo.key
chmod 600 ${proc_path}replica/mongo.key
其中create_shard_folder.sh
的目的是创建副本文件夹并修改权限。
#!bin/bash
mkdir -p $1
mkdir $1/db
mkdir $1/log
touch $1/log/mongod.log
chmod -R 777 $1
启动容器
image_name="MONGOIMAGE"
port=PORT
docker run -d \
--restart=always \
--name='CONTAINERNAME' \
-v /etc/localtime:/etc/localtime \
-v /etc/timezone:/etc/timezone\
-e "LANG=C.UTF-8"\
-m 2g \
-v ${proc_path}replica/mongo.key:/etc/mongo.key \
-v ${data_path}/replica:/home/mongod \
-v ${proc_path}replica/mongod.conf:/etc/mongod.conf \
-p ${port}:27017 \
${image_name} \
-f /etc/mongod.conf
在PRIMARY节点执行:
rs.add("IP:PORT2")
rs.status()
"_id" : 13,
"name" : "IP:PORT2",
"health" : 1,
"state" : 5,
"stateStr" : "STARTUP2",
...
到这里"stateStr" : "STARTUP2",
节点已经在同步数据了
处理没坏的节点
这个过程很顺利,并没有发生问题。当然最坏的情况也就是重新同步一次。
日志问题
因为集群是依靠oplog来进行同步的,这个日志会随着时间越来越大。一些所谓的控制方法我都试过,似乎不太灵光。然后之前我做好过轮转日志,但是只会轮转主节点,不会作用在从节点,这就比较麻烦。
毕竟如果集群搭建起来,总不能隔三差五的去删节点,加节点吧。
这次也解决了这个问题:主节点用Rotate,从节点用SIGUSR1
SIGUSR1 是什么
SIGUSR1 是 Unix/Linux 系统里的一个 用户自定义信号(Signal)
MongoDB 对这个信号有特殊处理:
当 mongod 进程收到 SIGUSR1 时,会 安全地关闭当前日志文件并重新打开日志
不会影响数据库的读写,也不会触发重启
换句话说,这是 MongoDB 官方推荐的 日志轮转方式,尤其适合 Secondary 或通过 Docker 启动的节点。
直接执行命令可以测试
副本日志清理
docker exec -it CONTAINER pgrep mongod // 一般是1
docker exec -it CONTAINER kill -USR1 1
考虑到是低频操作,而且节点可能会随着选举变化
import subprocess
import pymongo
# -------------------------------
# 配置
# -------------------------------
container_name = "CONTAINER"
mongo_host = "127.0.0.1"
mongo_port = PORT
mongo_user = "fff"
mongo_pwd = "ff"
auth_db = "admin"
# -------------------------------
# 1️⃣ 连接 MongoDB
# -------------------------------
client = pymongo.MongoClient(
host=mongo_host,
port=mongo_port,
username=mongo_user,
password=mongo_pwd,
authSource=auth_db,
authMechanism='SCRAM-SHA-1'
)
db = client['admin']
# -------------------------------
# 2️⃣ 判断是否主节点
# -------------------------------
ismaster = db.command("isMaster")
if ismaster.get("ismaster", False):
# 主节点,用 logRotate 命令
db.command({'logRotate': 1})
print("Primary 节点日志轮转完成")
else:
# 从节点,用 SIGUSR1
# 找 PID
pid_cmd = f"docker exec -i {container_name} pgrep mongod"
pid = subprocess.check_output(pid_cmd, shell=True).decode().strip()
kill_cmd = f"docker exec -i {container_name} kill -USR1 {pid}"
subprocess.run(kill_cmd, shell=True)
print(f"Secondary 节点日志轮转完成,发送 SIGUSR1 给 PID {pid}")
然后日志可以被轮转,另外再写一个简单的sh脚本,把对应目录下包含日期的文件取出来,然后再删除。
clear_mongo_rep.sh
#!/bin/bash
LOG_DIR="/data/aprojects/MongoReplica/replica/log"
# 删除所有带日期的日志文件(只保留 mongod.log)
find "$LOG_DIR" -type f -name "mongod.log.2*" -delete
echo "✅ 已清理所有历史日志文件"
Mongo5集群作为一个基础部件,以后应该也不太改了。以后有需要还可以搭建Mongo8集群。