3. 从零搭建三节点副本集
本节把前面学到的副本集概念落到一台机器(或三台机器)上,用配置文件方式启动三个 mongod 实例,组成一个名为 rs0 的三节点副本集,并通过 rs.initiate()、rs.add() 和 rs.status() 完成初始化、扩容与状态验证。相比命令行参数启动,配置文件方式更适合生产环境:参数集中、可版本管理、便于批量部署。
一、规划节点与目录
三节点副本集的最小可用拓扑是「一主两从」,其中两个从节点都持有完整数据副本,任意一个节点宕机都不影响读写。为了在一台机器上演示,我们用不同端口区分三个实例:
| 角色 | 主机:端口 | 数据目录 | 日志文件 |
|---|---|---|---|
| 主候选节点 A | 127.0.0.1:27017 | /data/rs0/node1 | /data/rs0/node1/mongod.log |
| 从节点 B | 127.0.0.1:27018 | /data/rs0/node2 | /data/rs0/node2/mongod.log |
| 从节点 C | 127.0.0.1:27019 | /data/rs0/node3 | /data/rs0/node3/mongod.log |
真实生产环境应把三个节点分散到三台物理机或三个可用区,避免单机故障导致多数派丢失。这里用单机多端口只是为了教学演示,副本集的行为与多机部署完全一致。
二、编写 mongod 配置文件
每个实例使用一份独立的 YAML 配置文件。以节点 A 为例,/etc/mongod-rs0-1.conf 内容如下:
storage:
dbPath: /data/rs0/node1
journal:
enabled: true
systemLog:
destination: file
path: /data/rs0/node1/mongod.log
logAppend: true
net:
bindIp: 127.0.0.1
port: 27017
replication:
replSetName: rs0
processManagement:
fork: true
关键字段说明:replication.replSetName 必须三个节点完全一致,否则无法加入同一副本集;storage.dbPath 与 systemLog.path 各节点必须不同;processManagement.fork: true 让 mongod 以守护进程方式运行。节点 B、C 只需把端口改为 27018/27019、目录改为 node2/node3 即可。
创建目录并启动三个实例:
mkdir -p /data/rs0/node{1,2,3}
mongod -f /etc/mongod-rs0-1.conf
mongod -f /etc/mongod-rs0-2.conf
mongod -f /etc/mongod-rs0-3.conf
此时三个实例各自独立运行,尚未组成副本集。用 mongosh --port 27017 连接节点 A,执行 rs.status() 会返回 NotYetInitialized,这是正常的。
三、初始化副本集并添加节点
在节点 A 上执行初始化。这里显式传入 _id 和 members,一次性把三个节点都写进配置,比先 initiate 再逐个 add 更简洁:
rs.initiate({
_id: "rs0",
members: [
{ _id: 0, host: "127.0.0.1:27017" },
{ _id: 1, host: "127.0.0.1:27018" },
{ _id: 2, host: "127.0.0.1:27019" }
]
})
返回 { ok: 1 } 表示配置已写入。如果只想先建单节点再扩容,可以执行 rs.initiate()(使用默认配置),随后用 rs.add("127.0.0.1:27018") 和 rs.add("127.0.0.1:27019") 逐个加入。两种方式最终配置等价,区别在于前者一次成型、后者便于观察节点加入过程。
初始化后需要等待几秒到几十秒,让节点完成选举和数据同步。此时在节点 A 上执行 rs.status(),可以看到 myState 为 1(PRIMARY),另外两个节点 stateStr 为 SECONDARY。
四、验证主从同步
验证同步最直接的方式是写入后到从节点读取。在 PRIMARY 上插入一条数据:
use testdb
db.users.insertOne({ name: "alice", age: 30 })
然后在从节点 B 上连接并查询。注意从节点默认不允许读,需要先开启从节点读权限:
mongosh --port 27018
rs.slaveOk() // 旧版本写法
// 新版本使用:
db.getMongo().setReadPref("secondary")
db.users.find()
如果能看到 alice 这条记录,说明主从复制链路正常。也可以在主节点执行 db.users.getDB().stats() 或查看 rs.printReplicationInfo() 观察 oplog 时间窗口,用 rs.printSecondaryReplicationInfo() 查看各从节点落后主节点多少秒。
五、rs.status() 输出解读
rs.status() 是排查副本集问题最重要的命令,返回结构包含 set、myState、members 等字段。下面这张图把关键字段与含义对应起来,便于快速定位问题:
解读时按以下顺序看:先看 ok 是否为 1,再看 myState 确认当前节点角色,最后逐个检查 members 中每个节点的 health 和 stateStr。如果某个节点 health 为 0,说明心跳失败,通常是网络不通或进程已退出;如果 stateStr 长期停留在 STARTUP2,说明正在做初始同步,数据量大时需要耐心等待。
六、常见问题与排查
- 初始化报 "already initialized":说明该实例已经属于某个副本集,需要先删除 dbPath 下的 local 数据库或使用新目录重新启动。
- 节点一直处于 STARTUP2:检查主节点 oplog 是否足够大,从节点能否拉取到全量数据;网络带宽不足也会导致同步缓慢。
- rs.add() 后节点不可达:确认
bindIp是否包含目标地址,副本集成员之间必须能互相访问配置中的 host 地址。 - 选举频繁切换:检查节点间网络延迟和心跳超时设置,默认
heartbeatTimeoutSecs为 10 秒。
keyFile 开启内部认证,否则任何能连上端口的客户端都可以加入副本集,存在安全风险。