1. 监控体系整体设计为什么盯紧MongoDB比写好CRUD更重要数据库监控这件事很多人一开始都嫌麻烦。业务没跑起来之前谁会关心那个db.serverStatus()输出又涨了几毫秒但MongoDB和MySQL不太一样它那套内存映射、WiredTiger缓存、oplog机制决定了“看症状猜病因”的排查成本特别高。等业务告警真炸了再开着mongostat盯屏幕基本已经晚了半拍。所以我在做数据库运维规划的时候向来是把监控体系摆在业务上线之前。这篇内容不聊概念直接说两种主流的落地方式传统企业里用得极广的Zabbix云原生环境下几乎成了事实标准的Prometheus Grafana组合。两条路我都踩过坑下面把设计方案、采集原理、告警阈值和日常维护经验一起梳理出来。先回答最核心的问题监控MongoDB到底要监控哪些东西抛开花哨的APM不谈数据库层面最值得盯的其实就四类可用性进程活着、端口通着、主从复制有没有断。容量磁盘剩余、oplog窗口、集合大小增长趋势。性能CPU/内存/IO、WiredTiger cache命中率、锁等待、慢查询数量。一致性复制延迟、选举抖动、从库落后主库秒数。上述四类无论你用Zabbix还是Prometheus最终都要落到具体指标上。区别只在于采集方式、数据模型和告警引擎。工具选型上我个人的经验是如果机房里有几百台机器、运维团队习惯用Zabbix统一纳管那就没必要为了“潮流”硬上Prometheus反过来如果业务已经在Kubernetes里跑还在用Zabbix agent手工发现Pod那纯属自虐。选型没有绝对好坏只有适不适合。从架构上看Zabbix属于“中心化采集 定时拉取”的老牌风格适合网络隔离明确、主机数量稳定的场景。Prometheus则是“Pull模型 指标多维标签”的云原生风格适合动态扩缩容、容器化部署的环境。对MongoDB来说两者都有成熟的采集器Zabbix有官方模板和mongodb_monitor脚本Prometheus有mongodb_exporter。下面我把两条路分别展开。2. Zabbix集成实战从模板导入到自定义Key的手把手上路Zabbix监控MongoDB的方式本质上是“Agent执行脚本拿数据再推给Server”。所以你要搞定的核心其实就两件事脚本怎么写、Key怎么配。2.1 官方模板与前期准备Zabbix官方有一个MongoDB by Zabbix agent模板它依赖的是zbx_mongodb_status.sh这类脚本。如果你用的Zabbix 6.0以上版本直接在模板库里导入就可以。但要注意官方模板默认调用的MongoDB命令是mongod进程的admin数据库认证信息所以事先得准备好一个只读监控账户。我的建议是单独创建专供监控用的数据库账号别用root或者admin超级账号。命令也很简单db.getSiblingDB(admin).createUser({ user: zbx_monitor, pwd: Strong-Pass-2025, roles: [ { role: clusterMonitor, db: admin }, { role: readAnyDatabase, db: admin } ] })给clusterMonitor角色就够查serverStatus和rs.status()了多一个权限多一份风险。创建好之后把账号密码填到脚本对应的环境变量里。Zabbix Agent侧使用Include方式加载自定义Key配置文件常规路径在/etc/zabbix/zabbix_agentd.conf.d/下新建mongodb.conf。2.2 自定义监控Key与JSON解析官方模板能覆盖基本指标但生产环境里总有特殊需求。比如你想单独监控当前活跃的连接数按来源IP分布或者某个特定集合的文档数模板就管不到了。这时候就需要自己写Key。Zabbix的UserParameter格式如下UserParametermongodb.custom.connections, /etc/zabbix/scripts/mongodb_stats.sh connections UserParametermongodb.custom.oplog, /etc/zabbix/scripts/mongodb_stats.sh oplog脚本内部通过mongo --eval执行JavaScript输出JSON然后在Zabbix里用JSONPath或预处理提取数值。这里有个容易翻车的细节Zabbix Agent对脚本返回值有字符数限制如果一次性输出整个serverStatus默认1MB是够的但要是输出全部集合列表量大会失败。所以脚本里一定要用db.serverStatus().connections这种精确到子节点的写法不要整段返回。下面给一个我实际在用的精简版脚本思路#!/bin/bash MONGO_CMDmongo --quiet --host 127.0.0.1 --username zbx_monitor --password xxx --authenticationDatabase admin case $1 in connections) $MONGO_CMD --eval printjson(db.serverStatus().connections) ;; mem) $MONGO_CMD --eval printjson(db.serverStatus().mem) ;; opcounters) $MONGO_CMD --eval printjson(db.serverStatus().opcounters) ;; esac然后在Zabbix前端“监控项”里新建对应项类型选“Zabbix Agent”键值填mongodb.custom.connections数据预处理选“JavaScript”或“JSONPath”提取比如$.active字段。从实操角度看对返回的JSON做JSONPath预处理最稳定比如$.active、$.currentQueue.total。2.3 触发器与告警收敛Zabbix里告警用“触发器”表达本质上就是“阈值表达式”。但别急着每个指标都配触发器。我先列几个最核心的这些配完基本就能拦住90%的生产事故。监控项触发器表达式说明进程存活nodata(mongodb.status.lastcheck, 2m)1两分钟没数据直接告警复制延迟last(mongodb.repl.secondary.behind)30从库落后主库30秒WiredTiger cache使用last(mongodb.wiredtiger.cache.used) 90缓存使用超过90%活跃连接数last(mongodb.connections.active) 80% of max连接池即将耗尽磁盘剩余last(vfs.fs.size[/data].free) 5G防止磁盘写满触发器表达式里有个细节MongoDB的复制延迟在rs.status()里单位是秒但某些老版本Zabbix模板采集出来是毫秒这里一定要核对单位不然明明从库落后一小时都没触发告警。我在某次演练里就被这个坑坑过后来统一在脚本里除以1000转成秒才对得上。告警收敛方面Zabbix可以设置“触发器”的告警间隔比如5m表示同一个触发器持续超过阈值时5分钟才发送一次告警。MongoDB的连接数抖动很常见务必加收敛否则大促期间告警会刷屏。3. Prometheus集成实战Exporter采集与服务发现再来看Prometheus这边。它和Zabbix的最大差异在数据模型Prometheus用“指标名 标签”构成时间序列配合Pull模型可以天然适配MongoDB分片集群这种多组件架构。你把每个mongod、mongos、config实例都暴露一个metrics端口Prometheus按标签去抓就行。3.1 mongodb_exporter部署要点mongodb_exporter是社区里最常用的MongoDB采集器项目地址是percona/mongodb_exporter。它支持兼容MongoDB 3.6到7.0的版本写这篇文章的时候甚至能用。部署方式非常简单二进制或容器都行。二进制部署一条命令示例./mongodb_exporter --mongodb.urimongodb://zbx_monitor:xxx127.0.0.1:27017/admin?connectTimeoutMS3000 \ --collector.diagnosticdata \ --collector.replicasetstatus \ --collector.dbstats \ --web.listen-address:9216这里有几个参数解释一下--collector.diagnosticdata采集serverStatus相关指标必开。--collector.replicasetstatus采集复制集状态必开。--collector.dbstats每个库的统计数据。如果数据库数量多这个会加大采集压力量大的时候建议按需开启。--collector.cluster针对分片集群会去连mongos汇总运行信息。默认端口是9216。部署完直接curl localhost:9216/metrics看指标前缀比如mongodb_up、mongodb_connections、mongodb_mongod_replset。3.2 Prometheus抓取配置与服务发现如果MongoDB跑在传统虚拟机里直接在prometheus.yml里写static_configs就行scrape_configs: - job_name: mongodb static_configs: - targets: [10.0.0.11:9216, 10.0.0.12:9216] relabel_configs: - source_labels: [__address__] regex: (.*):9216 target_label: instance replacement: $1如果跑在Kubernetes里更推荐用kubernetes_sd_configs按Pod注解自动发现。比如给MongoDB Pod打上prometheus.io/scrape: true和prometheus.io/port: 9216抓取配置里用annotation做relabel。有个实际经验MongoDB分片集群里mongos和mongod的同名标签会冲突。比如每个分片主节点都叫PRIMARY如果你只按instance标签区分Grafana面板上就会糊成一团。我习惯在relabel阶段额外增加一个role标签用正则从Pod名或Exporter启动参数里提取分片名。这样每个图表都能按分片和角色过滤。3.3 Grafana面板与PromQL数据切片拿到mongodb_exporter指标后Grafana可以直接用官方面板ID比如2588、14900、14031等。但官方面板的指标名可能和你的Exporter版本不完全一致比如老版本用mongodb_mongod_replset_optics新版本改成了mongodb_replset系列。我建议导入面板后先到Dashboard设置里看一下“Query”引用的指标是否存在不存在就手工改PromQL。几个常用的PromQL示例可以存成书签查看复制延迟秒max by (instance) (mongodb_mongod_replset_member_uptime{state_labelSECONDARY})这个不直观更实用的是按主从节点计算optimeDate差值( time() - max by (instance) (mongodb_mongod_replset_member_optime_date) ) / 1000如果optime_date指标没有可以退而求其次用mongodb_mongod_replset_member_health配合状态判断但延迟的精度会差一些。连接数使用率sum(mongodb_connections{statecurrent}) / sum(mongodb_connections{stateavailable}) * 100WiredTiger缓存脏数据比例rate(mongodb_wiredtiger_cache_pages_dirty_total[1m]) / rate(mongodb_wiredtiger_cache_pages_total[1m])关于PromQL必须提醒一句高基数标签不要乱用。mongodb_exporter会暴露mongodb_collection_approximate_document_count这类带collection标签的指标如果你的库里有几万个集合每15秒拉一次Prometheus的内存会涨得很快。普通场景不要全量采集集合维度只采集库维度或者选重点集合单独拉。3.4 告警规则示例Prometheus告警通过rules.yml配置。下面这个文件是我压箱底的模板已经配合Alertmanager用了很久适配大多数MongoDB集群groups: - name: mongodb-alerts rules: - alert: MongoDBDown expr: mongodb_up 0 for: 1m labels: severity: critical annotations: summary: MongoDB实例挂掉了 description: {{ $labels.instance }} 已停止响应监控探活。 - alert: MongoDBReplLagHigh expr: (time() - max by (instance) (mongodb_mongod_replset_member_optime_date)) / 1000 30 for: 2m labels: severity: warning annotations: summary: 从库复制延迟过高 description: {{ $labels.instance }} 延迟超过30秒。 - alert: MongoDBCacheHigh expr: (1 - rate(mongodb_wiredtiger_cache_bytes_total[5m])) * 100 10 for: 5m labels: severity: warning annotations: summary: WiredTiger可用缓存过少 description: WiredTiger cache 可写入比例低于10%告警规则里有个细节for字段表示持续时间期间持续满足才算触发这能过滤掉瞬时抖动。MongoDB复制延迟偶尔会瞬间飙到几十毫秒甚至一秒但30秒持续阈值基本能避开误报。Alertmanager这边路由可以按severity区分通知渠道critical直接走电话或短信warning走邮件或IM群。还有一点我觉得比告警规则本身更重要一定要给每个MongoDB节点配watchdog类探活而不是单纯依赖Exporter的up指标。因为Exporter进程本身也可能挂掉如果Exporter挂掉Prometheus会认为实例up0这倒也能发现但如果你用blackbox_exporter再加一层TCP探测能更好区分“数据库挂了”和“采集器挂了”。4. 指标深度解读别让监控面板变成数据废纸监控面板上铺满花花绿绿的曲线看着很安心但如果你不理解每个指标背后对应的MongoDB机制很可能统计了半天该出事的还是出事。这一节我挑几个最容易误读的指标配合底层原理解释清楚。4.1 WiredTiger Cache到底看哪个数MongoDB 3.2之后默认存储引擎就是WiredTiger它自带一个内部缓存默认大小是(RAM - 1GB) * 0.5容器环境里则是容器内存限制的50%。很多人直接看“cache used”百分比以为90%就是快满了其实不是。WiredTiger缓存的设计是“用了就扔”它会积极把冷数据刷盘以腾出空间给热数据所以缓存长期保持在80%-90%占用是正常的。真正要警惕的是缓存脏页比例和驱逐线程的eviction_dirty_total。脏比例高说明写操作积压的未刷盘数据多此时如果宕机恢复成本很高。驱逐线程持续繁忙说明WiredTiger在拼命换页IO跟不上这时候你增加缓存大小未必有用反而要去看磁盘IOPS。所以我推荐的判断维度是mongodb_wiredtiger_cache_pages_dirty稳定增长且占比超过20%要查磁盘IO。mongodb_wiredtiger_eviction_thread工作时间占比接近100%考虑加大缓存或优化热点数据分布。4.2 连接数是瓶颈还是表象MongoDB连接数飙升不一定就是并发高。很多驱动默认维护连接池比如Java驱动默认maxPoolSize100如果应用里出现了慢查询每个慢查询会占住一个连接等待服务端响应最终连接池被打满但这时候你的CPU可能才30%。所以监控连接数要联合currentQueue.total和opcounters里的query速率一起看。如果连接数高但currentQueue没有积压说明连接池正常只是连接基数大如果currentQueue同时有积压说明服务端处理不过来核心问题不是连接而是慢查询。这时候去看db.currentOp()里有没有长时间secs_running的请求才是正确的排障路径。4.3 Oplog窗口最容易忽略的定时炸弹Oplog是MongoDB复制机制的核心主库的写操作记录到oplog从库异步拉取回放。如果从库停机太久或者复制延迟超过oplog保留窗口从库就再也跟不上主库只能做全量重同步。这个场景比想象中常见得多。怎么监控Prometheus下看mongodb_mongod_replset_oplog相关指标或者直接用命令在MongoDB里计算db.getReplicationInfo().timeDiff返回的是oplog里最早和最晚记录的时间差单位小时。经验阈值是timeDiff建议保持在24小时以上。如果少于几小时备份任务、从库离线修复都会变得极其仓促。Zabbix那边可以在自定义脚本里把db.getReplicationInfo().timeDiff取出来作为普通监控项配上“低于24小时告警”。这个指标比复制延迟更重要我建议两个工具都加上。4.4 慢查询数量与执行时间MongoDB慢查询在日志里叫COMMAND慢日志默认超过100ms就记录。监控维度有两个慢查询的条数、最慢查询的执行时间。我更推荐用Atlas或Percona监控插件来采集慢日志分析但自建方案里用currentOp里的micros累计也能看个大概。Exporter里有mongodb_mongod_metrics_commands_total按命令统计配合rate()看每秒命令数变化如果find或者aggregate的速率突然加倍大概率是应用层请求量上升或出现了扫描全表的查询。5. 常见问题排查与应用故障实录再好的监控体系到实际运维中都会遇到各种意想不到的小毛病。这一节把我几年间处理过的典型问题整理成速查表并补上一些诊断思路。5.1 Zabbix脚本采集不到数据症状监控项显示“不支持”或“无数据”。先做三步排查在Zabbix Agent主机上手动执行zabbix_get -s 127.0.0.1 -k mongodb.custom.connections如果报错看脚本有没有执行权限。确认脚本里mongo命令能连上。注意mongoshell在MongoDB 6.0之后被mongosh替代老脚本用mongo会直接命令不存在。我遇到过不止一次升级后监控脚本全线失效。确认Zabbix Agent允许执行自定义Key。新版Zabbix Agent 2默认可能禁用UserParameter需要在zabbix_agent2.conf里设置AllowKey*或明确允许指定Key。还有一个特容易踩的点脚本里用了环境变量但Zabbix Agent以zabbix用户启动时PATH里没有/usr/binmongo找不到会导致“找不到命令”。解决办法是在脚本开头写死全路径例如MONGO_BIN/usr/bin/mongosh然后所有引用都加上$MONGO_BIN。5.2 Prometheus Exporter认证失败Exporter用mongodb://user:passhost连接MongoDB但密码里如果带着、:、/等特殊字符URI解析会崩。处理办法是对密码做URL编码例如换成%40。更省事的做法是使用--mongodb.uri从配置文件读取而不是写在命令行和systemd Unit里避免进程列表泄露密码。配置示例# /etc/mongodb_exporter.conf MONGODB_URImongodb://zbx_monitor:Pass%40word127.0.0.1:27017/admin然后systemd里加一行EnvironmentFile/etc/mongodb_exporter.conf。5.3 指标值对不上号明明MongoDB里db.serverStatus().connections.active是20但Prometheus画出来却是200。这种差异常见于Exporter的采集时间点和MongoDB状态自然变化尤其是连接数这种瞬态指标Web界面刷新一下都可能差出几十。要核对时直接用curl拉Exporter的文本输出和MongoDB原始state的值对比间隔尽量控制在1秒以内。如果是计数型指标如opcountersPrometheus里要配合rate()取增量而不是直接看原始值。有些人把原始计数器直接放面板里看到一条不断增长的折线以为没有在涨其实是不会看的。5.4 告警风暴监控搭好后的第一个大促告警风暴几乎是必然的。连接数上下抖动触发告警、复制延迟偶尔瞬高触发告警、磁盘使用率每天周期性上涨触发告警。要缓解这个问题除前文提到的for字段和告警间隔我还会在Alertmanager里配置group_wait和group_interval让同类告警合并通知。比如设置route: group_by: [alertname, instance] group_wait: 30s group_interval: 5m repeat_interval: 4h这样同一时刻多个MongoDB节点的同一类告警只会汇总成一条通知大幅减少骚扰。但要注意重复间隔repeat_interval不能设得太长否则恢复正常后再次告警要等很久。5.5 磁盘容量告警的误报Zabbix或Prometheus默认采集磁盘时会把所有挂载点都拿过来。MongoDB的数据目录如果和系统盘挂载在一起日志文件满了可能会误报“根分区占用高”。更稳妥的做法是监控MongoDB数据目录所在的挂载点。比如MongoDB数据目录是/data/mongo在Prometheus里用node_filesystem_avail_bytes{mountpoint/data}而不是笼统地按实例看所有分区。我还会额外配一个“数据目录增长趋势”的预测型告警用predict_linear看未来24小时能否落盘提前扩容。PromQL大概是predict_linear(node_filesystem_avail_bytes{mountpoint/data}[6h], 24 * 3600) 0这个表达式预测6小时走势延续24小时是否归零一旦预测0就触发告警比简单阈值告警更能留出缓冲时间。6. 混合场景下的取舍与个人实践经验如果公司既有传统虚拟机又有Kubernetes业务Zabbix和Prometheus同时存在很常见。我不建议强行统一成一套而是按职责拆分Zabbix继续管基础资源和纯主机层监控Prometheus管Kubernetes和应用层指标。MongoDB如果跨两种环境就在各自环境内分别采集再通过Grafana统一汇总展示。实际操作中我的做法是Grafana里新增两个数据源一个Prometheus一个Zabbix通过Zabbix的API插件把所有MongoDB相关的面板放在一个Dashboard里。这样运维只开一个页面就能看到全貌不用来回切换。Zabbix和Prometheus集成之间也存在数据打通的可能比如Zabbix触发告警后自动执行一个Prometheus API查询把当时的上下文带进告警通知。但这属于高阶玩法对大多数场景不是必须。先把每套方案的独立部分做扎实比追求花活重要。最后分享一个我实践中的体会刚开始搭MongoDB监控时总想把所有指标都采集下来面板一个接一个加结果每次排障都在翻图找重点。后来我强制自己只保留四类必要的指标——可用性、复制、缓存、连接。其他的要么删掉要么收进“诊断”子面板。数据量少了反而告警更准确定位问题更快。监控的价值不在于指标数量在于告警能否在你睡梦中精准叫醒你。所以这套体系的核心永远不是工具而是你对MongoDB本身运行机制的理解。想清楚哪些指标代表什么再交给工具去盯比本末倒置地追求工具功能丰富重要得多。