跳转至

第七章:复制集

复制集概述

复制集(Replica Set)是 MongoDB 实现高可用的核心机制,通过数据冗余和自动故障转移保证服务可用性。

架构图

┌─────────────────────────────────────────────────────────────┐
│                      Replica Set                            │
│                                                              │
│  ┌─────────────┐    ┌─────────────┐    ┌─────────────┐     │
│  │   Primary   │───▶│  Secondary  │───▶│  Secondary  │     │
│  │   (主节点)   │◀───│   (从节点)   │◀───│   (从节点)   │     │
│  │  Port: 27017│    │  Port: 27018│    │  Port: 27019│     │
│  └─────────────┘    └─────────────┘    └─────────────┘     │
│         │                  │                  │             │
│         └──────────────────┴──────────────────┘             │
│                      数据复制                                │
│                                                              │
│  ┌─────────────┐                                            │
│  │   Arbiter   │  ← 仲裁节点(可选)                         │
│  │  (投票用)   │                                            │
│  └─────────────┘                                            │
└─────────────────────────────────────────────────────────────┘

节点角色

角色 说明
Primary 主节点,处理所有写操作
Secondary 从节点,复制主节点数据
Arbiter 仲裁节点,只投票不存储数据
Hidden 隐藏节点,对客户端不可见
Delayed 延迟节点,延迟复制数据

部署复制集

单机多实例部署

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

# 启动三个实例
mongod --replSet rs0 --port 27017 --dbpath /data/rs0-0 --bind_ip localhost
mongod --replSet rs0 --port 27018 --dbpath /data/rs0-1 --bind_ip localhost
mongod --replSet rs0 --port 27019 --dbpath /data/rs0-2 --bind_ip localhost

初始化复制集

// 连接到主节点
mongosh --port 27017

// 初始化配置
rs.initiate({
    _id: "rs0",
    members: [
        { _id: 0, host: "localhost:27017" },
        { _id: 1, host: "localhost:27018" },
        { _id: 2, host: "localhost:27019" }
    ]
})

// 查看状态
rs.status()

配置文件方式

# /etc/mongod.conf (节点1)
storage:
  dbPath: /data/rs0-0

net:
  port: 27017
  bindIp: localhost

replication:
  replSetName: rs0
  oplogSizeMB: 1024

security:
  keyFile: /etc/mongodb/keyfile

Docker Compose 部署

version: '3.8'

services:
  mongo1:
    image: mongo:7.0
    container_name: mongo1
    ports:
      - "27017:27017"
    command: mongod --replSet rs0
    volumes:
      - mongo1_data:/data/db

  mongo2:
    image: mongo:7.0
    container_name: mongo2
    ports:
      - "27018:27017"
    command: mongod --replSet rs0
    volumes:
      - mongo2_data:/data/db

  mongo3:
    image: mongo:7.0
    container_name: mongo3
    ports:
      - "27019:27017"
    command: mongod --replSet rs0
    volumes:
      - mongo3_data:/data/db

volumes:
  mongo1_data:
  mongo2_data:
  mongo3_data:

复制集管理

查看状态

// 复制集状态
rs.status()

// 简洁输出
rs.status().members.forEach(m => print(m.name, m.stateStr))

// 查看配置
rs.conf()

// 查看是否为主节点
rs.isMaster()

添加/删除节点

// 添加节点
rs.add("host4:27017")

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

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

// 删除节点
rs.remove("host4:27017")

修改配置

// 获取当前配置
var cfg = rs.conf()

// 修改优先级
cfg.members[0].priority = 2
cfg.members[1].priority = 1

// 重新配置
rs.reconfig(cfg)

// 强制重新配置(无主节点时)
rs.reconfig(cfg, { force: true })

主节点切换

// 步降主节点
rs.stepDown(60)  // 60 秒内不能成为主节点

// 冻结节点
rs.freeze(120)   // 120 秒内不能参与选举

// 强制指定主节点
cfg = rs.conf()
cfg.members[1].priority = 10  // 提高优先级
rs.reconfig(cfg)

数据复制机制

Oplog

Oplog(操作日志)是复制集的核心:

// 查看 oplog 大小
db.printReplicationInfo()

// 查看 oplog 内容
use local
db.oplog.rs.find().sort({$natural: -1}).limit(5)

// oplog 结构
{
    "ts": Timestamp(1704067200, 1),  // 时间戳
    "t": NumberLong(1),              // term
    "h": NumberLong("123456789"),    // hash
    "v": 2,                          // 版本
    "op": "i",                       // 操作类型: i=insert, u=update, d=delete
    "ns": "mydb.users",              // 命名空间
    "o": { ... }                     // 操作文档
}

复制过程

