一套 OJ 系统的数据库用了 MongoDB 复制集(ReplicaSet rs0),主节点在一台经常离线的机器上。某天主节点整机失联(ping 不通、ARP 无响应),整个后端全部 502。这里记录如何把本机从节点强制提升为主、完成接管,以及排查过程中遇到的各种坑。
一、故障现象
# nginx 错误
upstream "http://192.168.2.125:8888/" connect failed (No route to host)
upstream "http://127.0.0.1:8888/" recv failed
# 应用日志
MongoServerSelectionError: connect ECONNREFUSED 192.168.2.125:27017
MongoServerError: node is not in primary or recovering state
主节点 192.168.2.125 离线 → 复制集选不出主 → 从节点进入只读受限状态 → 依赖它的应用全部失败。
二、先确认本机数据在不在
接管的前提是本机数据库里有完整数据。本机容器数据目录 507MB,是 rs0 的同步副本。注意复制集模式下从节点默认拒绝读写,直接 listDatabases 会报 not in primary or recovering state,这是正常的。
三、关键点:本机不是复制集有效成员
用 mongosh 读本地复制集配置:
db.getSiblingDB("local").system.replset.findOne().members
# [{_id:0, host:"192.168.2.125:27017"}, {_id:1, host:"192.168.2.123:27017"}, ...]
发现成员列表里全是远端 IP,本机容器自己不在列表里 —— 所以它永远选不上主,状态一直是 invalid。这正是「有数据但没法用」的根源。
四、强制 failover 为本机单节点主
先备份原复制集配置留档,然后 force 重新配置成单节点:
# 备份配置
mongosh --eval 'print(JSON.stringify(db.getSiblingDB("local").system.replset.findOne()))' > rs0_backup.json
# 强制重配为单节点(非 primary 也可以 force)
mongosh "mongodb://127.0.0.1:27017/?retryWrites=false" --eval '
rs.reconfig({_id:"rs0", version:1, members:[{_id:0, host:"127.0.0.1:27017"}]}, {force:true});
'
# 等待后验证
mongosh --eval 'rs.status().members[0].stateStr' # → PRIMARY
踩坑:drop 复制集配置失败
一开始想 drop 掉 local.system.replset 再重新 rs.initiate,结果:
drop: This MongoDB deployment does not support retryable writes.
Please add retryWrites=false to your connection string.
initiate: already initialized
drop 需要连接串带 retryWrites=false,而且复制集已初始化会拒绝二次 initiate。rs.reconfig({force:true}) 才是官方支持的非主节点覆盖方式,一步到位。
五、应用层连库:容器 localhost 的坑
后端(docker 容器)连库配置指向旧主节点,改成连本机后一直报 ECONNREFUSED 127.0.0.1:27017,明明宿主 27017 有监听。
原因:容器里的 127.0.0.1 是容器自己,不是宿主机!容器连宿主必须用宿主 IP 或 host 网络。
最干净的办法:把后端容器改成 host 网络重建,容器内 127.0.0.1 就是宿主机,数据库默认连接直接生效:
docker run -d --name oj-backend --network host --restart always \
-v /path/file:/data/file -v /path/backend:/root/.hydro \
hydrooj_oj-backend
# 日志出现 Server listening at: 0.0.0.0:8888 即恢复
六、遗留风险
- 接管后本机是独立单节点主库,原主节点恢复后不会自动并入这个复制集,需要重新规划拓扑(合并或迁移);
- 接管期间写入的新数据只在本地,原主恢复前先别让它抢回主节点,避免 split-brain;
- 长期方案:把复制集成员改为「本机为主 + 备用机为从」,并且主备两台都不该是日常开关机的机器。
七、小结
复制集兜底不等于高可用,主节点必须是可靠的常开机器,否则一离线全站 502。真遇到离线,记住三板斧:确认本机有数据 → force reconfig 单节点提升 → 应用改连本机。