第十章:高可用与故障转移
最后更新: 2024-01-01
作者: MongoDB Team
页面目录
第十章:高可用与故障转移
MongoDB 高可用架构设计与故障处理
10.1 高可用概述
┌─────────────────────────────────────────────────────────────────┐
│ 高可用架构层级 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────┐│
│ │ 应用层高可用 ││
│ │ - 多个应用实例 ││
│ │ - 负载均衡 ││
│ └─────────────────────────────────────────────────────────────┘│
│ ↓ │
│ ┌─────────────────────────────────────────────────────────────┐│
│ │ 路由层高可用 (mongos) ││
│ │ - 多个 mongos 实例 ││
│ │ - 客户端连接池 ││
│ └─────────────────────────────────────────────────────────────┘│
│ ↓ │
│ ┌─────────────────────────────────────────────────────────────┐│
│ │ 数据层高可用 ││
│ │ - Config Server 副本集 ││
│ │ - Shard 副本集 ││
│ │ - 自动故障转移 ││
│ └─────────────────────────────────────────────────────────────┘│
│ │
└─────────────────────────────────────────────────────────────────┘
10.2 多副本集架构
跨数据中心部署
// 跨地域副本集配置
rs.initiate({
_id: "geo-replica-set",
members: [
// 区域 A (主数据中心)
{
_id: 0,
host: "node-a1:27017",
priority: 3,
tags: { region: "region-a", dc: "dc1" }
},
{
_id: 1,
host: "node-a2:27017",
priority: 2,
tags: { region: "region-a", dc: "dc1" }
},
// 区域 B (灾备中心)
{
_id: 2,
host: "node-b1:27017",
priority: 1,
tags: { region: "region-b", dc: "dc2" }
},
{
_id: 3,
host: "node-b2:27017",
priority: 1,
tags: { region: "region-b", dc: "dc2" }
},
// 仲裁节点
{
_id: 4,
host: "node-arbiter:27017",
arbiterOnly: true,
tags: { dc: "dc1" }
}
]
})
标签感知写入
// 确保写入到主数据中心
db.orders.insertOne(
{ data: "critical" },
{ writeConcern: { w: "region-a" } }
)
// 确保数据写入多个区域
db.orders.insertOne(
{ data: "important" },
{ writeConcern: { w: { region: { $in: ["region-a", "region-b"] } } } }
)
10.3 故障检测与恢复
心跳配置
// 配置心跳超时
cfg = rs.conf()
cfg.settings = {
heartbeatTimeoutSecs: 10, // 默认 10 秒
heartbeatIntervalMillis: 2000 // 默认 2 秒
}
rs.reconfig(cfg)
// 查看心跳状态
rs.status().members.forEach(m => {
print(`Member: ${m.name}`)
print(` State: ${m.stateStr}`)
print(` Health: ${m.health}`)
print(` Last Heartbeat: ${m.lastHeartbeat}`)
})
故障转移流程
┌─────────────────────────────────────────────────────────────────┐
│ 主节点故障转移流程 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 1. 心跳超时 │
│ Secondary 检测到 Primary 心跳超时 │
│ │
│ 2. 发起选举 │
│ 符合条件的 Secondary 发起选举请求 │
│ │
│ 3. 投票过程 │
│ - 候选节点请求投票 │
│ - 其他节点投票 │
│ - 获得多数票的节点成为新 Primary │
│ │
│ 4. 故障转移 │
│ - 新 Primary 处理写入 │
│ - 客户端自动重连 │
│ │
│ 选举条件: │
│ - 候选节点数据最新 │
│ - 优先级足够高 │
│ - 能够获得多数投票 │
│ │
└─────────────────────────────────────────────────────────────────┘
选举配置
// 配置选举参数
cfg = rs.conf()
cfg.settings = {
electionTimeoutMillis: 15000, // 选举超时
heartbeatTimeoutSecs: 10,
catchUpTimeoutMillis: -1, // -1 表示无限等待
catchUpTimeoutMillis: 30000 // 或设置具体时间
}
rs.reconfig(cfg)
// 强制选举
rs.freeze(0) // 解除冻结
rs.stepDown(60) // 主节点降级,等待 60 秒后可能重新成为主节点
10.4 变更流(Change Streams)
基本使用
// 监视集合变更
const cursor = db.orders.watch()
// 监视副本集变更
const cursor = db.orders.watch([], { fullDocument: "updateLookup" })
// 监视数据库变更
const cursor = db.watch({ $db: "mydb" })
// 监视集群变更(需要 MongoDB 5.0+)
const cursor = db.adminCommand({ watch: 1 })
变更流配置
// 只监视插入操作
db.orders.watch([], {
fullDocument: "updateLookup",
fullDocumentBeforeChange: "whenAvailable"
})
// 监视特定类型的操作
db.orders.watch([
{
$match: {
operationType: {
$in: ["insert", "update", "replace", "delete"]
}
}
}
])
// 使用管道过滤
db.orders.watch([
{
$match: {
"fullDocument.status": "completed"
}
}
])
// 恢复令牌
const resumeToken = cursor.getResumeToken()
const newCursor = db.orders.watch([], { resumeAfter: resumeToken })
变更流应用
// Node.js 应用示例
const { MongoClient } = require('mongodb');
async function watchChanges() {
const client = new MongoClient('mongodb://localhost:27017');
await client.connect();
const collection = client.db('mydb').collection('orders');
const changeStream = collection.watch();
changeStream.on('change', (change) => {
console.log('变更类型:', change.operationType);
console.log('文档键:', change.documentKey);
console.log('完整文档:', change.fullDocument);
// 根据变更类型处理
switch (change.operationType) {
case 'insert':
handleInsert(change.fullDocument);
break;
case 'update':
handleUpdate(change.fullDocument);
break;
case 'delete':
handleDelete(change.documentKey);
break;
}
});
changeStream.on('error', (err) => {
console.error('Change stream error:', err);
});
}
// 关闭连接
// changeStream.close();
10.5 自动重连
MongoDB Driver 配置
// Node.js Driver 配置
const client = new MongoClient('mongodb://mongos1:27017,mongos2:27017', {
replicaSet: 'rs0',
retryWrites: true,
retryReads: true,
maxPoolSize: 100,
minPoolSize: 10,
serverSelectionTimeoutMS: 30000,
connectTimeoutMS: 10000,
socketTimeoutMS: 45000
})
// Python Driver 配置
from pymongo import MongoClient
client = MongoClient(
'mongodb://mongos1:27017,mongos2:27017',
replicaSet='rs0',
retryWrites=True,
retryReads=True,
serverSelectionTimeoutMS=30000,
connectTimeoutMS=10000,
socketTimeoutMS=45000
)
// Java Driver 配置
MongoClientURI uri = new MongoClientURI(
"mongodb://mongos1:27017,mongos2:27017/?replicaSet=rs0"
);
MongoClient client = new MongoClient(uri);
重试配置
// Retryable Writes (MongoDB 3.6+)
// 自动重试的单文档写入操作
// - insertOne, updateOne, deleteOne, replaceOne
// 启用/禁用重试
db.adminCommand({ setParameter: 1, supportsRetryableWrites: true })
// 自定义重试参数
db.adminCommand({
setParameter: 1,
transactionLifetimeLimitSeconds: 300
})
// Retryable Reads (MongoDB 4.2+)
// 自动重试的读取操作
// - find, aggregate, findOne, distinct
10.6 故障恢复
数据恢复流程
// 1. 检查副本集状态
rs.status()
// 2. 恢复故障节点
// Docker 场景
docker restart mongo1
// 物理机场景
sudo systemctl start mongod
// 3. 等待同步完成
rs.status() // 查看 stateStr
// 4. 如果同步失败,手动修复
// 查看同步状态
db.adminCommand({ replSetGetStatus: 1 })
// 强制重新同步
db.adminCommand({ resync: "mongo2" })
oplog 恢复
// 1. 查看 oplog 状态
use local
db.oplog.rs.stats()
// 2. 查找最近的操作
db.oplog.rs.find().sort({ ts: -1 }).limit(1)
// 3. 应用 oplog 到新节点
// 在新节点上
db.adminCommand({
applyOps: db.getSiblingDB("local").oplog.rs.find().toArray()
})
// 4. 检查同步延迟
db.adminCommand({ replSetGetStatus: 1 }).members.forEach(m => {
print(m.name + ": " + m.stateStr)
if (m.optime) {
print(" optime: " + m.optimeDate)
}
})
灾备切换
// 演练灾备切换
// 1. 停止主节点
docker stop mongo1
// 2. 观察副本集状态
rs.status()
// 3. 验证应用连接
// 检查应用是否连接到新主节点
// 4. 恢复原主节点
docker start mongo1
// 5. 观察同步
rs.status()
10.7 监控告警
关键监控指标
| 指标 | 说明 | 阈值建议 |
|---|---|---|
| 主从延迟 | 复制延迟时间 | > 10s 告警 |
| 节点健康 | 心跳状态 | 非 healthy 告警 |
| 磁盘使用 | 磁盘空间使用率 | > 80% 告警 |
| 连接数 | 当前连接数 | > 80% 最大值 |
| 操作延迟 | 平均操作时间 | > 100ms 告警 |
| 队列长度 | 等待操作数 | > 100 告警 |
监控脚本
// health-check.js
function checkReplicaSetHealth() {
const status = rs.status();
const health = {
ok: status.ok === 1,
primary: null,
secondaries: [],
issues: []
};
status.members.forEach(m => {
if (m.stateStr === 'PRIMARY') {
health.primary = m.name;
} else if (m.stateStr === 'SECONDARY') {
health.secondaries.push(m.name);
}
if (m.health !== 1) {
health.issues.push(`${m.name} is unhealthy`);
}
if (m.stateStr !== 'ARBITER' && m.optimeDate) {
const lag = new Date() - m.optimeDate;
if (lag > 10000) {
health.issues.push(`${m.name} lag: ${lag}ms`);
}
}
});
if (!health.primary) {
health.issues.push('No primary detected');
}
return health;
}
printjson(checkReplicaSetHealth());
💡 实践提示
- 副本集成员数:使用奇数成员(3、5、7)避免脑裂
- 监控复制延迟:关注主从数据同步状态
- 合理设置优先级:确保高配置节点优先成为主节点
- 测试故障转移:定期进行故障转移演练
- 使用变更流:实现实时数据同步和处理
📚 继续学习