Skip to content
On this page

MongoDB 副本集

副本集(Replica Set)是MongoDB实现高可用性和数据冗余的核心机制。副本集由一组MongoDB实例组成,它们维护相同的数据集,提供自动故障转移和数据恢复功能。

副本集基础概念

什么是副本集

副本集是一组维护相同数据集的mongod进程。副本集提供冗余和高可用性,是生产环境中推荐的部署方式。

副本集组件

  1. 主节点(Primary):处理所有写操作
  2. 从节点(Secondary):复制主节点的数据
  3. 仲裁节点(Arbiter):只参与选举,不存储数据
javascript
// 副本集配置示例
{
  "_id": "myReplSet",
  "members": [
    {
      "_id": 0,
      "host": "mongodb1.example.com:27017",
      "priority": 2
    },
    {
      "_id": 1,
      "host": "mongodb2.example.com:27017",
      "priority": 1
    },
    {
      "_id": 2,
      "host": "mongodb3.example.com:27017",
      "arbiterOnly": true
    }
  ]
}

副本集架构

主从架构

bash
# 主节点处理所有写操作
Primary Node (writes + reads)
    |
    |--- 复制到
    |
    v
Secondary Node 1 (reads only)
    |
    |--- 复制到
    |
    v
Secondary Node 2 (reads only)

选举机制

  • 当主节点不可用时,从节点之间进行选举
  • 获得多数票的节点成为新的主节点
  • 仲裁节点参与选举但不存储数据

部署副本集

1. 环境准备

bash
# 创建数据目录
mkdir -p /data/rs1 /data/rs2 /data/rs3

# 启动三个mongod实例
mongod --replSet myReplSet --port 27017 --dbpath /data/rs1 --bind_ip localhost
mongod --replSet myReplSet --port 27018 --dbpath /data/rs2 --bind_ip localhost
mongod --replSet myReplSet --port 27019 --dbpath /data/rs3 --bind_ip localhost

2. 初始化副本集

javascript
// 连接到任一mongod实例
mongo --port 27017

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

3. 验证副本集状态

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

// 查看副本集配置
rs.conf()

// 查看当前节点角色
db.isMaster()

副本集配置

基本配置选项

javascript
// 查看当前配置
cfg = rs.conf()

// 修改配置 - 例如设置优先级
cfg.members[0].priority = 2  // 提高主节点优先级
cfg.members[1].priority = 1
cfg.members[2].priority = 1

// 应用配置更改
rs.reconfig(cfg)

高级配置选项

javascript
// 配置示例 - 包含高级选项
{
  "_id": "myReplSet",
  "version": 1,
  "protocolVersion": NumberLong(1),
  "writeConcernMajorityJournalDefault": true,
  "members": [
    {
      "_id": 0,
      "host": "mongodb1.example.com:27017",
      "arbiterOnly": false,
      "buildIndexes": true,
      "hidden": false,  // 不隐藏节点
      "priority": 2,    // 优先级较高
      "tags": {
        "dc": "east",
        "rack": "rack1"
      },
      "slaveDelay": NumberLong(0),  // 无延迟
      "votes": 1  // 参与投票
    },
    {
      "_id": 1,
      "host": "mongodb2.example.com:27017",
      "priority": 1,
      "tags": {
        "dc": "west",
        "rack": "rack1"
      }
    },
    {
      "_id": 2,
      "host": "mongodb3.example.com:27017",
      "arbiterOnly": true,  // 仲裁节点
      "priority": 0,        // 不可成为主节点
      "votes": 1
    }
  ],
  "settings": {
    "chainingAllowed": true,
    "heartbeatIntervalMillis": 2000,
    "heartbeatTimeoutSecs": 10,
    "electionTimeoutMillis": 10000,
    "catchUpTimeoutMillis": 30000,
    "getLastErrorDefaults": {
      "w": 1,
      "j": true
    }
  }
}

读写操作

写操作

javascript
// 所有写操作都发送到主节点
db.products.insertOne({
  name: "iPhone 13",
  price: 6999
})

// 写关注(Write Concern)
db.products.insertOne(
  { name: "iPad Pro", price: 8999 },
  { writeConcern: { w: "majority", j: true, wtimeout: 5000 } }
)

读操作

javascript
// 读关注(Read Concern)
// local - 默认,读取实例的最新数据
db.products.find({}).readConcern("local")

// majority - 读取大多数节点确认的数据
db.products.find({}).readConcern("majority")

