Appearance
MongoDB 副本集
副本集(Replica Set)是MongoDB实现高可用性和数据冗余的核心机制。副本集由一组MongoDB实例组成,它们维护相同的数据集,提供自动故障转移和数据恢复功能。
副本集基础概念
什么是副本集
副本集是一组维护相同数据集的mongod进程。副本集提供冗余和高可用性,是生产环境中推荐的部署方式。
副本集组件
- 主节点(Primary):处理所有写操作
- 从节点(Secondary):复制主节点的数据
- 仲裁节点(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高可用性的核心组件,通过主从架构、自动故障转移和数据冗余提供:
- 高可用性:自动故障转移确保服务连续性
- 数据冗余:多节点存储防止数据丢失
- 读扩展:从节点分担读取负载
- 灾难恢复:延迟从节点提供时间点恢复能力
正确配置和管理副本集对于生产环境的稳定运行至关重要。需要根据业务需求合理规划节点数量、网络拓扑和监控策略。