
为什么你的 MongoDB 需要副本集
单机部署 MongoDB 最省事,但也最危险:一旦进程崩溃、系统重启或磁盘故障,数据库就彻底不可用,正在写入的数据也可能丢失。对面向用户的服务来说,几十分钟的数据库中断就意味着订单流失和信任受损。
副本集解决的就是「单点故障」问题。它把同一份数据分别保存在多个节点上,其中一个主节点负责全部写入,其余从节点同步保持一致;主节点一旦出问题,集群会自动把某个从节点提升为新主节点,读写几乎无感地切换。相比花钱买更贵的硬件,多配一两台普通机器组成副本集,往往能以更低的成本换来更可靠的可用性。
这篇实践指南会带你完成三件具体的事:在一组服务器上从零部署一个标准的 3 节点 MongoDB 副本集,验证自动故障转移是否真的生效,以及把读请求从主节点分流到从节点来降低压力。所有步骤都基于 MongoDB 6.0 的官方配置方式,你可以在 VPS 或独立服务器上按顺序复现。
部署前的准备:节点规划与版本选择
开始前先明确两点:副本集至少需要 3 个节点才能保证可用性,版本建议选择仍在官方支持窗口内的 MongoDB 6.0 或更新版本。这里以 3 台 Ubuntu 22.04 服务器、每台 2 核 4GB 内存为例,实际规模可以按读流量弹性调整。
节点分两类身份:一个 primary(主)负责写入,两个 secondary(从)负责同步和读取。为了避免同机故障一次性拖垮多个角色,三个节点最好分布在不同的物理机或可用区,而不是塞在同一台机器上。域名解析可选,这里直接用内网 IP 相互通信,端口统一使用 MongoDB 默认的 27017。
在三个节点上分别安装 MongoDB,操作一致。先导入官方公钥,再把官方软件源写入到 /etc/apt/sources.list.d/mongodb-org-6.0.list,然后执行更新与安装:
wget -qO - https://www.mongodb.org/static/pgp/server-6.0.asc | sudo apt-key add - echo "deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu jammy/mongodb-org/6.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-6.0.list sudo apt-get update sudo apt-get install -y mongodb-org
安装完成后先不要急着启动,接下来要改配置文件,把三个节点组成一个副本集。这个阶段最容易踩的坑是把所有节点都配成相同的副本集名却忘记同步初始节点,后续启动时会出现长时间处于 STARTUP 状态的现象,稍后我会演示如何排查。
初始化副本集:三节点怎么互相认识
副本集靠相同的副本集名(replica set name)绑定在一起,同时需要一个初始的节点作为「引路人」来发起选举。我们先在每台机器的配置文件中定义副本集名并绑定监听地址。
编辑 /etc/mongod.conf,把 net 与 replication 两段改成下面这样。bindIp 建议同时包含本机回环地址和本机内网 IP,否则客户端只能本机访问;replSetName 三个节点必须完全一致,这里统一用 rs0:
net: port: 27017 bindIp: 127.0.0.1,192.168.10.10 replication: replSetName: rs0
三台机器的 bindIp 里内网 IP 各填各自的(例如 192.168.10.10、192.168.10.11、192.168.10.12)。改完后分别在三个节点执行启动:
sudo systemctl enable mongod sudo systemctl start mongod sudo systemctl status mongod
只有 status 显示 active (running) 才继续下一步。接着在其中一个节点(比如 192.168.10.10)登录 mongo shell,执行初始化命令。这里用 _id 明确指定每个成员的优先级,priority: 2 意味着该节点在选举中更占优势,适合放在资源更强的机器上:
rs.initiate({
_id: "rs0",
members: [
{ _id: 0, host: "192.168.10.10:27017" },
{ _id: 1, host: "192.168.10.11:27017" },
{ _id: 2, host: "192.168.10.12:27017", priority: 2 }
]
})
返回 ok: 1 表示初始化成功。执行 rs.status() 能看到三个节点的状态,此时应出现一个 PRIMARY 和两个 SECONDARY。如果你看到某个节点长时间停留在 STARTUP2 或 ROLLBACK,通常是网络端口没打通,先用 telnet 192.168.10.12 27017 检查节点之间能否连通,并确认防火墙没有拦掉 27017 端口。

