第八章:副本集配置

最后更新: 2024-01-01 作者: MongoDB Team
页面目录

第八章:副本集配置

构建 MongoDB 高可用副本集架构

8.1 副本集概述

┌─────────────────────────────────────────────────────────────────┐
│                      MongoDB 副本集架构                          │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│                         ┌─────────────┐                         │
│                         │  Primary     │                        │
│                         │ (主节点)      │                        │
│                         │  写入/读取    │                        │
│                         └──────┬──────┘                         │
│                                │                                │
│                    ┌───────────┼───────────┐                   │
│                    │           │           │                    │
│              ┌──────▼──────┐ ┌──▼───────┐ ┌─▼───────┐          │
│              │ Secondary1  │ │Secondary2│ │ Arbiter │          │
│              │ (从节点1)   │ │ (从节点2) │ │ (仲裁节点)│          │
│              │  热备       │ │  读写分离  │ │ 投票     │          │
│              └─────────────┘ └──────────┘ └─────────┘          │
│                                                                 │
│  写入 ────────────────────────────────────────▶ Primary         │
│                         ↓ 复制                                   │
│  读取 ◀──────────────────────────────────────── Secondary(s)     │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

副本集组件

组件 说明 投票 数据复制
Primary 主节点,处理所有写入 -
Secondary 从节点,复制主节点数据
Arbiter 仲裁节点,只参与投票

8.2 副本集成员角色

角色 说明 优先级
Primary 主节点,接受写入操作 > 0
Secondary 从节点,复制主节点数据 ≥ 0
Arbiter 仲裁节点,不存储数据,只投票 = 0

8.3 创建副本集

单机模拟多节点副本集

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

# 启动第一个节点
mongod --replSet rs0 --port 27017 --dbpath /data/rs0/rs0-1 --bind_ip localhost

# 启动第二个节点
mongod --replSet rs0 --port 27018 --dbpath /data/rs0/rs0-2 --bind_ip localhost

# 启动第三个节点(仲裁节点)
mongod --replSet rs0 --port 27019 --dbpath /data/rs0/rs0-3 --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", arbiterOnly: true }
  ]
})

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

// 查看配置
rs.conf()

8.4 Docker Compose 部署副本集

# docker-compose.yml
version: '3.8'

services:
  mongo1:
    image: mongo:7.0
    container_name: mongo1
    command: mongod --replSet rs0 --port 27017
    volumes:
      - mongo1_data:/data/db
    ports:
      - "27017:27017"
    environment:
      - MONGO_INITDB_ROOT_USERNAME=admin
      - MONGO_INITDB_ROOT_PASSWORD=password

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

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

  mongo-arbiter:
    image: mongo:7.0
    container_name: mongo-arbiter
    command: mongod --replSet rs0 --port 27017
    volumes:
      - mongo-arbiter_data:/data/db
    ports:
      - "27020:27017"

volumes:
  mongo1_data:
  mongo2_data:
  mongo3_data:
  mongo-arbiter_data:
# 启动服务
docker-compose up -d

# 初始化副本集
docker exec -it mongo1 mongosh -u admin -p password --eval '
  rs.initiate({
    _id: "rs0",
    members: [
      { _id: 0, host: "mongo1:27017" },
      { _id: 1, host: "mongo2:27017" },
      { _id: 2, host: "mongo3:27017" },
      { _id: 3, host: "mongo-arbiter:27017", arbiterOnly: true }
    ]
  })
'

8.5 副本集配置

副本集配置对象

// 获取当前配置
const config = rs.conf()

// 修改配置
config.members[0].priority = 2  // 提高第一个节点的优先级
config.members[1].priority = 1
config.members[2].priority = 1
config.members[3].arbiterOnly = true

// 应用配置
rs.reconfig(config)

// 完整配置示例
rs.initiate({
  _id: "rs0",
  protocolVersion: 1,
  members: [
    {
      _id: 0,
      host: "mongo1:27017",
      priority: 2,
      votes: 1
    },
    {
      _id: 1,
      host: "mongo2:27017",
      priority: 1,
      votes: 1
    },
    {
      _id: 2,
      host: "mongo3:27017",
      priority: 1,
      votes: 1
    },
    {
      _id: 3,
      host: "mongo-arbiter:27017",
      arbiterOnly: true,
      votes: 1
    }
  ],
  settings: {
    chainingAllowed: true,
    heartbeatTimeoutSecs: 10,
    electionTimeoutMillis: 10000,
    catchUpTimeoutMillis: -1
  }
})

成员优先级

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

// 隐藏成员(不推荐为主,但可用于备份)
cfg.members[2].hidden = true
cfg.members[2].priority = 0
rs.reconfig(cfg)

