一、副本集故障的常见表现

当MongoDB副本集出现问题时,通常会有一些明显的症状。最常见的就是应用突然无法写入数据,查询也变得特别慢。有时候你会看到控制台不断弹出"Primary不可用"的警告,或者secondary节点莫名其妙地掉线。

比如我们有个三节点的副本集,突然发现应用报错了:

// 应用层报错示例
try {
    await db.collection('orders').insertOne({order_id: 1001});
} catch (err) {
    console.error('写入失败:', err.message); 
    // 典型错误: "no primary available for writes"
}

这种情况多半是副本集内部选举出了问题,或者网络分区导致节点间失去联系。我遇到过最棘手的一次是数据中心网络抖动,导致三个节点互相认为对方挂了,结果全都变成了secondary。

二、诊断故障的基本方法

遇到副本集问题时,别急着重启服务。先做系统性的检查,这里分享我的诊断三部曲:

首先,查看副本集状态:

// 在mongo shell中执行
rs.status()
// 重点关注:
// 1. members[].stateStr (节点状态)
// 2. members[].lastHeartbeat (心跳时间)
// 3. members[].optime (操作时间戳)

其次,检查日志是关键。MongoDB的日志通常会明确告诉你问题所在:

# 查看mongod日志的关键错误
grep -E "replSet error|ELECTION|heartbeat" /var/log/mongodb/mongod.log
# 常见错误示例:
# "Couldn't elect self, not receiving enough votes"
# "Heartbeat timeout, stopping replication"

最后,别忘了基础检查:

# 检查基础资源
free -h       # 内存情况
df -h         # 磁盘空间
ping node2    # 节点间网络

三、典型故障场景与解决方案

3.1 选举失败问题

这是最常见的副本集问题。比如我们有个三节点集群,突然无法选举出primary:

// rs.status()输出示例
{
    "set": "myReplSet",
    "members": [
        { "name": "node1:27017", "stateStr": "SECONDARY" },
        { "name": "node2:27017", "stateStr": "SECONDARY" },
        { "name": "node3:27017", "stateStr": "SECONDARY" }
    ]
}

解决方案分几步走:

  1. 检查多数节点是否在线:
rs.conf()  // 确认配置中定义的节点数
  1. 强制重新选举:
// 在其中一个secondary上执行
rs.stepDown()  // 先让当前primary下台
rs.freeze(60)  // 防止立即重新当选

3.2 数据不同步问题

有时候会发现secondary节点严重落后于primary:

// 检查复制延迟
db.printSlaveReplicationInfo()
// 输出示例:
// source: node1:27017
// syncedTo: Thu Jan 01 2020 12:00:00 GMT+0800
// 0 secs (0 hrs) behind the primary

处理方案:

  1. 对于轻微延迟,可以增加oplog大小:
// 在primary上执行
use admin
db.runCommand({replSetResizeOplog: 1, size: 2048})  // 单位MB
  1. 对于严重延迟,可能需要重新同步:
// 在secondary上执行
rs.syncFrom("node1:27017")  // 指定同步源

四、预防性维护建议

与其被动救火,不如主动预防。以下是我的运维经验:

  1. 合理设置副本集配置:
// 推荐的副本集配置
cfg = rs.conf()
cfg.settings = {
    "heartbeatIntervalMillis": 2000,
    "electionTimeoutMillis": 10000
}
rs.reconfig(cfg)
  1. 定期检查副本集健康状态:
# 编写定期检查脚本
#!/bin/bash
mongo --eval "db.hello().isWritablePrimary || process.exit(1)" && echo "OK" || echo "Not Primary"
  1. 设置适当的监控告警:
# Prometheus监控规则示例
- alert: MongoDBReplLag
  expr: mongodb_replset_oplog_head_timestamp - mongodb_replset_oplog_tail_timestamp > 60
  for: 5m
  labels:
    severity: warning

五、高级故障处理技巧

对于特别棘手的问题,可能需要一些高级手段:

  1. 当副本集配置损坏时:
// 紧急恢复配置
var tempConfig = rs.conf()
tempConfig.members = tempConfig.members.filter(m => m.host.includes("node1"))
rs.reconfig(tempConfig, {force: true})
// 注意: force参数慎用,可能导致数据不一致
  1. 处理网络分区问题:
// 手动设置节点优先级
cfg = rs.conf()
cfg.members[0].priority = 2
cfg.members[1].priority = 1
rs.reconfig(cfg)
// 这样在网络恢复时,优先级高的节点更容易当选primary

六、总结与最佳实践

经过多次实战,我总结了几个关键点:

  1. 保持奇数节点数量(3个或5个),避免选举僵局
  2. 跨机房部署时,合理设置节点优先级
  3. 定期检查oplog大小,建议至少保留24小时的操作量
  4. 重要集群建议部署监控代理,如MongoDB Ops Manager

记住,副本集的问题往往不是孤立的,可能是整个系统问题的表现。处理时要全面考虑网络、存储、资源配置等多方面因素。