第八章:副本集配置
最后更新: 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" }
})
💡 实践提示
- 仲裁节点使用:在偶数成员时使用仲裁节点,避免脑裂
- 副本集成员数:建议奇数成员(3、5、7)
- 优先级设置:将高配置节点设为高优先级
- 读写分离:读取压力大时使用 secondaryPreferred
- 监控复制延迟:关注 oplog 同步状态
📚 继续学习