MongoDB ReplicaSet 主节点离线:本机 failover 接管实战

一套 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 单节点提升 → 应用改连本机。