Skip to content

05 MongoDB 副本集与高可用 ​

MongoDB 副本集(Replica Set)是实现高可用和数据冗余的核心机制。本章介绍副本集的原理、部署、故障转移和生产环境最佳实践。

5.1 副本集概念 ​

5.1.1 架构 ​

text
副本集架构(至少 3 个节点):

┌─────────────────────────────────────────┐
│           Replica Set "rs0"             │
│                                         │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐
│  │ Primary  │  │Secondary │  │Secondary │
│  │  主节点   │◄─┤  从节点   │  │  从节点   │
│  │          │  │          │  │          │
│  │ 写操作    │  │ 读操作    │  │ 读操作    │
│  │ 数据同步  │  │ 数据复制  │  │ 数据复制  │
│  └──────────┘  └──────────┘  └──────────┘
│       ▲              │              │
│       │              │              │
│       └──────────────┴──────────────┘
│              oplog 复制
└─────────────────────────────────────────┘

5.1.2 节点类型 ​

节点类型角色特点
Primary主节点接受写操作,唯一可写节点
Secondary从节点复制主节点数据,可提供读操作
Arbiter仲裁节点只参与投票,不存储数据(节省资源)
Hidden隐藏节点不可见,用于备份或报告
Delayed延迟节点延迟复制,用于灾难恢复

5.1.3 选举机制 ​

text
选举规则:
  · 需要大多数节点(majority)投票
  · 3 节点:至少 2 票
  · 5 节点:至少 3 票
  · 优先级高的节点更容易当选
  · 数据最新的节点优先

故障转移流程:
  1. Primary 宕机
  2. Secondary 检测到 Primary 不可达
  3. 发起选举(需要大多数节点同意)
  4. 选举新的 Primary
  5. 客户端自动切换到新 Primary

5.2 部署副本集 ​

5.2.1 准备环境 ​

bash
# 创建 3 个数据目录
mkdir -p /data/rs0-0 /data/rs0-1 /data/rs0-2

# 生成密钥文件(用于内部认证)
openssl rand -base64 756 > /data/keyfile
chmod 400 /data/keyfile

5.2.2 启动节点 ​

bash
# 启动节点 0(端口 27017)
mongod --replSet rs0 \
  --port 27017 \
  --dbpath /data/rs0-0 \
  --keyFile /data/keyfile \
  --bind_ip localhost,192.168.1.10

# 启动节点 1(端口 27018)
mongod --replSet rs0 \
  --port 27018 \
  --dbpath /data/rs0-1 \
  --keyFile /data/keyfile \
  --bind_ip localhost,192.168.1.11

# 启动节点 2(端口 27019)
mongod --replSet rs0 \
  --port 27019 \
  --dbpath /data/rs0-2 \
  --keyFile /data/keyfile \
  --bind_ip localhost,192.168.1.12

5.2.3 初始化副本集 ​

javascript
// 连接到节点 0
mongosh --port 27017

// 初始化副本集
rs.initiate({
  _id: "rs0",
  members: [
    { _id: 0, host: "192.168.1.10:27017", priority: 2 },
    { _id: 1, host: "192.168.1.11:27018", priority: 1 },
    { _id: 2, host: "192.168.1.12:27019", priority: 1 }
  ]
})

// 查看状态
rs.status()

// 查看配置
rs.conf()

5.2.4 Docker Compose 部署 ​

yaml
# docker-compose.yml
version: '3.8'

services:
  mongo0:
    image: mongo:7.0
    container_name: mongo0
    ports:
      - "27017:27017"
    volumes:
      - mongo0_data:/data/db
      - ./keyfile:/data/keyfile
    command: mongod --replSet rs0 --keyFile /data/keyfile --bind_ip_all

  mongo1:
    image: mongo:7.0
    container_name: mongo1
    ports:
      - "27018:27017"
    volumes:
      - mongo1_data:/data/db
      - ./keyfile:/data/keyfile
    command: mongod --replSet rs0 --keyFile /data/keyfile --bind_ip_all

  mongo2:
    image: mongo:7.0
    container_name: mongo2
    ports:
      - "27019:27017"
    volumes:
      - mongo2_data:/data/db
      - ./keyfile:/data/keyfile
    command: mongod --replSet rs0 --keyFile /data/keyfile --bind_ip_all

