资讯详情

MongoDB 手动主从切换全流程实战:rs.stepDown 演练指南

📅 2026/10/19 5:41:12 | 华诺云谱 👁 阅读
MongoDB 手动主从切换全流程实战:rs.stepDown 演练指南
1. 什么场景逼着你手动切换不只是机器要挂那么简单去年某天凌晨两点值班手机把我在睡梦中吵醒。某核心业务库的 primary 所在宿主机磁盘剩余空间不到 2%再过半小时可能就写不进去了。业务方在群里说绝对不能停必须在十分钟内把主切到另外一台已经准备好的从节点上。等我打开运维文档发现手动主从切换那一节只有一句“执行 rs.stepDown()”。当时对着 mongosh 敲命令的时候心里是真没底。从那以后我就把 MongoDB 手动主从切换列入了必须定期演练的清单今天这篇内容就是那几次演练沉淀下来的完整过程。很多人觉得 MongoDB 复制集的自动故障转移已经解决了高可用问题手动切换根本用不上。这个想法大错特错。自动选举解决的是“节点进程挂了”的场景但运维中有一大堆情况是进程没挂、心跳正常、自动选举永远不会触发可你就是得把主切走。下面列几个我实际遇到的场景。1.1 磁盘告警与硬件维护最常见的切换触发条件宿主机磁盘空间告警是头号原因。日志涨得快、慢查询日志、备份文件、mongod 的 journal任何一个把磁盘塞满写操作就会开始报错。磁盘快满的时候 MongoDB 不会自动把 primary 让给别人它只是在那边硬扛直到写不进去业务雪崩。同样的情况还有宿主机内存 ECC 报错、CPU 型号不一致导致性能差异过大、网卡固件要升级、内核要打补丁。这些统统不会触发自动选举只能手动切。还有一种很容易被忽略的情况机房割接。你所在公司可能用某家云厂商也可能自建机房不管哪种只要底层网络设备要维护、机柜要迁移、供电要改造都意味着某个节点要下电。下电前必须把 primary 位置挪走否则等你把机器关了才发现集群没有主那真是叫天天不应。1.2 让主节点回到更适合的位置资源水位与角色错位还有一种常见情况是角色错位。比如你新采购了一台高性能机器或者某台从节点所在宿主机特别空闲你希望 primary 落在资源更充裕的节点上。虽然 MongoDB 可以通过设置 priority 来自动促成这种结果但 priority 机制生效需要一次选举而正常运行时除非当前主挂掉或退位否则不会重新选。这时候你依然需要手动 stepDown 一次让集群重新选举把主“弹”到目标节点。如果你拖着不处理高性能节点空转还无所谓真正的问题是低性能节点因为承载写入而持续高负载早晚拖垮整个集群。1.3 从 primary 上赶走异常流量应用层故障隔离还有一个我遇到过的场景某个应用侧发了 bug写入流量翻了几十倍把 primary 的 CPU 和 IO 全部打满。这时候开发在紧急修复业务又不能停我们就得先手动把 primary 切到另一台负载较低的从节点上。注意stepDown 并不是负载均衡器切换之后异常流量会跟着打到新 primary 上所以这只能算缓兵之计真正解决问题的还是把应用的异常写入挡住。但它确实能给从节点之间争取一些喘息时间尤其在你想快速恢复只读能力的时候非常管用。1.4 自动故障转移的盲区为什么高可用不等于高可用演练最关键的认知是MongoDB 自动故障转移依赖心跳和选举但很多“看起来不正常但进程没死”的情况它根本不会自动处理。比如 primary 节点网络延迟飙升到几百毫秒但心跳仍然能通过比如磁盘 IO 等待时间很长写入已经慢到不可接受但进程还活着比如出现死锁或长事务把某个关键操作卡死但节点状态仍是 PRIMARY。这些场景下自动选举不会触发必须有预案、有脚本、有人敢在凌晨执行这一步。高可用系统的价值恰恰在于你能在它需要切换的时候知道切换会发生什么、会丢多少数据、对应用造成多大影响。这些只能靠演练来验证靠文档凭空想象是没有用的。2. 演练前吃透 rs.stepDown()它到底替你做了什么事2.1 stepDown 的三段式执行过程rs.stepDown() 并非简单地把节点降级了事它背后有一套协调流程。以命令rs.stepDown(60, 15)为例整段执行大致分为三个阶段。第一阶段当前 primary 会停止接受新的写入在客户端视角里它开始表现为“即将退位”。但这和进程被杀不一样它不会立刻放弃自己的主身份而是先检查集群中是否有从节点的 oplog 已经追平到自己的最新位置。第二阶段是等待从节点追平。如果发现还没有从节点追平它会启动一个等待计时也就是第二个参数secondaryCatchUpPeriodSecs控制的窗口。在这个窗口内从节点会尽力追赶 primary 的 oplog追得越接近后续切换的数据丢失窗口就越小。如果从节点在窗口内成功追上切换会相对平滑如果没追上窗口时间一到它仍然会执行降级。第三阶段是触发选举。当前节点广播自己已经变成 SECONDARY复制集成员重新发起 leader 选举获得多数派票数的新节点成为 PRIMARY。而原 primary 在第一个参数seconds指定的时间内不能再次参与选举这个冷却期就是为了防止它刚退下来又被立刻选回去导致集群角色来回抖动。2.2 两个参数到底怎么设置seconds指退位冷却时间默认为 60 秒。运维演练时我一般设 60 到 120 秒。如果设置太短比如 10 秒可能出现切换完成后集群刚稳定原主又参与选举并胜出造成二次切换。这个过程中应用的连接池会再次经历一次拓扑刷新两波抖动叠加起来业务侧会很痛苦。secondaryCatchUpPeriodSecs指等待从节点追平 oplog 的时间上限默认是 10 秒。建议演练时设 15 秒左右。这个值不是越大越好因为如果从节点本身复制延迟已经很大等再久也追不上反而拖延整个切换的完成时间。10 到 15 秒对于常规健康集群已经足够。我整理了一个参考表方便后续演练时快速决策。参数默认值推荐演练值备注seconds6060 - 120冷却期越长避免反复切回secondaryCatchUpPeriodSecs1010 - 15给从节点追 oplog 的时间2.3 与 kill -9 自动故障转移的本质区别很多人觉得手动切换和直接杀死 primary 进程是一样的反正都会触发选举。差别非常大。直接 kill 掉 primary集群立刻进入无主状态从节点们会马上发起选举但新上任的 primary 的 oplog 位置可能落后于旧 primary旧节点上最近写入的一部分数据可能在新主上不存在。而rs.stepDown()给了一次优雅切换的机会它在退位之前先让从节点尽量追平 oplog。虽然这不保证数据绝对不丢但至少把丢失窗口压到了最小。真正决定数据丢不丢的其实是 writeConcern。如果应用写入时用的是writeConcern: majority那这条数据必须被多数节点确认才返回切换时多数节点里至少有一个持有该数据所以不会丢。如果应用用的是w1甚至不关心写确认那最近的一部分写入只存在于旧 primary 内存和 oplog 里无论优雅 stepDown 还是 kill切换后都可能消失这是很多业务埋下的雷平时看不出来一切换就炸。所以我一直强调演练前先检查应用的写入确认级别别等到切完再去找数据。2.4 一个容易忽略的细节priority 和回切stepDown 的冷却期结束后原 primary 会恢复参选资格。如果它的 priority 还是默认的 1而且它的 oplog 是最新的那么在下一轮选举中它很可能会再次胜出变成 primary。这意味着如果你执行完 stepDown 后没有及时处理集群可能“自动回切”到你头上。演练时如果只想让某个节点暂时退出主角色更稳妥的做法是临时把它的 priority 改成 0等维护完成后再改回来。但注意修改 priority 本身可能触发选举顺序上必须先确认目标节点已经追平 oplog再操作。3. 完整演练过程预检、切换、验证、回切的一个完整闭环3.1 演练前的预检清单磨刀不误砍柴工。我每次演练前都要过一遍检查项任何一个不满足就直接取消演练绝不硬上。第一复制集至少要有三个数据节点或者两个数据节点加一个仲裁节点。无论如何要保证多数派可用因为 stepDown 之后必须能选出新主。如果一个从节点已经挂了这时候不要做演练先把挂的节点修复或剔除。第二用rs.status()查看每个成员的状态。重点关注health是否等于 1stateStr是否是 PRIMARY 或 SECONDARY以及各节点的optimeDate差距。我在 mongosh 里常用下面这段脚本快速检查// 在 mongosh 中对复制集任一节点执行 rs.status().members.forEach(m { print(m.name, m.stateStr, health:, m.health, lagMs:, Date.now() - (m.optimeDate ? m.optimeDate.getTime() : 0)); });第三检查 oplog 窗口和复制延迟。用rs.printReplicationInfo()看 oplog 总量和时间窗口用rs.printSecondaryReplicationInfo()看从节点复制延迟正常情况下应该是 0 秒或非常接近 0。如果从节点落后太多先暂停演练排查复制延迟原因。第四检查应用的 MongoDB 连接串。必须确认 URI 是完整的三节点副本集形式类似mongodb://node1:27017,node2:27017,node3:27017/?replicaSetrs0retryWritestrue并且驱动版本支持自动拓扑刷新。如果发现连接串只写了单节点地址演练前必须先让应用修改配置否则切完应用必挂。第五确认业务处于低峰期。虽然 MongoDB 复制集设计上能承受切换但任何切换都有抖动窗口选在低峰期演练既是对业务负责也方便从容处理意外。3.2 执行 stepDown命令的正确姿势与预期输出预检通过后登录当前主节点先执行rs.isMaster()确认角色再执行切换。我习惯用完整的 admin 命令格式便于日后写脚本// 连接当前 primary db.adminCommand({ replSetStepDown: 60, secondaryCatchUpPeriodSecs: 15 });执行这条命令后mongosh 通常会卡住一小会儿时间取决于从节点追平速度然后会返回一个对象常见输出是{ok: 1}。接着你在这个会话里再执行rs.isMaster()会发现自己已经变成 SECONDARY。有些版本下命令执行期间连接会短暂断开这也是正常的因为服务端已经把自己降级了。我建议在另一台终端上同时执行监控命令实时观察集群状态变化// 在任一从节点上循环观察 while (true) { const s rs.status(); const primary s.members.find(m m.stateStr PRIMARY); print(new Date().toISOString(), primary ? primary.name : NO PRIMARY); sleep(1000); }切换完成后新 primary 会在几秒内出现。正常情况下整个选主过程在 10 秒内完成加上 catch-up 等待整体应该在 20 秒左右。如果超过 30 秒还没有新主说明有异常先看rs.status()里各节点的stateStr和lastHeartbeatMessage。3.3 切换后的业务验证清单角色切过去不代表演练成功业务验证才是关键。我会按顺序做下面几件事。第一在应用服务器上用驱动自带的健康检查接口确认能够写读。最简单的方式是直接跑一条 insert 和 find确认新主可写、从节点可读。如果有多个从节点也要轮流检查一遍连通性。第二看应用日志和监控告警。重点看有没有连接异常报错、超时、NotPrimary 之类的关键字。驱动侧如果配置了 retryWrites切换瞬间的写失败会自动重试账面上可能无感但日志里可能有重试记录这很正常。第三观察新 primary 的资源水位。CPU、内存、磁盘 IO、连接数都要看确认新主能够扛住现有流量。如果新主是老旧机器配置明显低于原来的主那这个切换可能不适合长期维持。第四让切换后的状态稳定运行至少 10 到 20 分钟再决定是否回切。如果演练目标是“切走并保持”那就保留新主把原主降为从节点观察。如果只是验证切换能力完成后要执行回切。3.4 把主切回去回切也要按流程来回切操作本质上是再执行一次 stepDown只不过方向相反。比如现在新 primary 是 node2你想把主还给 node1就登录 node2 执行同样的命令db.adminCommand({ replSetStepDown: 60, secondaryCatchUpPeriodSecs: 15 });前提是 node1 已经追平 oplog并且 priority 没有被改成 0。回切完成后同样要走一遍业务验证流程。这里有个容易踩的坑回切太急。如果刚切完不到几分钟就立刻回切集群会在短时间内经历两次切换业务侧的连接池和客户端拓扑缓存会连续抖动一些老驱动甚至会因此进入不健康状态。我的做法是两次切换之间至少间隔 10 分钟让连接池完全稳定下来再说。下面是我每次演练都会打印的一份检查清单表格你可以直接拿去改。阶段检查项预期结果切换前复制集多数派可用至少 3 个健康节点切换前从节点复制延迟0 秒或接近 0切换前应用连接串包含 replicaSet 和 retryWrites切换中stepDown 返回值ok: 1切换后新 primary 出现时间30 秒内切换后业务写读验证成功切换后应用日志无持续报错回切前稳定运行观察至少 10 分钟回切后再次业务验证成功4. 我在演练中踩过的坑排查链路和补救方法4.1 应用连接池不刷新持续报 NotPrimary我遇到过最典型的问题集群本身切得好好的新 primary 也确认了但某个应用服务一直报NotPrimaryNoSecondaryOk或NotWritablePrimary错误日志里刷屏。第一反应会觉得是 MongoDB 的问题其实问题出在应用侧连接串。排查链路是这样的。先确认集群状态用rs.isMaster()在应用服务器上验证新主地址可达。然后看应用日志发现它一直在尝试连接旧 primary 的 IP。最后翻驱动配置发现 URI 写死成了mongodb://old_primary_ip:27017/db既没带replicaSet参数也没带上其他节点地址。这种配置下驱动压根不知道这是一个副本集只会傻傻地连一个节点切换之后当然连不上。解决办法是把 URI 改成完整的副本集连接串加上replicaSetrs0retryWritestrue重新发布应用。排查过程中最耗时的其实是定位因为一开始大家都在怀疑 MongoDB 选主出了问题。所以现在我把“验证应用连接串”列进了演练前预检清单的第一优先级宁可多花五分钟检查也不想等切换完再被业务方找上门。4.2 stepDown 之后集群短暂没有主投票配置和 priority 的问题另一次演练中我执行 stepDown 后集群竟然整整 40 秒没有出现新主。当时第一反应是选举被卡住了立刻值班查看rs.status()结果发现一个非常隐蔽的配置问题复制集里一个数据节点的priority被设成了 0另一个是hidden: true而我在集群里总共只有两个数据节点加一个仲裁节点。表面看有三个投票成员实际上真正的候选者只有一个而那个候选者追 oplog 花了点时间加上选举超时配置偏大整体拉长了无主时间。排查链路是先看rs.status()的每个成员stateStr再看配置里priority和votes字段最后对比选举相关的settings.electionTimeoutMillis。解决办法是把候选节点的 priority 调成正常值确认配置后重新选举。这一步给我的教训是复制集成员的 priority 和 votes 配置不能想当然尤其经历过其他人调整后必须每次演练前都验证一遍。4.3 长事务或慢写拖住 stepDown命令长时间不返回还有一次执行 stepDown 时命令迟迟不返回像是卡死了一样。当时第一直觉是网络问题后来在另一个会话里运行db.currentOp()发现当前 primary 上有一个长时间运行的聚合操作一直占用着资源。stepDown 过程中需要等节点达到一个相对安静的状态这个慢操作导致降级流程迟迟走不完。排查链路是这样的新开一个 mongosh 连接执行db.currentOp()过滤出长时间运行的写操作和事务结合appName或client字段确认是哪个应用发起的操作。确认后和业务沟通或者直接db.killOp(opid)终止该操作。kill 之后再次执行 stepDown命令立刻返回切换顺利完成。这里必须提醒一句killOp是双刃剑杀错业务操作可能造成数据不一致或业务报错。所以遇到这种情况我优先联系业务方确认操作是否重要实在联系不上才会在风险评估后再做决定。演练前先看看 currentOp能提前避免这个麻烦。4.4 单节点或二节点复制集上执行 stepDown把自己玩没了这个坑发生在一次测试环境里。某同事在一套单节点复制集上执行了rs.stepDown()结果整个复制集没有 primary集群直接不可写。单节点复制集本来就没有任何人能参与选举stepDown 之后没有任何节点能顶上来必须人工干预才能把主“救”回来。二节点复制集也一样除非有仲裁节点否则 stepDown 后也大概率选不出主因为多数派票数凑不够。正确的做法是测试环境最差也要用“两个数据节点加一个仲裁节点”的结构生产环境至少三节点。演练前用rs.status()数一下健康节点数少于三直接取消操作。这个坑一旦踩了恢复过程非常难看甚至可能要去底层手动修改 local 库的 replication 配置风险极高。4.5 seconds 设太短导致的反复弹回有一段时间我把 seconds 参数设成 10 秒做演练想着快点结束。结果第一次切换完成后原主在冷却期一过立刻重新参与选举而且因为它数据最新、priority 又一样顺利再次当选。于是集群在不到一分钟内又切了回去应用侧经历了两次切换产生了大量连接重连告警。后来我把 seconds 固定为至少 60 秒回切时至少间隔 10 分钟以上再操作这个反复弹回的问题就再也没有出现过。5. 让演练成为常规动作复盘模板和自动化雏形5.1 每次演练都要有记录复盘模板比命令更值钱演练的意义不只是练一次手而是通过每次演练发现周边配套系统的问题。我建议每次演练后都要填写一张简单的复盘表至少包含以下字段。字段示例演练日期2024-xx-xx 02:00演练目的验证 stepDown 流程与监控告警执行人值班 DBA切换前 primarynode1切换后 primarynode2是否回切是 / 否无主窗口时长约 8 秒业务影响无 / 少量重试应用日志异常无 / 连接重试发现的问题连接串未配置 retryWrites改进项应用侧修改 URI 并重发这张表的价值在于半年后你再看到某次切换时发现“无主窗口 40 秒”就能立刻回忆起是 priority 配置问题而不是临时查一堆日志。复盘记录是运维团队最宝贵的资产。5.2 演练频率和触发节点我建议至少每季度做一次完整的手动切换演练并且在以下几种情况下强制加练一次复制集拓扑变更后、MongoDB 大版本升级后、机房或网络架构调整后、应用连接串统一改造后。每次演练都选在业务低峰期提前和业务方、开发团队同步时间窗口争取大家的配合。5.3 自动化切换脚本的思路与边界说到自动化我的态度是“半自动”就好不建议全自动。原因很简单stepDown 本身只是一条命令但切换前后的预检、验证、告警确认都需要人来判断。如果盲目把整个流程自动化一旦漏掉某个前置条件比如从节点落后、priority 配置异常、连接串单点就是生产事故。可以做的是把固定、机械的部分包装成脚本。比如切换命令的封装#!/bin/bash # 传入当前 primary 的地址执行 stepDown 并输出结果 PRIMARY_HOST$1 mongosh mongodb://${PRIMARY_HOST}:27017/admin --quiet \ --eval const res db.adminCommand({replSetStepDown: 60, secondaryCatchUpPeriodSecs: 15}); printjson(res);再比如切换后的角色确认脚本// 等待新 primary 出现最多等 60 秒 const deadline Date.now() 60000; let found false; while (Date.now() deadline) { const s rs.status(); const p s.members.find(m m.stateStr PRIMARY); if (p) { print(New PRIMARY:, p.name); found true; break; } sleep(1000); } if (!found) { print(ERROR: no PRIMARY within 60s); }这两个脚本组合起来基本覆盖了“切换 验证”的主干。但前置预检、业务验证、告警确认这些环节我仍然坚持人工把关。5.4 最后分享一点个人经验演练的次数多了之后我发现真正能反映团队运维水平的不是 stepDown 命令敲得有多熟练而是切换前后那一整套配套动作是否完整预检是否做足、监控是否覆盖、连接池是否能感知拓扑变化、回切是否克制、复盘是否到位。每次演练完我都会把发现的问题修复掉而不是记录完就丢到一边。下次演练前再翻一遍上一次的复盘表很多坑就不会再踩第二遍。如果你所在的团队还没有做过一次完整的手动主从切换演练建议从今天开始补上至少先从测试环境完整走一遍流程。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