// 延迟成员(用于灾难恢复)
cfg.members[2].priority = 0
cfg.members[2].slaveDelay = 3600  // 延迟 1 小时
rs.reconfig(cfg)

投票成员配置

// 设置投票数
cfg = rs.conf()
cfg.members[0].votes = 1
cfg.members[1].votes = 0  // 非投票成员
cfg.members[2].votes = 0
rs.reconfig(cfg)

// 注意:votes=0 的成员 priority 也必须为 0

8.6 副本集操作

成员管理

// 添加成员
rs.add("mongo4:27017")
rs.add({ host: "mongo4:27017", priority: 1 })

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

// 移除成员
rs.remove("mongo4:27017")

// 强制移除成员
rs.remove("mongo4:27017", { force: true })

副本集状态

// 查看状态
rs.status()

// 查看详细状态
rs.status({ full: true })

// 查看主节点
rs.isMaster()
db.isMaster()

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

// 查看所有成员
rs.member("_id")

故障转移

// 模拟主节点故障
// 在主节点上执行
db.adminCommand({ shutdown: 1 })

// 强制重新选举
rs.freeze(30000)  // 冻结 30 秒,阻止成为主节点
rs.freeze(0)      // 解除冻结

// 强制重新选举
rs.stepDown(60)  // 降级为主节点,等待 60 秒

8.7 读写偏好

读取偏好模式

// 默认:主节点读取
db.getMongo().setReadPref("primary")

// 主节点优先
db.getMongo().setReadPref("primaryPreferred")

// 从节点优先
db.getMongo().setReadPref("secondaryPreferred")

// 总是从节点读取(可能读取到旧数据)
db.getMongo().setReadPref("secondary")

// 最近节点(需要 MongoDB 5.0+)
db.getMongo().setReadPref("nearest")

标签集

// 定义标签
cfg = rs.conf()
cfg.members[0].tags = { "datacenter": "dc1", "region": "us-east" }
cfg.members[1].tags = { "datacenter": "dc2", "region": "us-west" }
cfg.members[2].tags = { "datacenter": "dc1", "region": "us-east" }
rs.reconfig(cfg)

// 使用标签读取
db.getMongo().setReadPref(
  "secondaryPreferred",
  [{ "region": "us-east" }]
)

// 查询时指定读取偏好
db.orders.find({ status: "completed" })
  .readPref("secondary", [{ "datacenter": "dc1" }])

8.8 复制配置

Oplog

// 查看 Oplog 状态
rs.printReplicationInfo()

// 查看 Oplog 大小
rs.printSlaveReplicationInfo()

// 自定义 Oplog 大小(需要在副本集初始化前设置)
// 创建 Oplog 集合
use local
db.createCollection("oplog.rs", {
  capped: true,
  size: 10737418240  // 10GB
})

复制延迟

// 查看复制延迟
db.getSiblingDB("admin").runCommand({ replSetGetStatus: 1 })
// 或
rs.status()

// 成员状态中的 "optimeDate" 和 "lastHeartbeat" 可以计算延迟

// 设置最大复制延迟(写入确认)
db.getMongo().setWriteConcern({ w: "majority", wtimeout: 5000 })

8.9 写入关注

写入关注级别

// 默认写入关注(仅主节点确认)
db.collection.insertOne({ data: "value" })

// w: 1 - 主节点确认
db.collection.insertOne(
  { data: "value" },
  { writeConcern: { w: 1 } }
)

// w: "majority" - 大多数节点确认
db.collection.insertOne(
  { data: "value" },
  { writeConcern: { w: "majority" } }
)

// w: "all" - 所有节点确认
db.collection.insertOne(
  { data: "value" },
  { writeConcern: { w: "all" } }
)

// j: true - 写入 journal
db.collection.insertOne(
  { data: "value" },
  { writeConcern: { w: "majority", j: true } }
)

// wtimeout - 超时时间
db.collection.insertOne(
  { data: "value" },
  { writeConcern: { w: "majority", wtimeout: 5000 } }
)

读取和写入关注结合

// 使用 majority 读取和写入
db.getMongo().setReadConcern("majority")
db.getMongo().setWriteConcern("majority")

// 事务中的读取和写入关注
session.startTransaction({
  readConcern: { level: "majority" },
  writeConcern: { w: "majority" }
})

💡 实践提示

  1. 仲裁节点使用:在偶数成员时使用仲裁节点,避免脑裂
  2. 副本集成员数:建议奇数成员(3、5、7)
  3. 优先级设置:将高配置节点设为高优先级
  4. 读写分离:读取压力大时使用 secondaryPreferred
  5. 监控复制延迟:关注 oplog 同步状态

📚 继续学习