volumes:
  mongo0_data:
  mongo1_data:
  mongo2_data:

5.3 副本集管理 ​

5.3.1 添加节点 ​

javascript
// 添加从节点
rs.add("192.168.1.13:27017")

// 添加仲裁节点
rs.addArb("192.168.1.14:27017")

// 添加隐藏节点
rs.add({
  host: "192.168.1.15:27017",
  priority: 0,
  hidden: true
})

5.3.2 移除节点 ​

javascript
// 移除节点
rs.remove("192.168.1.13:27017")

5.3.3 切换主节点 ​

javascript
// 手动切换主节点(step down)
rs.stepDown(60)  // 60 秒内不参与选举

// 强制切换
rs.stepDown(60, { force: true })

5.3.4 维护模式 ​

javascript
// 进入维护模式(临时降级为 Secondary)
rs.stepDown(600)  // 10 分钟

5.4 读写策略 ​

5.4.1 写关注(Write Concern) ​

javascript
// 写关注级别
db.users.insertOne(
  { name: "张三" },
  { writeConcern: { w: "majority", j: true, wtimeout: 5000 } }
)

// w: 1          - 主节点确认(默认)
// w: "majority" - 大多数节点确认
// w: 0          - 不等待确认
// j: true       - 写入 journal
// wtimeout      - 超时时间(毫秒)

5.4.2 读偏好(Read Preference) ​

javascript
// 设置读偏好
db.getMongo().setReadPref("secondaryPreferred")

// 或在查询时指定
db.users.find().readPref("secondary")

// 读偏好类型
// primary            - 只读主节点(默认)
// primaryPreferred   - 优先主节点,不可用时读从节点
// secondary          - 只读从节点
// secondaryPreferred - 优先从节点,不可用时读主节点
// nearest            - 读最近的节点

读偏好选择建议:

场景推荐读偏好原因
强一致性要求primary保证读最新数据
读多写少secondaryPreferred分担主节点压力
报表查询secondary不影响主节点性能
低延迟要求nearest读最近的节点

5.5 Oplog(操作日志) ​

5.5.1 Oplog 原理 ​

text
Oplog(Operation Log):
  · 主节点记录所有写操作
  · 从节点通过复制 oplog 保持同步
  · 固定大小的 capped collection
  · 默认大小:磁盘空间的 5%

Oplog 窗口:
  · 从最早记录到最新记录的时间范围
  · 从节点必须在这个窗口内完成同步
  · 如果从节点落后太多,需要重新全量同步

5.5.2 查看 Oplog ​

javascript
// 查看 oplog 状态
rs.printReplicationInfo()

// 输出示例:
// configured oplog size: 990.00 MB
// log length start to end: 172800 secs (48 hours)
// oplog first event time: Wed Sep 04 2026 10:00:00 GMT+0800
// oplog last event time: Fri Sep 06 2026 10:00:00 GMT+0800
// now: Fri Sep 06 2026 10:00:00 GMT+0800

// 查看从节点同步延迟
rs.printSecondaryReplicationInfo()

5.5.3 调整 Oplog 大小 ​

javascript
// 查看当前 oplog 大小
db.oplog.rs.stats().maxSize

// 调整 oplog 大小(需要重启)
// mongod.conf
replication:
  oplogSizeMB: 10240  // 10GB

5.6 故障转移与恢复 ​

5.6.1 自动故障转移 ​

text
故障转移流程:
  1. Primary 宕机或网络分区
  2. Secondary 在 10 秒内检测到
  3. 发起选举(需要大多数节点同意)
  4. 选举新的 Primary(通常 10-30 秒)
  5. 客户端自动重连到新 Primary

客户端配置:
  mongodb://host1:27017,host2:27018,host3:27019/?replicaSet=rs0
  · 客户端会自动发现拓扑变化
  · 自动切换到新 Primary

5.6.2 手动故障转移 ​

javascript
// 强制选举
rs.stepDown()

// 指定某个节点成为 Primary
// 1. 设置优先级
cfg = rs.conf()
cfg.members[1].priority = 10
rs.reconfig(cfg)

// 2. 当前 Primary step down
rs.stepDown()

5.6.3 数据恢复 ​

javascript
// 从节点数据落后太多,需要重新同步
// 1. 停止从节点
// 2. 删除数据目录
// 3. 重新启动(会自动全量同步)