// linearizable - 线性化读取(仅主节点)
db.products.find({}).readConcern("linearizable")

// 读偏好(Read Preference)
// primary - 只从主节点读取(默认)
db.products.find({}).readPref("primary")

// secondary - 只从从节点读取
db.products.find({}).readPref("secondary")

// primaryPreferred - 优先从主节点读取
db.products.find({}).readPref("primaryPreferred")

// secondaryPreferred - 优先从从节点读取
db.products.find({}).readPref("secondaryPreferred")

// nearest - 从网络延迟最低的节点读取
db.products.find({}).readPref("nearest")

故障转移

自动故障转移过程

javascript
// 模拟主节点故障
// 1. 从节点检测到主节点不可用
// 2. 发起选举
// 3. 获得多数票的节点成为新主节点
// 4. 应用程序自动连接到新主节点

// 检查当前主节点
rs.isMaster()

// 手动触发选举
rs.stepDown()  // 当前主节点主动降级

故障转移配置

javascript
// 调整故障转移参数
cfg = rs.conf()
cfg.settings.electionTimeoutMillis = 15000  // 选举超时时间
cfg.settings.catchUpTimeoutMillis = 60000   // 数据同步超时时间
rs.reconfig(cfg)

延迟从节点

创建延迟从节点

javascript
// 配置延迟从节点(用于灾难恢复)
cfg = rs.conf()
cfg.members[1].priority = 0  // 不可成为主节点
cfg.members[1].hidden = true // 隐藏节点
cfg.members[1].slaveDelay = 3600  // 延迟1小时
rs.reconfig(cfg)

隐藏节点

创建隐藏节点

javascript
// 隐藏节点不对外提供服务,只用于备份或报告
cfg = rs.conf()
cfg.members[2].priority = 0  // 不参与选举
cfg.members[2].hidden = true // 隐藏
rs.reconfig(cfg)

标签和优先级

使用标签进行路由

javascript
// 为节点添加标签
cfg = rs.conf()
cfg.members[0].tags = { "dc": "east", "usage": "prod" }
cfg.members[1].tags = { "dc": "west", "usage": "prod" }
cfg.members[2].tags = { "dc": "east", "usage": "backup" }
rs.reconfig(cfg)

// 基于标签的读取
db.products.find({}).readPref("secondary", [{ "dc": "west" }])

优先级配置

javascript
// 设置节点优先级
cfg = rs.conf()
cfg.members[0].priority = 2  // 更高的优先级
cfg.members[1].priority = 1
cfg.members[2].priority = 0  // 不会成为主节点
rs.reconfig(cfg)

监控副本集

监控命令

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

// 复制延迟
db.printSlaveReplicationInfo()

// 当前操作
db.currentOp()

// 服务器状态
db.serverStatus()

// 复制状态
db.adminCommand({replSetGetStatus: 1})

监控指标

javascript
// 获取复制延迟
function getReplicationLag() {
  const status = rs.status();
  const primary = status.members.find(m => m.stateStr === "PRIMARY");
  
  status.members.forEach(member => {
    if (member.stateStr === "SECONDARY") {
      const lag = primary.optimeDate - member.optimeDate;
      print(`Node ${member.name} lag: ${lag}ms`);
    }
  });
}

连接字符串和客户端配置

应用程序连接

javascript
// Node.js 连接副本集
const { MongoClient } = require('mongodb');

const uri = "mongodb://host1:27017,host2:27017,host3:27017/mydb?replicaSet=myReplSet";

const client = new MongoClient(uri, {
  readPreference: 'secondaryPreferred',
  w: 'majority',
  j: true
});

// 读偏好配置
const options = {
  readPreference: 'secondary',  // 从从节点读取
  readConcern: { level: 'majority' },  // 多数确认读取
  writeConcern: { w: 'majority', j: true }  // 多数确认写入
};

连接池配置

javascript
// 连接池优化
const client = new MongoClient(uri, {
  maxPoolSize: 20,        // 最大连接数
  minPoolSize: 5,         // 最小连接数
  maxIdleTimeMS: 30000,   // 最大空闲时间
  serverSelectionTimeoutMS: 5000,  // 服务器选择超时
  heartbeatFrequencyMS: 10000      // 心跳频率
});

备份和恢复

使用mongodump备份副本集

bash
# 备份整个副本集
mongodump --host myReplSet/mongodb1:27017,mongodb2:27017,mongodb3:27017 --out /backup/dump

