Skip to content

副本集

副本集概述

什么是副本集?

副本集(Replica Set)是一组 MongoDB 实例,提供数据冗余和高可用性。

副本集架构

                    ┌─────────────┐
                    │   Primary   │
                    │  (主节点)    │
                    └──────┬──────┘

           ┌───────────────┼───────────────┐
           │               │               │
           ▼               ▼               ▼
    ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
    │  Secondary  │ │  Secondary  │ │   Arbiter   │
    │  (从节点)    │ │  (从节点)    │ │  (仲裁节点)  │
    └─────────────┘ └─────────────┘ └─────────────┘

节点类型

类型说明
Primary主节点,处理写操作
Secondary从节点,复制数据
Arbiter仲裁节点,参与选举
Hidden隐藏节点,不参与选举
Delayed延迟节点,延迟复制
Priority 0优先级为0,不成为主节点

复制原理

Primary                    Secondary
   │                          │
   ├─── 写入 Oplog ───────────┤
   │                          │
   │                    读取 Oplog
   │                          │
   │                    应用操作
   │                          │

部署副本集

配置文件

主节点配置

yaml
storage:
  dbPath: /data/db
  journal:
    enabled: true

systemLog:
  destination: file
  path: /var/log/mongodb/mongod.log

net:
  bindIp: 0.0.0.0
  port: 27017

replication:
  replSetName: rs0

security:
  authorization: enabled
  keyFile: /etc/mongodb/keyfile

从节点配置

yaml
storage:
  dbPath: /data/db
  journal:
    enabled: true

systemLog:
  destination: file
  path: /var/log/mongodb/mongod.log

net:
  bindIp: 0.0.0.0
  port: 27017

replication:
  replSetName: rs0

security:
  authorization: enabled
  keyFile: /etc/mongodb/keyfile

初始化副本集

javascript
rs.initiate({
  _id: "rs0",
  members: [
    { _id: 0, host: "mongo1:27017" },
    { _id: 1, host: "mongo2:27017" },
    { _id: 2, host: "mongo3:27017" },
  ],
});

添加节点

javascript
rs.add("mongo4:27017");
rs.add({ host: "mongo5:27017", priority: 0, hidden: true });
rs.addArb("arbiter:27017");

删除节点

javascript
rs.remove("mongo4:27017");

副本集管理

查看状态

javascript
rs.status();
rs.isMaster();
rs.conf();

状态字段说明

字段说明
name节点地址
state节点状态
stateStr状态描述
health健康状态
uptime运行时间
optime最后操作时间
lastHeartbeat最后心跳时间

节点状态

状态说明
PRIMARY主节点
SECONDARY从节点
STARTUP启动中
STARTUP2初始化中
RECOVERING恢复中
UNKNOWN未知
ARBITER仲裁节点
DOWN宕机
ROLLBACK回滚中
REMOVED已移除

修改配置

javascript
let cfg = rs.conf();
cfg.members[0].priority = 2;
cfg.members[1].hidden = true;
cfg.members[1].priority = 0;
rs.reconfig(cfg);

强制重新配置

javascript
rs.reconfig(cfg, { force: true });

选举机制

选举触发条件

  • 主节点宕机
  • 主节点网络中断
  • 主节点主动降级
  • 新节点加入

选举过程

1. 发现主节点不可用
2. 发起选举请求
3. 获取多数投票
4. 成为新的主节点

优先级设置

javascript
rs.add({
  host: "mongo4:27017",
  priority: 2,
});

let cfg = rs.conf();
cfg.members[0].priority = 3;
rs.reconfig(cfg);

禁止选举

javascript
rs.freeze(60);

rs.freeze(0);

主动降级

javascript
rs.stepDown(60);
rs.stepDown({ force: true });

读偏好

读偏好模式

模式说明
primary只读主节点(默认)
primaryPreferred优先主节点
secondary只读从节点
secondaryPreferred优先从节点
nearest最近节点

设置读偏好

javascript
db.users.find().readPref("secondary");
db.users.find().readPref("secondaryPreferred");
db.users.find().readPref("nearest");

连接字符串

mongodb://host1:27017,host2:27017,host3:27017/mydb?readPreference=secondary
mongodb://host1:27017,host2:27017,host3:27017/mydb?readPreference=secondaryPreferred&maxStalenessSeconds=120

标签读取

javascript
let cfg = rs.conf();
cfg.members[0].tags = { dc: "east", use: "reporting" };
cfg.members[1].tags = { dc: "west", use: "reporting" };
rs.reconfig(cfg);

db.users.find().readPref("secondary", { dc: "east" });

写关注

写关注级别

级别说明
w: 0不确认
w: 1主节点确认(默认)
w: majority大多数节点确认
w: nn个节点确认
w: 'tag'指定标签节点确认

设置写关注

javascript
db.users.insertOne(
  { name: "张三" },
  { writeConcern: { w: "majority", j: true } },
);

db.users.insertOne(
  { name: "李四" },
  { writeConcern: { w: 3, j: true, wtimeout: 5000 } },
);

默认写关注

javascript
db.adminCommand({
  setDefaultRWConcern: 1,
  defaultWriteConcern: { w: "majority", j: true },
});

特殊节点

仲裁节点

