MongoDB集群在线上跑起来之后,真正的挑战往往不是搭建,而是后续的运维。很多团队照着文档把副本集或分片集群搭好,一上线就遇到各种诡异问题:主从延迟飙升、内存莫名其妙占满、选举频繁导致服务抖动。这些问题背后通常不是MongoDB本身的缺陷,而是部署和运维时忽略了几个关键细节。

副本集成员数量必须为奇数,但原因不止是选举

几乎所有人都知道副本集成员数应该是奇数,但多数人只停留在“避免平票”这个层面。实际上,奇数配置更核心的价值在于稳定写入可用性。当你有一个3节点的副本集,挂掉1台,剩下2台依然可以选出主节点并正常写入。如果配置成4节点,挂掉1台后剩下3台,看似也能工作,但此时再挂1台,剩下2台就无法选举了。所以同样是容忍1台故障,3节点比4节点更经济,容错能力完全一样。真正需要关注的是,在生产环境中绝对不要让副本集成员数变成偶数,哪怕临时加一个只具备投票权的仲裁节点也要维持奇数。仲裁节点不存数据,资源消耗极小,用一台低配云主机甚至容器就能跑,但它能在关键时刻避免集群陷入只读状态。

优先级和隐藏节点的组合使用,是滚动维护的基石

很多人把副本集所有节点的priority都设为1,这样谁先启动谁就可能成为主节点,这在计划内维护时会很被动。正确的做法是,在集群中明确指定一个或两个节点的priority为2,其余保持1,这样主节点始终落在你期望的机器上。更关键的是,你需要至少保留一个priority为0且hidden为true的节点。这个隐藏节点不参与外部读请求,也不会被选举为主节点,但它是做全量备份、数据迁移、大查询分析的绝佳目标。当你需要对主节点进行内核升级或参数调整时,先把这个隐藏节点的priority临时调高,让它接替主节点角色,完成切换后再恢复原状,整个过程对业务几乎无感知。

Oplog大小直接决定你的恢复窗口期

Oplog是MongoDB副本集同步的核心机制,但默认的oplog大小通常只占磁盘空间的5%,对于写入密集的业务,这个大小可能只够存几分钟的操作记录。一旦某个从节点因为维护或网络抖动落后太久,oplog已经被覆盖,它就再也无法通过增量同步追上,只能做全量同步。全量同步会瞬间打满从节点的磁盘IO和网络带宽,甚至拖垮主节点。运维上必须根据业务写入量主动调整oplog大小。你可以通过以下命令查看当前oplog窗口期能覆盖多长时间:

rs.printReplicationInfo()

这条命令会输出oplog的起止时间和可覆盖的小时数。如果窗口期小于24小时,强烈建议扩容oplog。调整方法是先停掉从节点,以单机模式启动,删除local库中的oplog集合,再用--replSet参数重新加入集群,oplog就会按照新配置的大小重建。这个过程有风险,操作前务必做好数据备份。

写关注和读关注级别配置不当,数据一致性形同虚设

很多故障事后复盘才发现,根源在于驱动层默认的写关注级别是w:1,也就是只要主节点确认写入就返回成功。如果此时主节点宕机,而这条写入还没复制到任何一个从节点,新选出的主节点上这条数据就永久丢失了。对于订单、交易这类核心数据,必须把写关注设为w:majority,并且配合j:true强制刷盘。读关注也有类似陷阱,默认的local级别可能读到主节点上尚未被多数节点确认的“脏数据”,这些数据在故障切换时会被回滚。对一致性有要求的读操作,应该使用majority读关注。这两个参数在驱动连接串里就能配置,但很多团队上线前根本不检查,等到数据不一致才发现为时已晚。

分片集群的片键选择,一旦选错代价巨大

分片集群上线后最头疼的运维问题,十有八九是片键没选好。片键不仅决定数据分布,还直接影响查询性能。一个坏的片键会导致热点分片,所有写入都涌向同一个分片,其他分片闲着没事干。好的片键应该具备高基数、均匀分布、与主要查询模式匹配这三个特征。实际场景中,单一字段往往很难同时满足这些条件,所以复合片键更常用。比如在电商订单场景,用{userId:1, orderId:1}做哈希分片,既能保证同一用户的订单聚合在少数分片上便于查询,又能通过哈希避免热点。片键一旦设定就不能更改,唯一补救办法是新建一个分片集群,把数据迁移过去,这个操作的成本极高。所以在建集群之初,一定要用mongoperf或explain工具对候选片键做充分的读写模拟测试。

连接池大小和超时配置是隐蔽的性能杀手

应用服务器到MongoDB的连接池设置,很多人直接用驱动的默认值就上线了。默认值通常是100个连接,如果你的应用实例有几十个,总连接数轻松破千。MongoDB每个连接都会消耗大约1MB的内存和一定的文件描述符,连接数过多会导致服务端内存压力增大,甚至触发OOM。更隐蔽的问题是连接超时和socket超时的配置。默认的socket超时往往是0,也就是永不超时,一旦某个连接因为网络问题僵死,应用线程就会一直阻塞,直到线程池耗尽,整个服务雪崩。正确的做法是根据业务容忍度设置合理的socketTimeout和connectTimeout,通常建议在3到10秒之间。同时,服务端要配置maxIncomingConnections限制总连接数,并在监控中设置连接数告警阈值。

