【最后203篇系列】033 Mongo副本集修复过程

发布于:2025-08-19 ⋅ 阅读:(18) ⋅ 点赞:(0)

今天又经历了一次修复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集群。


网站公告

今日签到

点亮在社区的每一天
去签到