MongoDB 副本集部署实战:三节点高可用与读写分离口径

MongoDB 的高可用标准形态就是副本集:一组 mongod 进程维护同一份数据,自动选举、自动切换,应用只认一个连接串。本文以 MongoDB 7.x、三台 Rocky Linux 9 节点(mongo-01 / mongo-02 / mongo-03)部署一个三数据节点的副本集为例,讲清配置、初始化、状态读法与切换演练,并把读写分离的口径和副本集最常见的三个坑一次说明白。选三数据节点而不是「两数据 + 一仲裁」,是因为仲裁者不存数据、切换余地小,只适合预算极紧的场景,本文默认用生产上更稳妥的形态。

模型速览:选举、心跳与 oplog

副本集里只有一个 PRIMARY 接受写入,其余为 SECONDARY,通过复制主节点 oplog(操作日志)来同步数据。成员之间每 2 秒互发心跳,多数派(majority)是生死线:三节点挂掉一个还能选主,挂掉两个就只剩只读甚至不可用。oplog 存在 local 库里、大小固定、循环覆盖——它决定了从节点最多能落后多久还追得回来,这个「窗口」是副本集调优的核心概念,部署时就要规划好(见常见问题)。带 priority 的成员优先当选,priority 为 0 的成员永不参选,常用于异地容灾节点。

前置条件

  • 三台节点已安装同版本 mongod,数据目录 /data/mongo、日志目录规划在数据盘;
  • 27017 端口三台互通,主机名可解析,时间同步完成;
  • 已规划副本集名称(本文用 rs0)与管理员口令的保管方式。

第一步:mongod.conf 关键配置

三台节点的配置除 bindIp 里写各自地址外保持一致。先不开认证把副本集组起来、再统一加认证,是排障成本最低的顺序(认证与 keyfile 一旦顺序错了,日志会非常难读):

storage:
  dbPath: /data/mongo
  wiredTiger:
    engineConfig:
      # 缓存取物理内存的约一半即可,其余留给系统页缓存
      cacheSizeGB: 8
systemLog:
  destination: file
  path: /data/mongo/logs/mongod.log
  logAppend: true
net:
  port: 27017
  bindIp: 127.0.0.1,192.0.2.11
processManagement:
  fork: true
replication:
  replSetName: rs0
  # oplog 大小(MB):按写入量规划,窗口至少覆盖一次全量同步+故障处理时长
  oplogSizeMB: 10240
mkdir -p /data/mongo/logs
chown -R mongod:mongod /data/mongo
mongod -f /etc/mongod.conf
# 三台逐台启动,此时各节点还是独立状态,尚未组成员关系

第二步:初始化副本集

只在 mongo-01 上用 mongosh 执行初始化,把三台声明为成员。_id 与 replSetName 必须与配置文件里的一致:

mongosh --host mongo-01 --eval '
rs.initiate({
  _id: "rs0",
  members: [
    { _id: 0, host: "mongo-01:27017" },
    { _id: 1, host: "mongo-02:27017" },
    { _id: 2, host: "mongo-03:27017" }
  ]
})'

执行后等十几秒,mongo-01 的提示符应变成 rs0 [primary]。随后立即创建管理员并开启认证:三台都生成同一份 keyfile(权限必须 400,所有者为 mongod),在配置里加上 security.keyFile 与 authorization: enabled 后滚动重启,最后在主节点建 root 角色管理员。注意 keyfile 内容三台必须逐字节一致,且重启期间不要动成员配置。

验证:rs.status 看哪些字段

mongosh --host mongo-01 -u admin -p '请替换为实际口令' --eval 'rs.status()'

输出很长,巡检时只盯五处:每个成员的 stateStr(应是 1 个 PRIMARY + 2 个 SECONDARY);health 全为 1;各成员 optimeDate 与主节点的差距(即复制延迟,正常秒级以内);myState 所在节点的 electionDate 是否近期有异常变化(频繁变说明在反复选举);以及整体的 ok: 1。再补一个从节点可读验证:在任一 SECONDARY 上执行 db.getMongo().setReadPref('secondary') 后查询刚写入的数据,能读到即同步链路通畅。

故障切换演练

上线前必须做一次真演练:业务低峰时在主节点执行 rs.stepDown() 主动让位,观察其余两节点在 10 秒量级内选出新主、应用连接串自动跟随、写入短暂报错后恢复。再做一次狠的:直接停掉主节点的 mongod 进程,验证同样的行为,并记录从故障发生到应用恢复写入的实测时长——这个数字就是你对业务承诺的 RTO,要写进交付文档,而不是拍脑袋估计。

连接串与读写分离的口径

mongodb://appuser:口令@mongo-01:27017,mongo-02:27017,mongo-03:27017/appdb?replicaSet=rs0&readPreference=primary

连接串里 replicaSet 参数必写,三个种子地址写全,驱动靠它发现成员变更。readPreference 的取舍:默认 primary,一致性最稳;把读流量分到从节点要用 secondaryPreferred,但必须接受复制延迟带来的「刚写完查不到」——下单、支付这类写后即读的链路绝不能走从读,报表、列表这类容忍秒级延迟的场景才适合。顺带记住,单连接串只连一个 mongos/副本集入口的思想在驱动里是自动完成的,应用侧不需要自己维护成员列表。

备份的边界

一句话交代:副本集是高可用不是备份,误删数据会忠实地同步到所有成员。备份用 mongodump 定期逻辑导出(适合中小数据量与单库恢复),大数据量用文件系统快照或官方 Ops Manager/Cloud Manager 的备份方案;两条路线都要做恢复演练,没恢复过的不算数。

常见问题

1. 新成员长期卡在 STARTUP2

STARTUP2 是初始同步状态,卡住不动通常是数据量大且 oplog 窗口不够——同步还没拷完,主节点上对应的 oplog 已经被覆盖,只能从头再来,如此循环。先看该成员日志里 resync 的次数,再对照主节点 rs.printReplicationInfo() 的 oplog 时间跨度;治法是放大 oplogSizeMB 后重新同步,并尽量在业务低峰执行初始同步。

2. oplog 窗口不足的风险怎么提前发现

把「oplog 覆盖时长」做成监控项:窗口时长 = oplog 首尾条目的时间差,要求它大于「一次全量同步耗时 + 预留故障处理时间」,一般不低于 24 小时。写入量上涨后窗口会悄悄变短,必须定期复核,这是副本集最容易被忽视的慢性病。

3. 仲裁者节点的适用与误用

仲裁者(arbiter)只投票、不存数据、几乎不耗资源,适合「两个数据中心 + 一个轻量仲裁点」这类预算受限的跨机房形态。误用有二:一是拿仲裁者凑三节点省机器,结果任一数据节点故障后剩下的从节点没有完整数据保障、切换后性能减半;二是给仲裁者设 priority 期望它当主——它永远不能当主。能用三数据节点就不要用仲裁者。

4. 频繁重新选举

先看是不是心跳被网络抖动或大 GC 卡断:查三台之间的 ping 延迟、mongod 日志里的选举原因与 GC 停顿。机器配置差异大时,把弱机的 priority 调低,让选举结果稳定落在强节点上,不要三台同优先级来回横跳。

小结

副本集的功夫在部署之外:成员与票数按多数派原则规划,oplog 窗口按同步耗时留足余量并纳入监控,切换一定要在上线前真演练并记录时长,读写分离只给能容忍延迟的场景用。把这四件事做扎实,三节点副本集就是 MongoDB 最可靠的形态,没有之一。

发表回复

后才能评论