# 从从节点备份(减少对主节点的影响)
mongodump --host mongodb2:27017 --readPreference secondary --out /backup/dump

使用mongorestore恢复

bash
# 恢复到副本集
mongorestore --host myReplSet/mongodb1:27017,mongodb2:27017,mongodb3:27017 /backup/dump

性能优化

复制优化

javascript
// 调整复制参数
cfg = rs.conf()
cfg.settings.chainingAllowed = true  // 允许节点间复制(提高效率)
cfg.settings.writeConcernMajorityJournalDefault = true
rs.reconfig(cfg)

网络优化

javascript
// 优化网络配置
cfg = rs.conf()
cfg.settings.heartbeatIntervalMillis = 2000  // 心跳间隔
cfg.settings.heartbeatTimeoutSecs = 10       // 心跳超时
cfg.settings.electionTimeoutMillis = 10000   // 选举超时
rs.reconfig(cfg)

安全配置

启用认证

bash
# 启动时启用认证
mongod --replSet myReplSet --auth --keyFile /path/to/keyfile

# 或使用SCRAM-SHA-256
mongod --replSet myReplSet --auth --clusterAuthMode keyFile

创建用户

javascript
// 在主节点上创建用户
use admin
db.createUser({
  user: "replica_admin",
  pwd: "secure_password",
  roles: [
    { role: "clusterAdmin", db: "admin" },
    { role: "readWriteAnyDatabase", db: "admin" }
  ]
})

管理操作

添加节点

javascript
// 添加新节点到副本集
cfg = rs.conf()
cfg.members.push({
  _id: cfg.members.length,
  host: "mongodb4.example.com:27017"
})
rs.reconfig(cfg)

移除节点

javascript
// 从副本集中移除节点
cfg = rs.conf()
cfg.members = cfg.members.filter(member => member._id !== 2)
rs.reconfig(cfg)

调整节点属性

javascript
// 将节点设置为仲裁节点
cfg = rs.conf()
cfg.members[2].arbiterOnly = true
cfg.members[2].priority = 0
rs.reconfig(cfg)

最佳实践

1. 节点数量

  • 使用奇数个节点(3、5、7)
  • 至少3个节点确保高可用性
  • 避免偶数个节点(需要额外仲裁节点)

2. 地理分布

  • 将节点分布在不同地理位置
  • 确保网络延迟可接受
  • 考虑数据中心故障场景

3. 硬件配置

  • 主节点通常需要更好的硬件
  • 确保网络带宽足够
  • 使用SSD提高I/O性能

4. 监控和告警

javascript
// 监控脚本示例
function monitorReplicaSet() {
  const status = rs.status();
  
  // 检查成员状态
  status.members.forEach(member => {
    if (member.health !== 1) {
      print(`Node ${member.name} is unhealthy!`);
    }
    
    // 检查复制延迟
    const optimeDate = new Date(member.optimeDate);
    const now = new Date();
    const lag = (now - optimeDate) / 1000; // 秒
    
    if (lag > 30) {  // 超过30秒延迟
      print(`Node ${member.name} has ${lag}s replication lag!`);
    }
  });
}

5. 备份策略

  • 定期备份数据
  • 测试备份的可恢复性
  • 使用延迟从节点作为时间点恢复选项

故障排除

常见问题

javascript
// 检查副本集状态
rs.printSlaveReplicationInfo()  // 查看复制延迟

// 重新同步节点
// 1. 停止mongod进程
// 2. 删除数据目录内容
// 3. 重新启动mongod
// 4. 节点会自动重新同步

// 强制重新同步
db.adminCommand({resync: 1})  // 在从节点上执行

扩展到分片集群

从副本集到分片集群

javascript
// 副本集可以作为分片集群的分片
// 每个分片通常是一个副本集
sh.addShard("shard1ReplSet/mongodb1:27017,mongodb2:27017,mongodb3:27017")

总结

副本集是MongoDB高可用性的核心组件,通过主从架构、自动故障转移和数据冗余提供:

  1. 高可用性:自动故障转移确保服务连续性
  2. 数据冗余:多节点存储防止数据丢失
  3. 读扩展:从节点分担读取负载
  4. 灾难恢复:延迟从节点提供时间点恢复能力

正确配置和管理副本集对于生产环境的稳定运行至关重要。需要根据业务需求合理规划节点数量、网络拓扑和监控策略。