一、故事背景:凌晨三点响起的告警

某次大促前夜,笔者的手机突然收到MongoDB集群状态异常的告警。当时副本集包含3个数据节点和1个仲裁节点,但业务系统开始出现间歇性写入失败。通过rs.status()命令发现仲裁节点处于"UNKNOWN"状态,而主节点频繁切换导致写入操作大面积失败。

(技术栈说明:本文所有示例均基于MongoDB 5.0版本,部署在Ubuntu 20.04系统)

二、致命配置错误分析

// 错误配置示例(初始化副本集时)
rs.initiate({
   _id: "myReplSet",
   members: [
      { _id: 0, host: "node1:27017" },
      { _id: 1, host: "node2:27017" },
      { _id: 2, host: "node3:27017" },
      { _id: 3, host: "arbiter:27017", arbiterOnly: true } // 仲裁节点声明
   ]
})

/* 
错误点分析:
1. 仲裁节点与数据节点使用相同端口号(违反最佳实践)
2. 未配置投票权(votes)参数
3. 网络防火墙未开放仲裁节点通信端口
*/

三、典型故障现象全记录

  1. 日志中的幽灵选举(主节点日志片段):
2023-08-20T03:12:45.456+0800 I REPL     [replexec-0] Starting election 
2023-08-20T03:12:45.458+0800 I REPL     [replexec-0] conducting a dry run election
2023-08-20T03:12:46.123+0800 W REPL     [replexec-0] Not standing for election; 
arbiter:27017 has no vote
  1. 客户端报错集锦
pymongo.errors.NotPrimaryError: node2:27017 is not primary

# Java客户端异常
com.mongodb.MongoNotPrimaryException: The replica set has no primary

四、排查法

步骤1:验证基础通信

# 在所有节点执行连通性测试
mongo --host arbiter --port 27017 --eval "db.adminCommand('ping')"

# 预期成功输出:
{ "ok" : 1 }

步骤2:检查副本集配置

// 在主节点执行配置验证
cfg = rs.conf()
cfg.members.forEach(m => {
   print(`节点 ${m._id}: 
   - 类型: ${m.arbiterOnly ? "仲裁" : "数据"}
   - 投票权: ${m.votes}
   - 优先级: ${m.priority}`)
})

步骤3:网络分区模拟测试

# 临时阻断仲裁节点网络(慎用!)
sudo iptables -A INPUT -p tcp --dport 27017 -s arbiter_ip -j DROP

步骤4:强制重新配置

// 危险操作!需先移除问题节点
rs.remove("arbiter:27017")
rs.reconfig(cfg, {force: true})

五、正确配置示范

# 生产环境推荐配置模板
members:
   - _id: 0
     host: "node1:27017"
     priority: 2
     votes: 1
   - _id: 1
     host: "node2:27017"
     priority: 1
     votes: 1
   - _id: 2
     host: "node3:27017"
     priority: 1
     votes: 1
   - _id: 3
     host: "arbiter:30000"  # 使用独立端口
     arbiterOnly: true
     votes: 1
     priority: 0

六、关联技术深度解析

副本集选举算法改进: MongoDB 4.0后采用Raft-like算法,当仲裁节点不可达时:

  1. 数据节点必须获得超过半数的投票
  2. 选举超时时间从默认的10秒调整为动态计算
  3. 心跳间隔影响故障检测灵敏度
// 选举参数调优示例
cfg.settings = {
   electionTimeoutMillis: 2000,
   heartbeatIntervalMillis: 500
}
rs.reconfig(cfg)

七、应用场景决策树

是否需要仲裁节点?
├── 节点数量为奇数 → 不需要
├── 节点数量为偶数 → 需要
└── 跨地域部署 → 优先使用隐藏节点代替

八、技术方案优劣对比

仲裁节点方案优势

  • 节省硬件成本(不需要存储数据)
  • 快速故障转移(减少选举成员数量)
  • 简化维护复杂度

潜在风险

  • 单点故障风险(需部署多个仲裁节点)
  • 网络抖动导致误判
  • 版本升级时的兼容性问题

九、十大避坑指南

  1. 永远为仲裁节点配置独立端口
  2. 生产环境禁止将仲裁节点部署在数据节点所在物理机
  3. 跨机房部署时仲裁节点应位于第三方区域
  4. 监控必须包含replSetGetConfigreplSetGetStatus指标
  5. 定期执行rs.stepDown()测试故障转移
  6. 使用连接字符串包含所有节点地址
  7. 避免在JavaScript模式下执行长时间操作
  8. 配置合理的writeConcern级别
  9. 日志级别至少保持为INFO
  10. 升级前使用featureCompatibilityVersion验证

十、血泪经验总结

经过本次故障排查,我们最终发现根本原因是仲裁节点未正确配置votes参数,导致在节点故障时无法形成有效多数派。修正配置后,集群稳定性显著提升,故障转移时间从原来的15秒缩短至3秒以内。

(以下是作者深夜调试后的灵魂感悟)

"仲裁节点就像分布式系统中的裁判,当裁判自己都站不稳时,整个比赛就会陷入混乱。给裁判一个稳固的哨位,比赛才能顺利进行。"