// 或使用 initialSync
rs.syncFrom("primary_host:27017")

5.7 监控与告警 ​

5.7.1 关键指标 ​

javascript
// 副本集状态
rs.status()

// 关键字段
// "set" - 副本集名称
// "myState" - 当前节点状态(1=Primary, 2=Secondary)
// "members" - 所有成员状态
// "members[i].stateStr" - 状态描述
// "members[i].health" - 健康状态(1=健康)
// "members[i].optimeDate" - 最后操作时间
// "members[i].optimeLag" - 与主节点的延迟(秒)

5.7.2 监控脚本 ​

bash
#!/bin/bash
# check_replication.sh

# 检查副本集状态
mongosh --eval "
const status = rs.status();
const primary = status.members.find(m => m.stateStr === 'PRIMARY');
const secondaries = status.members.filter(m => m.stateStr === 'SECONDARY');

print('Primary: ' + primary.name);
print('Secondaries: ' + secondaries.length);

secondaries.forEach(s => {
  const lag = (new Date() - s.optimeDate) / 1000;
  print('  ' + s.name + ' lag: ' + lag + ' seconds');
  if (lag > 60) {
    print('WARNING: Replication lag > 60 seconds!');
    exit(1);
  }
});
"

if [ $? -eq 1 ]; then
  echo "Replication lag detected!"
  # 发送告警
fi

5.8 生产环境最佳实践 ​

5.8.1 节点配置 ​

text
推荐配置(3 节点):
  · 1 Primary + 2 Secondary
  · 或 1 Primary + 1 Secondary + 1 Arbiter

节点分布:
  · 跨可用区部署(不同机架/数据中心)
  · 避免所有节点在同一网络段
  · 至少 3 个投票节点

硬件要求:
  · Primary:高性能(CPU、内存、SSD)
  · Secondary:中等性能(可稍低)
  · Arbiter:最低配置(只参与投票)

5.8.2 连接字符串 ​

javascript
// 生产环境连接字符串
mongodb://user:password@host1:27017,host2:27018,host3:27019/mydb?
  replicaSet=rs0&
  authSource=admin&
  readPreference=secondaryPreferred&
  w=majority&
  wtimeoutMS=5000&
  retryWrites=true&
  retryReads=true

5.8.3 安全配置 ​

yaml
# mongod.conf

security:
  authorization: enabled
  keyFile: /data/keyfile

net:
  tls:
    mode: requireTLS
    certificateKeyFile: /etc/ssl/mongodb.pem
    CAFile: /etc/ssl/ca.pem

5.8.4 备份策略 ​

bash
# 从从节点备份(避免影响主节点)
mongodump --host secondary_host:27017 \
  --username backup_user \
  --password password \
  --authenticationDatabase admin \
  --oplog \
  --out /backup/mongodb

# 使用隐藏节点备份
# 隐藏节点不影响选举,专门用于备份

本章小结 ​

  • 副本集是 MongoDB 高可用的核心机制
  • 至少 3 个节点:1 Primary + 2 Secondary
  • 自动故障转移,通常在 10-30 秒内完成
  • 写关注控制数据一致性级别
  • 读偏好分散读压力到从节点
  • Oplog 是数据同步的基础,需要足够大的窗口
  • 生产环境跨可用区部署,使用 TLS 加密
  • 从从节点备份,避免影响主节点性能

练习:把「05 MongoDB 副本集与高可用」变成可验证能力 ​

  1. 从本文任选一个示例,在可丢弃环境重跑;记录运行时/服务版本、输入、关键输出和清理动作。
  2. 保持其他条件不变,只制造一个边界:空值、重复请求、并发、超时、断连、权限不足或资源耗尽。
  3. 用四句话复盘:现象是什么、原理在哪一层、证据是什么、生产中如何止损。

验收清单

  • [ ] 示例能从空环境重复执行,必要依赖和版本已写明。
  • [ ] 同时保留成功路径与至少一个失败路径,不用“应该没问题”冒充证据。
  • [ ] 结论写明适用边界,并能链接回对应学习路线的能力域。
  • [ ] 练习结束后已清理临时数据、凭据、端口或进程。

对照 数据库练习答案与复盘尺。先交自己的证据再翻答案;答案是镜子,不是传送门。

用心记录每一步成长