自动故障转移测试:真的能自己恢复吗
副本集最大的价值在于故障转移,不验证一下等于白搭。这一节我们在不影响业务的前提下做一次受控演练:手动停掉当前主节点,看从节点能否自动补位。
先在任意节点执行 rs.status() 找到当前 PRIMARY,然后到那台机器上停掉 mongod:
sudo systemctl stop mongod
主节点宕机后,剩余的两个从节点会经过一次选举,大约 10 到 30 秒内会有一个被提升为新的 PRIMARY。在存活节点上执行 rs.status(),并观察新增的 newPrimary 字段:
rs.status().lastStableCheckpointTimestamp rs.status().members.filter(m => m.stateStr === "PRIMARY")
如果新主节点正常出现,说明自动故障转移生效。再把刚才停掉的旧主节点拉起来,它会以 SECONDARY 身份重新加入集群并回放增量数据,不会抢占新主节点:
sudo systemctl start mongod
这里有一个容易被忽视的细节:故障转移期间仍会有一小段「无主」窗口,写入客户端如果设置了合理的写关注(write concern),就能等到新主节点确认后才返回成功,而不是丢失请求。生产环境建议把应用端的写关注配置为 majority,代价是写入略慢,但能换来更强的数据一致性。

读写分离实战:把读压力分流到从节点
副本集同步了三份数据,如果只让主节点干活,等于浪费从节点的计算资源。读写分离的做法是把写请求固定发给主节点,把可以容忍略微滞后的读请求(报表查询、后台统计、搜索类接口)发到从节点。
MongoDB 驱动层通过「读偏好」(read preference)控制这个行为。以 primaryPreferred 为例,客户端优先读主节点,主节点不可用时才退回到从节点;而 secondary 则明确要求只读从节点。Java 驱动的连接串写法如下:
mongodb://appuser:pwd@192.168.10.10:27017,192.168.10.11:27017,192.168.10.12:27017/?replicaSet=rs0&readPreference=secondary
Python 的 pymongo 可以用同样的方式指定连接串,并把 readPreference=secondaryPreferred 写入 URI。设置之后,用 rs.secondaryOk() 在 mongo shell 里检查延迟,确认从节点数据确实跟得上:
rs.secondaryOk()
db.collection.find().explain("executionStats")
从节点读取的代价是「最终一致性」:由于复制是异步的,从节点看到的数据可能比主节点晚几百毫秒。所以只把对时效性不敏感的业务切到从节点,订单金额校验、库存扣减这类强一致操作仍必须走主节点,否则会出现读到旧数据的线上事故。

把高可用和读分离落到你的业务里
副本集不是部署完就万事大吉,长期稳定还依赖持续巡检和备份。rs.status() 能反映每个节点的心跳与延迟,建议配合监控系统定时采集,一旦出现持续的同步延迟或某个节点长期脱离集群就及时告警;mongodump 配合定期全量备份,能保证即使发生灾难性故障也能从副本之外恢复数据。

查看 MySQL 主从复制与读写分离的对照实践 可以看到同类思路在关系型数据库里的落地区别;如果你的数据规模不大但追求运维省心,独立服务器与 VPS 分层的数据库缓存架构 提供了另一条更简单的路线。需要应对更高读并发时,Redis 哨兵集群与读写分离方案 也是不错的补充。
总结一下:三节点副本集用一套配置文件和一个 rs.initiate() 就能搭起来,故障转移是 Mongo 内建能力,不用自己写脑裂判断;读写分离能显著降低主节点压力,但要分清哪些读可以容忍延迟。如果你不想把精力花在服务器高可用细节上,Hostease 的 VPS 主机 与 独立服务器 都支持按需扩容,副本集各节点可以独立选型,把数据库高可用和主机运维更清晰地解耦。建议先从 3 节点的最小高可用起步,跑通故障转移后再逐步引入多从节点,用实测延迟来决定读分离的边界。