javascript
rs.addArb("arbiter:27017");

仲裁节点不存储数据,只参与投票。

隐藏节点

javascript
let cfg = rs.conf();
cfg.members[2].hidden = true;
cfg.members[2].priority = 0;
rs.reconfig(cfg);

隐藏节点对客户端不可见,可用于备份。

延迟节点

javascript
let cfg = rs.conf();
cfg.members[2].priority = 0;
cfg.members[2].hidden = true;
cfg.members[2].slaveDelay = 3600;
rs.reconfig(cfg);

延迟节点延迟复制数据,可用于数据恢复。

优先级为0的节点

javascript
let cfg = rs.conf();
cfg.members[2].priority = 0;
rs.reconfig(cfg);

优先级为0的节点永远不会成为主节点。

Oplog

Oplog 概述

Oplog(操作日志)记录所有写操作,用于复制。

查看 Oplog

javascript
use local
db.oplog.rs.find().sort({ $natural: -1 }).limit(10)

Oplog 大小

javascript
db.adminCommand({ replSetResizeOplog: 1, size: 16000 });

Oplog 格式

javascript
{
    "ts": Timestamp(1705312200, 1),
    "t": 1,
    "h": 0,
    "v": 2,
    "op": "i",
    "ns": "mydb.users",
    "ui": UUID("..."),
    "wall": ISODate("..."),
    "o": { "_id": 1, "name": "张三" }
}

Oplog 字段说明

字段说明
ts时间戳
t选举任期
op操作类型
ns命名空间
o操作文档
o2查询条件

操作类型

类型说明
i插入
u更新
d删除
n空操作
c命令

故障恢复

自动故障转移

当主节点不可用时,副本集会自动选举新的主节点。

手动故障转移

javascript
rs.stepDown();

节点恢复

javascript
rs.add("mongo4:27017");

数据同步

javascript
rs.printReplicationInfo();
rs.printSlaveReplicationInfo();

重新同步

javascript
db.adminCommand({ resync: 1 });

监控

副本集状态

javascript
rs.status();

复制延迟

javascript
rs.printSlaveReplicationInfo();

db.adminCommand({ replSetGetStatus: 1 }).members.forEach(function (m) {
  if (m.stateStr === "SECONDARY") {
    print(
      m.name +
        ": " +
        (m.optimeDate - rs.status().members[0].optimeDate) / 1000 +
        " seconds behind",
    );
  }
});

监控指标

指标说明
replicationLag复制延迟
oplogWindowOplog 时间窗口
memberHealth节点健康状态
electionCount选举次数

监控脚本

javascript
function monitorReplicaSet() {
  let status = rs.status();

  status.members.forEach(function (member) {
    print("节点: " + member.name);
    print("  状态: " + member.stateStr);
    print("  健康: " + member.health);
    if (member.optimeDate) {
      print("  最后操作: " + member.optimeDate);
    }
    if (member.lastHeartbeat) {
      print("  最后心跳: " + member.lastHeartbeat);
    }
    print("");
  });
}

monitorReplicaSet();

常见问题

1. 主节点选举失败

原因:

  • 节点数量不足
  • 网络分区
  • 配置错误

解决:

javascript
rs.status();
rs.reconfig(cfg, { force: true });

2. 复制延迟过大

原因:

  • 从节点性能不足
  • 网络延迟
  • 大量写入

解决:

  • 增加从节点配置
  • 优化网络
  • 使用批量写入

3. 数据不一致

原因:

  • 网络分区
  • 写关注级别过低

解决:

javascript
db.users.find().readConcern('majority')
db.users.insertOne({...}, { writeConcern: { w: 'majority' } })

4. Oplog 耗尽

原因:

  • 从节点宕机时间过长
  • Oplog 太小

解决:

javascript
db.adminCommand({ replSetResizeOplog: 1, size: 16000 });

高可用架构

三节点架构

Primary + Secondary + Secondary

最简单的生产架构,可容忍一个节点故障。

五节点架构

Primary + 2 Secondary + 2 Arbiter

可容忍两个节点故障。

跨机房架构

javascript
rs.initiate({
  _id: "rs0",
  members: [
    { _id: 0, host: "mongo1.dc1:27017", priority: 2, tags: { dc: "dc1" } },
    { _id: 1, host: "mongo2.dc1:27017", priority: 1, tags: { dc: "dc1" } },
    { _id: 2, host: "mongo3.dc2:27017", priority: 1, tags: { dc: "dc2" } },
    { _id: 3, host: "mongo4.dc2:27017", priority: 1, tags: { dc: "dc2" } },
    { _id: 4, host: "arbiter:27017", arbiterOnly: true },
  ],
});

最佳实践

1. 节点数量

  • 最少3个节点(1主2从)
  • 奇数个投票节点
  • 生产环境建议5个节点

2. 网络配置

  • 使用低延迟网络
  • 配置防火墙规则
  • 启用 TLS 加密

3. 监控告警

  • 监控节点状态
  • 监控复制延迟
  • 设置告警阈值

4. 备份策略

  • 定期备份
  • 使用隐藏节点备份
  • 验证备份有效性

5. 故障演练

  • 定期进行故障转移测试
  • 验证自动恢复能力
  • 更新运维文档