一、概述
在企业级数据库架构中,"两地三中心" 是一种经典的容灾部署模式------即在同一城市的两个数据中心(同城双活)加一个异地灾备中心,构成三层防护体系。
对于 MongoDB 而言,这是通过 副本集(Replica Set) 来实现的。
二、MongoDB 的容灾体系
2.1 容灾级别演进
MongoDB 提供了多层次的容灾能力,从低到高依次为:
#mermaid-svg-XyProj8NVFQ3H5FP{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-XyProj8NVFQ3H5FP .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-XyProj8NVFQ3H5FP .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-XyProj8NVFQ3H5FP .error-icon{fill:#552222;}#mermaid-svg-XyProj8NVFQ3H5FP .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-XyProj8NVFQ3H5FP .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-XyProj8NVFQ3H5FP .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-XyProj8NVFQ3H5FP .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-XyProj8NVFQ3H5FP .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-XyProj8NVFQ3H5FP .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-XyProj8NVFQ3H5FP .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-XyProj8NVFQ3H5FP .marker{fill:#333333;stroke:#333333;}#mermaid-svg-XyProj8NVFQ3H5FP .marker.cross{stroke:#333333;}#mermaid-svg-XyProj8NVFQ3H5FP svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-XyProj8NVFQ3H5FP p{margin:0;}#mermaid-svg-XyProj8NVFQ3H5FP .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-XyProj8NVFQ3H5FP .cluster-label text{fill:#333;}#mermaid-svg-XyProj8NVFQ3H5FP .cluster-label span{color:#333;}#mermaid-svg-XyProj8NVFQ3H5FP .cluster-label span p{background-color:transparent;}#mermaid-svg-XyProj8NVFQ3H5FP .label text,#mermaid-svg-XyProj8NVFQ3H5FP span{fill:#333;color:#333;}#mermaid-svg-XyProj8NVFQ3H5FP .node rect,#mermaid-svg-XyProj8NVFQ3H5FP .node circle,#mermaid-svg-XyProj8NVFQ3H5FP .node ellipse,#mermaid-svg-XyProj8NVFQ3H5FP .node polygon,#mermaid-svg-XyProj8NVFQ3H5FP .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-XyProj8NVFQ3H5FP .rough-node .label text,#mermaid-svg-XyProj8NVFQ3H5FP .node .label text,#mermaid-svg-XyProj8NVFQ3H5FP .image-shape .label,#mermaid-svg-XyProj8NVFQ3H5FP .icon-shape .label{text-anchor:middle;}#mermaid-svg-XyProj8NVFQ3H5FP .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-XyProj8NVFQ3H5FP .rough-node .label,#mermaid-svg-XyProj8NVFQ3H5FP .node .label,#mermaid-svg-XyProj8NVFQ3H5FP .image-shape .label,#mermaid-svg-XyProj8NVFQ3H5FP .icon-shape .label{text-align:center;}#mermaid-svg-XyProj8NVFQ3H5FP .node.clickable{cursor:pointer;}#mermaid-svg-XyProj8NVFQ3H5FP .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-XyProj8NVFQ3H5FP .arrowheadPath{fill:#333333;}#mermaid-svg-XyProj8NVFQ3H5FP .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-XyProj8NVFQ3H5FP .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-XyProj8NVFQ3H5FP .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-XyProj8NVFQ3H5FP .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-XyProj8NVFQ3H5FP .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-XyProj8NVFQ3H5FP .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-XyProj8NVFQ3H5FP .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-XyProj8NVFQ3H5FP .cluster text{fill:#333;}#mermaid-svg-XyProj8NVFQ3H5FP .cluster span{color:#333;}#mermaid-svg-XyProj8NVFQ3H5FP div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-XyProj8NVFQ3H5FP .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-XyProj8NVFQ3H5FP rect.text{fill:none;stroke-width:0;}#mermaid-svg-XyProj8NVFQ3H5FP .icon-shape,#mermaid-svg-XyProj8NVFQ3H5FP .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-XyProj8NVFQ3H5FP .icon-shape p,#mermaid-svg-XyProj8NVFQ3H5FP .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-XyProj8NVFQ3H5FP .icon-shape .label rect,#mermaid-svg-XyProj8NVFQ3H5FP .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-XyProj8NVFQ3H5FP .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-XyProj8NVFQ3H5FP .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-XyProj8NVFQ3H5FP :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 单节点
无容灾
副本集
同机房 HA
副本集
同城双中心
副本集
两地三中心
分片集群
多地域部署
| 级别 | 架构 | 容灾能力 | 适用场景 |
|---|---|---|---|
| L1 | 单节点 | 无 | 开发测试 |
| L2 | 副本集(同机房) | 单机故障自动切换 | 一般生产环境 |
| L3 | 副本集(同城双中心) | 单机房故障存活 | 核心业务 |
| L4 | 副本集(两地三中心) | 城市级灾难存活 | 金融/政务 |
| L5 | 分片集群 + 多地域 | 跨地域容灾 | 全球化业务 |
2.2 什么是两地三中心
┌──────────────────────────────────────────────────────┐
│ 城市A(主站点) │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ 数据中心 1 │ │ 数据中心 2 │ │
│ │ (主数据中心) │ │ (同城灾备中心) │ │
│ │ │ │ │ │
│ │ ┌───┐ ┌───┐ │ │ ┌───┐ ┌───┐ │ │
│ │ │P0 │ │P1 │ │ │ │S0 │ │S1 │ │ │
│ │ └───┘ └───┘ │ │ └───┘ └───┘ │ │
│ │ 高优先级节点 │ │ 普通优先级节点 │ │
│ └─────────────────┘ └─────────────────┘ │
│ │ │ │
│ │ 低延迟专用网络 │ │
│ └───────────────────────┘ │
└──────────────────────────────────────────────────────┘
│
│ 跨城网络
│
┌──────────────────────────────────────────────────────┐
│ 城市B(异地灾备) │
│ ┌─────────────────┐ │
│ │ 数据中心 3 │ │
│ │ (异地灾备中心) │ │
│ │ │ │
│ │ ┌───┐ │ │
│ │ │A0 │ │ ← 仅投票 / 延迟数据同步 │
│ │ └───┘ │ │
│ └─────────────────┘ │
└──────────────────────────────────────────────────────┘
三、两地三中心设计要点
3.1 节点数量
推荐采用 2 + 2 + 1 = 5 节点 的分布模式:
| 位置 | 节点数 | 角色 | 说明 |
|---|---|---|---|
| 主数据中心(DC1) | 2 | PRIMARY 候选 | 高优先级,承载读写 |
| 同城灾备中心(DC2) | 2 | SECONDARY | 普通优先级,同城同步 |
| 异地灾备中心(DC3) | 1 | SECONDARY(可设延迟) | 异地容灾,仅投票 |
为什么是 5 个节点? MongoDB 副本集选举需要多数派(majority),5 节点可容忍 2 个节点故障。如果只有 3 个节点,任意一个数据中心(2 节点)宕机就会失去多数派。
3.2 选举优先级
javascript
// 将主数据中心的节点优先级调高
// 主数据中心 DC1 节点
{ priority: 10 } // member1 --- 最优先当选 PRIMARY
{ priority: 5 } // member2
// 同城灾备 DC2 节点
{ priority: 1 } // member3
{ priority: 1 } // member4
// 异地灾备 DC3 节点
{ priority: 0.5 } // member5 --- 仅在紧急情况下当选
设置更高优先级的目的是 避免跨城切换 PRIMARY,因为异地网络延迟高,跨城选举 PRIMARY 会严重影响写入性能。
3.3 网络要求
| 路径 | 要求 | 原因 |
|---|---|---|
| DC1 ↔ DC2(同城) | 低延迟(< 5ms),高带宽 | writeConcern: majority 需要同城双写 |
| DC1/DC2 → DC3(跨城) | 容忍较高延迟 | 异地节点以异步复制为主 |
3.4 Write Concern 配置
javascript
// 推荐配置:数据写入需要多数节点确认
db.collection.insertOne(
{ data: "critical" },
{ writeConcern: { w: "majority", wtimeout: 5000 } }
)
在 2+2+1 架构中,majority = 3。这意味着每次写入必须至少同步到 3 个节点(DC1 + DC2 各至少一个)才算成功。
四、环境准备
4.1 拓扑规划
| 虚拟机 | IP | 部署节点 | 所属数据中心 |
|---|---|---|---|
| geekdemo1 | 192.168.1.1 | member1 (10001), member2 (10002) | DC1(主数据中心) |
| geekdemo2 | 192.168.1.2 | member3 (10003), member4 (10004) | DC2(同城灾备) |
| geekdemo3 | 192.168.1.3 | member5 (10005) | DC3(异地灾备) |
4.2 环境要求
- MongoDB 4.2+
- 3 台 Linux 虚拟机(CentOS 7 / Ubuntu 18.04+)
- 各节点间网络互通
五、完整部署步骤
步骤 1:配置域名解析
在每台虚拟机上配置 /etc/hosts,使节点可通过域名互相访问:
bash
# === 在 geekdemo1 (192.168.1.1) 上执行 ===
cat >> /etc/hosts << EOF
192.168.1.1 geekdemo1 member1.example.com member2.example.com
192.168.1.2 geekdemo2 member3.example.com member4.example.com
192.168.1.3 geekdemo3 member5.example.com
EOF
# === 在 geekdemo2 (192.168.1.2) 上执行 ===
cat >> /etc/hosts << EOF
192.168.1.1 geekdemo1 member1.example.com member2.example.com
192.168.1.2 geekdemo2 member3.example.com member4.example.com
192.168.1.3 geekdemo3 member5.example.com
EOF
# === 在 geekdemo3 (192.168.1.3) 上执行 ===
cat >> /etc/hosts << EOF
192.168.1.1 geekdemo1 member1.example.com member2.example.com
192.168.1.2 geekdemo2 member3.example.com member4.example.com
192.168.1.3 geekdemo3 member5.example.com
EOF
验证解析:
bash
# 在三台机器上互相 ping 测试
ping -c 2 member1.example.com
ping -c 2 member3.example.com
ping -c 2 member5.example.com
步骤 2:创建数据目录并启动 MongoDB 实例
bash
# ===== geekdemo1(DC1:启动 member1 和 member2)=====
mkdir -p ~/data/member1 ~/data/member2
mongod --dbpath ~/data/member1 \
--replSet demo \
--bind_ip 0.0.0.0 \
--port 10001 \
--fork \
--logpath ~/data/member1.log \
--oplogSize 1024
mongod --dbpath ~/data/member2 \
--replSet demo \
--bind_ip 0.0.0.0 \
--port 10002 \
--fork \
--logpath ~/data/member2.log \
--oplogSize 1024
# ===== geekdemo2(DC2:启动 member3 和 member4)=====
mkdir -p ~/data/member3 ~/data/member4
mongod --dbpath ~/data/member3 \
--replSet demo \
--bind_ip 0.0.0.0 \
--port 10003 \
--fork \
--logpath ~/data/member3.log \
--oplogSize 1024
mongod --dbpath ~/data/member4 \
--replSet demo \
--bind_ip 0.0.0.0 \
--port 10004 \
--fork \
--logpath ~/data/member4.log \
--oplogSize 1024
# ===== geekdemo3(DC3:启动 member5)=====
mkdir -p ~/data/member5
mongod --dbpath ~/data/member5 \
--replSet demo \
--bind_ip 0.0.0.0 \
--port 10005 \
--fork \
--logpath ~/data/member5.log \
--oplogSize 1024
参数说明:
| 参数 | 含义 |
|---|---|
--replSet demo |
副本集名称 |
--bind_ip 0.0.0.0 |
绑定所有网卡 |
--fork |
后台守护进程模式 |
--oplogSize 1024 |
oplog 大小(MB),生产环境建议更大 |
验证所有实例均已启动:
bash
# 在任意节点上测试连通性
mongo --eval "db.runCommand({ping:1})" member1.example.com:10001
mongo --eval "db.runCommand({ping:1})" member2.example.com:10002
mongo --eval "db.runCommand({ping:1})" member3.example.com:10003
mongo --eval "db.runCommand({ping:1})" member4.example.com:10004
mongo --eval "db.runCommand({ping:1})" member5.example.com:10005
步骤 3:初始化副本集
连接到 member1 并执行初始化:
javascript
// 连接到 member1
mongo member1.example.com:10001
// 初始化副本集
rs.initiate({
_id: "demo",
version: 1,
members: [
{ _id: 0, host: "member1.example.com:10001" },
{ _id: 1, host: "member2.example.com:10002" },
{ _id: 2, host: "member3.example.com:10003" },
{ _id: 3, host: "member4.example.com:10004" },
{ _id: 4, host: "member5.example.com:10005" }
]
})
查看副本集状态:
javascript
rs.status()
// 应看到 member1 被选举为 PRIMARY(_id:0 默认优先)
步骤 4:配置选举优先级
javascript
// 获取当前配置
var cfg = rs.conf()
// member1(DC1)--- 最高优先级
cfg.members[0].priority = 10
// member2(DC1)--- 次高优先级
cfg.members[1].priority = 5
// member3、member4(DC2)--- 保持默认 1
// cfg.members[2].priority = 1
// cfg.members[3].priority = 1
// member5(DC3)--- 更低优先级,仅兜底
cfg.members[4].priority = 0.5
// 应用配置
rs.reconfig(cfg)
验证优先级:
javascript
rs.conf().members.forEach(function(m) {
print("Host: " + m.host + ", Priority: " + m.priority)
})
步骤 5:配置异地节点延迟同步(可选)
如果希望异地节点刻意延迟同步,防止误操作或逻辑错误被即时同步到灾备中心:
javascript
var cfg = rs.conf()
// member5 延迟 1 小时同步
cfg.members[4].secondaryDelaySecs = 3600
rs.reconfig(cfg)
步骤 6:编写持续写入脚本
创建 ingest.js 用于模拟持续写入:
javascript
// ingest.js --- 每2秒插入一条记录,验证写入成功
db = db.getSiblingDB("test")
db.test.drop()
for (var i = 1; i <= 1000; i++) {
var result = db.test.insertOne({
item: i,
ts: new Date().getTime() / 1000
})
var inserted = db.test.findOne({ item: i })
if (inserted) {
print("✓ Item " + i + " inserted at " + new Date().toISOString())
} else {
print("✗ Unexpected failure at item " + i)
}
sleep(2000)
}
启动持续写入(使用副本集连接串以支持自动故障转移):
bash
mongo --retryWrites \
"mongodb://member1.example.com:10001,member2.example.com:10002,member3.example.com:10003,member4.example.com:10004,member5.example.com:10005/test?replicaSet=demo" \
ingest.js
--retryWrites确保在 PRIMARY 切换时,写入操作能自动重试。MongoDB 4.2+ 驱动默认启用,但命令行中显式声明更安全。
六、容灾演练
6.1 模拟从数据中心故障(DC2 宕机)
操作:停止 geekdemo2 上的所有 MongoDB 进程
bash
# 在 geekdemo2 上执行
pkill mongod
预期结果:
- 副本集仍有 3 个节点存活(member1, member2, member5),满足 majority(3/5)
- PRIMARY 不发生切换(member1 和 member2 仍在 DC1)
- 持续写入脚本不受影响
验证:
javascript
rs.status().members.forEach(function(m) {
print(m.name + " → " + m.stateStr)
})
// member3 和 member4 应显示为 (not reachable/health check failed)
6.2 模拟主数据中心故障(DC1 宕机)
操作:停止 geekdemo1 上的所有 MongoDB 进程
bash
# 在 geekdemo1 上执行
pkill mongod
预期结果:
- DC2 的 member3 或 member4 自动被选举为新 PRIMARY
- 写入脚本自动重试连接到新 PRIMARY
- 数据不丢失(已写入 majority 的数据得到保留)
验证:
javascript
rs.status().members.forEach(function(m) {
print(m.name + " → " + m.stateStr)
})
// member3 或 member4 应成为 PRIMARY
6.3 故障恢复
bash
# 在 geekdemo1 和 geekdemo2 上重新启动 mongod 进程
# 恢复后,由于 DC1 节点优先级更高,PRIMARY 会自动切回 DC1
6.4 极限测试:双中心同时故障
当 DC1 和 DC2 同时宕机,仅剩 DC3 的 member5:
- 存活节点数 1 < majority(3),副本集进入 只读状态
- 无法自动恢复,需要手动干预:
javascript
// 在 member5 上强制重新配置(紧急恢复用)
rs.reconfig({
_id: "demo",
version: 2,
members: [
{ _id: 4, host: "member5.example.com:10005" }
]
}, { force: true })
强制重配置是紧急手段,可能导致数据回滚,谨慎使用。
七、监控要点
7.1 关键监控指标
javascript
// 副本集状态概览
rs.status()
// 复制延迟
rs.printSecondaryReplicationStatus()
// Oplog 窗口(防止 oplog 被覆盖导致全量同步)
db.getReplicationInfo()
7.2 告警规则建议
| 指标 | 阈值 | 说明 |
|---|---|---|
| 复制延迟 | > 10s | 可能网络异常或从节点负载过高 |
| Oplog 窗口 | < 1h | 存在全量同步风险 |
| 节点存活数 | < 3(5节点集) | 即将丢失 majority |
| PRIMARY 位置 | 不在 DC1 | 可能是主数据中心故障 |
八、备注
- Oplog 大小:生产环境建议 ≥ 磁盘容量的 5%,确保有充足的回放窗口
- 认证与授权 :务必开启
--auth并使用keyfile进行副本集内部认证 - 网络隔离:异地中心之间建议使用 VPN 或专线
- 备份策略:异地灾备中心不应替代备份------仍需独立的备份方案(mongodump / 文件系统快照)
- 定期演练:至少每季度执行一次完整的容灾切换演练
九、总结
| 要点 | 说明 |
|---|---|
| 架构模式 | 2(DC1)+ 2(DC2)+ 1(DC3)= 5 节点 |
| 优先级分布 | DC1 高优先级 → 避免不必要的跨城切换 |
| 多数派原理 | 5 节点 majority = 3,可容忍 2 节点故障 |
| 网络依赖 | 同城需低延迟,跨城可容忍高延迟 |
| 异地节点 | 可设置 secondaryDelaySecs 防止误删同步 |
| 极限场景 | 双中心同时宕机需手动 force 恢复 |
MongoDB 副本集的"两地三中心"架构是经过大量生产环境验证的成熟方案,合理配置优先级和 Write Concern,可以在数据安全与写入性能之间取得最佳平衡。