内存使用不能只看MongoDB进程的RES

MongoDB使用MMAPv1或WiredTiger存储引擎时,内存管理策略完全不同。现在主流是WiredTiger,它默认会把机器内存的一半用作缓存,但这个缓存大小是可以通过wiredTigerCacheSizeGB参数调整的。很多运维看到MongoDB进程占用了大量内存就紧张,其实这里面很大一部分是文件系统缓存,是操作系统层面的行为,MongoDB本身控制不了。真正需要关注的是WiredTiger内部缓存的命中率,你可以在mongostat输出中看到dirty和used两列,如果used长期接近你设定的cacheSize上限,说明缓存不够用,需要增加cacheSize或者加内存。另外,page fault是另一个关键指标,频繁的page fault意味着数据需要从磁盘加载到内存,这会显著拖慢查询速度。

备份策略必须结合集群架构来设计

单靠mongodump做逻辑备份,在分片集群上会面临严重的一致性问题。mongodump是逐集合导出的,分片集群上各个分片的导出进度不一致,备份出来的数据可能处于不同的时间点,恢复后数据关系可能错乱。正确的做法是,对于副本集,使用文件系统快照或者复制隐藏节点的数据文件来做物理备份,这样恢复速度最快。对于分片集群,必须先停止balancer,确保数据不再迁移,然后对每个分片和配置服务器分别做快照,保证所有组件在同一个时间点。如果业务允许短暂停写,可以直接锁库做一致性备份;如果不允许停写,就需要依赖Oplog做增量备份,结合全量快照实现时间点恢复。备份文件一定要定期做恢复演练,很多团队备份了半年,真到恢复时才发现备份文件是损坏的,这种教训太常见了。

监控指标要聚焦,避免告警疲劳

MongoDB暴露的监控指标有上百个,全盘监控等于没有监控。真正需要盯紧的核心指标只有这几个:副本集主从延迟(repl lag)、oplog窗口期、连接数、锁等待比例、page fault频率、WiredTiger缓存使用率、磁盘IO利用率。主从延迟如果持续超过10秒,就要立即排查网络和从节点负载。锁等待比例升高通常意味着存在长时间运行的写操作或者索引缺失。磁盘IO利用率持续超过80%,说明数据量已经超出内存承载能力,要么加内存,要么做分片拆分。告警阈值要根据业务特点定制,不要照搬网上模板。比如日志类业务的写入延迟容忍度高,但订单类业务必须把写入延迟告警设在毫秒级。

版本升级不能跳级,滚动升级有严格顺序

MongoDB的版本升级有明确的兼容性要求,大版本之间不能跳级。比如从3.4升级到4.0,必须经过3.6。这是因为不同版本之间的featureCompatibilityVersion不同,跳级会导致元数据格式不兼容,升级后集群无法启动。滚动升级的顺序也有严格要求:先升级所有配置服务器,再升级所有mongos路由节点,最后才升级分片节点。每个分片内部,要先升级从节点,再降级主节点并升级。整个过程要确保featureCompatibilityVersion在每一步都设置正确。升级前务必在测试环境用生产数据量做完整模拟,因为新版本的存储引擎或查询优化器行为可能有变化,曾经跑得很快的查询升级后可能突然变慢。

日志轮转和清理,不做好会撑爆磁盘

MongoDB默认的日志行为是追加写入,不会自动清理。几个月下来,日志文件可能积累到几十GB,直接把系统盘撑满。系统盘满了之后MongoDB进程会异常退出,而且因为无法写日志而启动失败,这是一个很常见的线上事故。必须在部署时就配置logRotate,可以用SIGUSR1信号触发日志轮转,或者在配置文件中设置systemLog.logRotate为reopen,配合外部logrotate工具定期压缩归档旧日志。同时,journal日志的存放路径最好和数据目录分开到不同的物理盘,避免IO争抢。

网络分区和防火墙规则,是集群稳定性的隐形门槛

副本集和分片集群的各个节点之间需要持续的心跳通信,任何网络抖动都可能被MongoDB判定为节点失联,从而触发选举。在云环境下,安全组和防火墙规则如果配置过严,比如只放行了业务端口27017,却忘了放行节点间通信的端口,或者限制了某些方向的流量,就会导致集群成员之间部分可达、部分不可达,出现“脑裂”症状。正确的做法是,集群内部所有节点之间必须全互联互通,端口全部放行,不要做任何限制。同时,节点之间的网络延迟要控制在毫秒级,跨地域部署副本集是不推荐的,选举超时和同步延迟会让你痛不欲生。如果确实需要跨机房容灾,应该优先考虑分片集群的多数据中心部署方案,而不是强行把副本集成员分散到不同地域。