深入了解MongoDB 两地三中心架构

一、概述

在企业级数据库架构中,"两地三中心" 是一种经典的容灾部署模式------即在同一城市的两个数据中心(同城双活)加一个异地灾备中心,构成三层防护体系。

对于 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 可能是主数据中心故障

八、备注

  1. Oplog 大小:生产环境建议 ≥ 磁盘容量的 5%,确保有充足的回放窗口
  2. 认证与授权 :务必开启 --auth 并使用 keyfile 进行副本集内部认证
  3. 网络隔离:异地中心之间建议使用 VPN 或专线
  4. 备份策略:异地灾备中心不应替代备份------仍需独立的备份方案(mongodump / 文件系统快照)
  5. 定期演练:至少每季度执行一次完整的容灾切换演练

九、总结

要点 说明
架构模式 2(DC1)+ 2(DC2)+ 1(DC3)= 5 节点
优先级分布 DC1 高优先级 → 避免不必要的跨城切换
多数派原理 5 节点 majority = 3,可容忍 2 节点故障
网络依赖 同城需低延迟,跨城可容忍高延迟
异地节点 可设置 secondaryDelaySecs 防止误删同步
极限场景 双中心同时宕机需手动 force 恢复

MongoDB 副本集的"两地三中心"架构是经过大量生产环境验证的成熟方案,合理配置优先级和 Write Concern,可以在数据安全与写入性能之间取得最佳平衡。

相关推荐
贵慜_Derek1 小时前
《从零实现 Agent 系统》连载 29|多 Agent 研究 Harness:Lead、Worker 与 Spawn
人工智能·架构·agent
代码雕刻家2 小时前
1.24.MySQL-idea中连接MySQL的基本操作
数据库·mysql·intellij-idea
Yue1682 小时前
球球了主人,请把我当傻子调教吧系列之MongoDB数据库
mongodb
毛骗导演2 小时前
Tool Boundary:如何让大模型永远不知道也不会泄露用户敏感数据
前端·架构
炘爚2 小时前
MySQL——事务和隔离级别
数据库·mysql
DeboPXK2 小时前
NSK VH25EM 高防尘法兰型导轨技术手册
服务器·网络·数据库·经验分享·规格说明书
“码”力全开2 小时前
解耦异构设备:基于 Docker 与边缘计算的 GB28181/RTSP 统一流媒体平台架构演进(全源码交付)
docker·架构·边缘计算
翼龙云_cloud2 小时前
阿里云国际代理商:如何使用RDS MySQL 构建网站数据库?
数据库·mysql·阿里云
程序猿乐锅2 小时前
【 苍穹外卖day03 | 菜品管理 】
java·开发语言·数据库·mysql