Primary                          Secondary
   │                                 │
   │  1. 写入 Oplog                  │
   │──────▶                          │
   │                                 │
   │  2. 返回写入确认                 │
   │◀──────                          │
   │                                 │
   │  3. Secondary 轮询 Oplog        │
   │◀────────────────────────────────│
   │                                 │
   │  4. 返回新操作                   │
   │────────────────────────────────▶│
   │                                 │
   │                    5. 应用操作   │
   │                                 │

复制延迟

// 查看复制延迟
rs.printSlaveReplicationInfo()

// 输出示例
// source: host2:27017
//     syncedTo: Fri Jan 01 2024 10:00:00 GMT+0800
//     0 secs (0 hrs) behind the primary

选举机制

选举触发条件

  1. 复制集初始化
  2. 主节点不可用
  3. 主节点步降
  4. 新节点加入

选举过程

1. 检测主节点不可用
2. 从节点发起选举
3. 获得多数票成为主节点
4. 其他节点同步新主节点

投票规则

  • 需要获得多数票(N/2 + 1)
  • 奇数节点最佳(3、5、7)
  • 仲裁节点只投票不存储数据

优先级

// 设置优先级
cfg = rs.conf()
cfg.members[0].priority = 5   // 更可能成为主节点
cfg.members[1].priority = 1
cfg.members[2].priority = 0   // 永远不会成为主节点
rs.reconfig(cfg)

读偏好

读偏好模式

模式 说明
primary 只从主节点读(默认)
primaryPreferred 优先主节点,不可用时读从节点
secondary 只从从节点读
secondaryPreferred 优先从节点,无可用时读主节点
nearest 读网络延迟最低的节点

连接设置

// 连接字符串
mongodb://host1:27017,host2:27017,host3:27017/?readPreference=secondaryPreferred

// 驱动设置
const client = new MongoClient(uri, {
    readPreference: 'secondaryPreferred'
});

查询级别

// 查询时指定
db.users.find({}).readPref('secondary')

// 聚合时指定
db.orders.aggregate([...]).readPref('secondary')

写关注

写关注级别

// w: 1 - 默认,主节点确认
db.users.insertOne({...}, { w: 1 })

// w: "majority" - 多数节点确认
db.users.insertOne({...}, { w: "majority" })

// w: 2 - 指定节点数确认
db.users.insertOne({...}, { w: 2 })

// w: "customTag" - 指定标签节点确认
db.users.insertOne({...}, { w: "eastCoast" })

// j: true - 等待日志落盘
db.users.insertOne({...}, { w: "majority", j: true })

// wtimeout - 超时时间
db.users.insertOne({...}, { 
    w: "majority", 
    wtimeout: 5000 
})

故障转移

自动故障转移

Primary 故障
Secondary 检测超时(默认 10 秒)
发起选举
新 Primary 选出
客户端自动重连

手动故障转移

// 主节点步降
rs.stepDown()

// 指定步降时间
rs.stepDown(120)  // 120 秒

安全配置

Keyfile 认证

# 生成 keyfile
openssl rand -base64 756 > /etc/mongodb/keyfile
chmod 400 /etc/mongodb/keyfile
chown mongodb:mongodb /etc/mongodb/keyfile
# mongod.conf
security:
  keyFile: /etc/mongodb/keyfile
  authorization: enabled

用户创建

// 创建管理员
use admin
db.createUser({
    user: "admin",
    pwd: "StrongPassword123!",
    roles: [
        { role: "userAdminAnyDatabase", db: "admin" },
        { role: "clusterAdmin", db: "admin" }
    ]
})

// 创建应用用户
use mydb
db.createUser({
    user: "appuser",
    pwd: "AppPassword123!",
    roles: [
        { role: "readWrite", db: "mydb" }
    ]
})

监控与维护

监控指标

// 复制集状态
rs.status()

// 复制延迟
rs.printSlaveReplicationInfo()

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

// Oplog 状态
db.printReplicationInfo()

常见问题

复制延迟过大

// 检查延迟原因
db.serverStatus().metrics.repl

// 增加从节点数量分散读压力
// 使用 secondaryPreferred 读偏好

主节点频繁切换

// 检查网络
// 调整心跳超时
cfg.settings.heartbeatTimeoutSecs = 20
rs.reconfig(cfg)

小结

本章学习了:

  • ✅ 复制集架构和节点角色
  • ✅ 部署复制集
  • ✅ 复制集管理
  • ✅ 数据复制机制
  • ✅ 选举机制
  • ✅ 读偏好和写关注
  • ✅ 故障转移
  • ✅ 安全配置

下一章

第八章:分片集群 - 学习 MongoDB 大规模分片集群架构。