说实话,MongoDB 跑起来很容易,但等它性能出问题的时候,你往往无从下手。这个项目就是围绕“MongoDB 性能监控仪表板”展开的,核心链路是用 Prometheus 抓取 MongoDB 的运行时指标,再交给 Grafana 渲染成可视化大盘。整套东西能覆盖连接数、操作量、内存、复制延时、慢查询等关键维度,适合自己搭了 Mongo 集群又不想用官方云服务的后端、运维和偏运维的 DBA 参考。很多时候你只需要看一眼仪表板,就能判断是索引问题还是锁竞争,不用再挨个节点 SSH 上去敲命令对参数。这篇实战会从环境准备一直写到告警配置,过程中把实打实踩过的坑也一并列出来,希望你能少绕点弯路。
1. 先想清楚:监控 MongoDB 到底要解决什么问题
1.1 为什么选 Prometheus + Grafana,而不是 MongoDB 官方全家桶
一提 MongoDB 监控,很多人第一反应是 Ops Manager、Atlas 高级监控,或者第三方的 Percona Monitoring and Management(PMM)。这些工具的界面确实漂亮,开箱即用,但绑定感太强。社区版的 MongoDB 本来就没有 Ops Manager,Atlas 监控只覆盖云托管实例,PMM 则是整套 PMM Server + Client 体系,光部署就够喝一壶,后续升级还会连带好多组件。如果只是想要“一个能看到 QPS、连接数、复制延迟的仪表板”,这些方案明显过重。
Prometheus + Grafana 不一样。它本质是一套“抓取、存储、展示、告警”的标准管线:Prometheus 定期从 exporter 暴露的 HTTP 端点拉取指标,存入自带的时间序列数据库,Grafana 再用 PromQL 去查询这些指标画图。这套链路的好处是什么?数据库层面是通用的。你今天监控 MongoDB,明天要监控 MySQL、Redis、Nginx,加一个 exporter、加一段 scrape_config,照样进同一套 Grafana。对于团队里已经有 Prometheus 基础设施的公司,新增一个 Mongo 监控的成本几乎为零。
另外一个容易被忽略的点是标签体系。Prometheus 的指标自带 label,比如会用instance区分不同节点,用replset区分副本集。这样一来,同一个面板模板可以同时展示整个集群的多个节点状态,鼠标点一下 label 就能切换视角。这点比传统监控一个个单独配置要灵活太多,也是我后来越来越愿意用它的原因。
| 方案 | 部署复杂度 | 费用 | 扩展性 | 适合场景 |
|---|---|---|---|---|
| MongoDB Ops Manager | 高,需独立部署 | 企业版限额 | 绑定 MongoDB 生态 | 大型企业、统一管理大量集群 |
| Atlas Advanced Monitoring | 低,云上即开 | 按节点收费 | 仅限 Atlas 托管实例 | 用 Atlas 的团队 |
| Prometheus + Grafana | 中,组件多但单点 | 开源免费 | 极强,一套监控全家桶 | 自建 Mongo、愿意花时间打磨的团队 |
1.2 数据从哪里来:mongodb_exporter 在整个链路里的位置
这条监控链路的中间人是 mongodb_exporter。你可能要问了,Prometheus 为什么不直接连 MongoDB 拿数据?因为 MongoDB 原生不暴露 Prometheus 格式的指标,它的状态数据藏在serverStatus()、dbStats()、collStats()这些命令的返回里。exporter 的作用就是定期调用这些命令,把结果翻译成 Prometheus 能识别的指标格式,再放到 HTTP 端点等 Prometheus 来拉。
这里有个关键认知:MongoDB 的监控指标是分层的。第一层是实例级,来自serverStatus(),包括连接数、操作计数、内存、网络、复制状态等,这类指标最核心。第二层是库级,来自dbStats(),能告诉你某个数据库占了多少磁盘、多少个集合、平均文档大小。第三层是集合级,来自collStats()、indexStats(),能看到每个集合的文档数、索引大小、碎片情况。对应到 MongoDB 的核心概念来说,数据库(database)和集合(collection)在监控里就是你说的库级、集合级指标;而文档(document)级别一般不会直接监控,真到了单条文档的定位问题,通常要靠慢日志和应用日志去追。
搞清楚这个分层,你就明白为什么 exporter 有那么多 collector 开关了。只做基础监控,默认的实例级指标就够用;想把每个集合的存储趋势也看明白,就要额外开启--collector.dbstats和--collector.collstats。后面实操部分我会再展开讲参数,这里先记住一句话:exporter 的采集范围是可控的,不是越全越好,越全对数据库本身的压力就越大。
2. 环境准备:从零搭一套能用的监控环境
2.1 先把 MongoDB 装好并跑起来:常见的安装失败排查
这个项目的前提是 Mongo 本身能连,否则后面全是白搭。很多人第一步就卡在“mongod 装了但起不来”。我见过最多的报错无非这几类:
- 用
apt install mongodb会装到 Ubuntu 自带的旧版包,版本老得离谱,很多监控指标命令缺失,建议用官方源装mongodb-org。 - 启动时日志报
Permission denied或者缺少/var/lib/mongodb目录,这是目录权限问题,chown 给mongodb:mongodb基本能解。 - 配置文件里用了 Tab 缩进。MongoDB 的
mongod.conf是 YAML 格式,YAML 不能用 Tab,藏得很深,报错又不容易一眼看出来。 - 装好后直接跑
mongosh -u xxx认证失败,很可能是因为net.bindIp还写着127.0.0.1,外部机器根本连不进来。
验证方式很简单,先在本地跑一句:
mongosh --eval "db.runCommand({ping:1})"能返回{ ok: 1 }就算 Ok。如果这一步都过不了,先别碰监控,把 Mongo 本身弄干净再说。我习惯在项目最开始就写清楚数据库版本、端口、是否开启认证,后面配 exporter 连接串和告警阈值会省很多事。
2.2 部署 Prometheus:配置文件里的 target 就是数据源
Prometheus 本身是个单个二进制的 Go 程序,装起来非常简单。下载对应架构的 tar 包解压,或者直接用容器跑都行。我建议用二进制方式,方便你用 systemd 管理,也方便理解整个文件结构。
核心动作是改prometheus.yml。这是全局配置,你需要在scrape_configs里把 mongodb_exporter 的地址加进去。最简版是这样:
global: scrape_interval: 15s evaluation_interval: 15s scrape_configs: - job_name: "mongodb" static_configs: - targets: ["127.0.0.1:9216"] labels: instance: "mongo-01"注意这里的scrape_interval,我习惯设 15 秒。太快了对 exporter 和 Mongo 都有不必要的压力,太慢了比如 60 秒,遇到连接数飙涨这类瞬时故障,面板上只能看到锯齿状的残缺曲线,排查时很容易漏掉关键证据。
配置文件改好后,后台启动:
prometheus --config.file=prometheus.yml --storage.tsdb.retention.time=30d--storage.tsdb.retention.time是时序数据保留时长,默认 15 天。这个参数定多大,取决于你想回溯多久的性能数据。磁盘够大就留 30 天,别上来就整一年,Prometheus 存高基数的指标时,磁盘空间和查询响应时间都会让你后悔。启动后去http://localhost:9090/targets看一眼,能搜到 mongodb 这个 job 并且 state 是 UP,说明抓取链路已经通了。
2.3 Grafana 安装启动:端口占用与初始化密码
Grafana 的安装更无脑,一个 deb/rpm 包装好,或者:
docker run -d --name=grafana -p 3000:3000 grafana/grafana默认监听 3000 端口。这里有个高频坑:如果你本地已经开了其他服务占着 3000,Grafana 启动会反复报 “bind: address already in use”,很多人以为装失败了,其实改一下配置就行。编辑 Grafana 的app.ini,把http_port改成比如 3001,重启完就能访问。
第一次打开 Grafana,默认账号密码是admin/admin,登录后第一件事就是强制改密码。这个没什么好说的,安全习惯要好。另外提醒一点,Grafana 本身是无状态的,面板配置存在它的 SQLite 元数据库里。看起来不像什么大事,但我建议你把后面导入的每个 dashboard 的 JSON model 下载下来,跟着配置文件放一起管理。重装 Grafana 时一键导入就恢复了,这个习惯能避免很多返工。
3. 集成实战:把 MongoDB 指标真正接到 Grafana 仪表板
3.1 创建最小权限监控账号:连接串里藏着的坑
生产环境的 MongoDB 一般都要开启认证,不会让你裸奔。那 exporter 连接 Mongo 用什么账号?很多人图省事直接丢一个 root 账号进去,这是非常危险的做法。监控工具的数据源一旦泄露,等于把整个库的管理权限送给别人。
我建议单独建一个专用账号,只给监控需要的权限。在mongosh里执行:
use admin db.createUser({ user: "mongo-monitor", pwd: "用密码生成器造一个强口令", roles: [ { role: "clusterMonitor", db: "admin" }, { role: "readAnyDatabase", db: "admin" }, { role: "read", db: "local" } ] })这里用到的是 MongoDB 内置角色。clusterMonitor给的是serverStatus、replSetGetStatus等实例级监控命令的权限,readAnyDatabase用来支撑dbStats、collStats这类库级集合级读取,local库上的read是为了读副本集相关配置。实践下来,这一套权限组合是监控场景里比较标准的配置,既不会给业务读写风险,又能覆盖绝大多数 dashboard 的查询需求。
有了账号,exporter 的连接串要写成带认证信息的形式。以官方 mongodb_exporter 为例:
./mongodb_exporter \ --mongodb.uri="mongodb://mongo-monitor:强口令@127.0.0.1:27017/admin?ssl=false" \ --collector.dbstats \ --collector.collstats \ --collector.indexstats \ --collector.replicasetstatus \ --web.listen-address="127.0.0.1:9216"这里有个非常容易踩的坑:认证库不是test,不是admin的前面那个名字,而是你db.createUser时use admin的admin。连接串里/admin指的是认证数据库,如果写成了其他库,exporter 启动时大概率报Authentication failed。另外,新版 mongodb_exporter 默认不开启所有 collector,不加--collector.collstats的话,你即使导入社区仪表板,集合层面的面板全是空的。很多人一上来就骂官方仪表板有问题,其实大半是 collector 没开齐。
补充一点,如果你的 Mongo 是副本集或者分片集群,我建议每个 mongod 节点单独跑一个 exporter 实例,不要一个 exporter 连接整个副本集地址。原因很简单,exporter 每次调用serverStatus只能拿到当前连接节点的状态,如果你用副本集地址,它只会连到其中一个节点,抓到的状态是漂移的,做报警时很不可靠。每个节点一个 exporter,Prometheus 侧用instance标签区分,后面 Grafana 里看多个节点就非常直观。
3.2 Grafana 数据源接入和仪表板导入
Grafana 装好之后,先加数据源。路径是Configuration > Data Sources > Add data source,类型选 Prometheus,URL 填http://localhost:9090。这里唯一要留意的是网络模式:Prometheus 和 Grafana 都跑在宿主机的话,localhost没问题;如果 Grafana 是容器而 Prometheus 是宿主机进程,localhost:9090指向的是容器内部,妥妥连不上。要么用host.docker.internal:9090,要么直接让 Grafana 也用 host 网络。
数据源加好,可以先在 Explore 里跑一句mongodb_up,能出1就说明 Prometheus 到 exporter 这层已经通了。之后导入 dashboard:打开Dashboards > Import,在Grafana.com dashboard ID框里填社区仪表板 ID。比较常用的是 2583 和 12083,前者老一些,后者新版指标适配更好。如果你不确定,就去 grafana.com 的官网搜 MongoDB,挑 star 数高的就行。
很多时候导入之后面板还是空的,先别急着怀疑插件坏了。按我的排查顺序走:第一步确认 Prometheus 里mongodb_up == 1;第二步在 Explore 里看看那些mongodb_mongod_*指标到底有没有数据;第三步打开具体面板的编辑模式,看它的 PromQL 是否和你环境里的指标名对得上。这一步十有八九能发现是 exporter 版本差异导致指标名变了。版本不同,指标名确实会从mongodb_connections_current这种旧风格迁移到mongodb_mongod_connections_current新风格,dashboard 作者没跟上版本就会留坑。实战里不要死记指标名,学会用 Prometheus 表达式浏览器搜{__name__=~"mongodb_.*"}就足够了。
3.3 核心 PromQL 查询与面板解读:哪些指标真正有用
dashboard 导入只是第一步,你得看懂每个面板在表达什么。这里挑几个最关键的查询,对应到日常运维最关心的几个问题:
| 关注点 | PromQL 示例 | 说明 |
|---|---|---|
| 当前连接数 | mongodb_mongod_connections_current | 和 maxIncoming 对比,连接数接近 max 就是 Connections 告警的预兆 |
| 操作 QPS | sum(rate(mongodb_mongod_op_counters_total[1m])) by (type) | 按 insert/query/update/delete/command 分类看各类型占比 |
| 复制延迟 | mongodb_mongod_replset_optime_behind | 单位通常是秒,长期大于 0 说明 secondary 追不上主节点 |
| 进程内存占用 | mongodb_mongod_process_resident_memory_bytes | 结合系统可用内存,判断 Mongo 是否有内存压力或配置不合理的 cache 占用 |
我看面板的习惯是这样的:先看op_counters的整体曲线,确认当前有没有明显的读写尖峰;再看连接数是否同步上涨,如果两者都涨说明是正常流量,如果连接数涨而 QPS 没涨,大概率是连接泄漏或者客户端配置问题;最后扫一眼复制延迟,确保所有从节点都在安全范围内。这三个维度可以覆盖绝大多数“数据库变慢了”的现场还原。
顺带说一句,很多 community dashboard 上都有一个叫 “Query Executor / Scanned vs Returned” 的面板,本质是想让你看扫描文档数和返回文档数的比例。如果这个比例长期大到离谱,直接提示索引没命中或者查询条件写得太宽。这种面板不是所有 dashboard 都有,但看到了一定好好留着,它是以后做性能调优最宝贵的线索。
4. 告警规则与常用排障技巧实录
4.1 三条 P1 级告警规则建议
光有仪表板还不够,出了问题不可能二十四小时盯屏幕,告警规则必须配上。Prometheus 的告警规则写在独立 YAML 文件里,比如mongo_alerts.yml,然后在prometheus.yml里通过rule_files引入:
rule_files: - "mongo_alerts.yml"我建议从这三条规则开始,覆盖最关键的稳定性和高可用场景:
groups: - name: mongodb_alerts rules: - alert: MongoDBDown expr: mongodb_up == 0 for: 5m labels: severity: critical annotations: summary: "MongoDB 实例不可抓取,请立即检查 mongod 进程" - alert: MongoDBReplicationLagHigh expr: mongodb_mongod_replset_optime_behind > 60 for: 5m labels: severity: warning annotations: summary: "从节点复制延迟超过 60 秒"这里有一个特别要注意的点:规则里的指标名一定要用你当前环境里实际存在的指标名。不同 exporter 版本之间,复制延迟的指标名有可能从mongodb_replset_optime_behind变成mongodb_mongod_replset_optime_behind。验证办法是在 Prometheus 的 Alerts 页面看规则能否正确求值,如果提示 “no data” 或者 “vector does not contain”,就要回头改指标名。我见过太多人照着网上的规则文件一贴,以为完事了,结果评估半天全是不满足条件的空查询,等同于没告警。
告警阈值不要拍脑袋。每个环境的基准不一样,如果是写入密集的日志系统,QPS 每分钟上十万也正常。我习惯先观察两周,看面板上的常态水位,再在常态水平上留出 50% 的余量来设阈值。从 80% 的连接数占用率开始告警,复制延迟超过 60 秒开始告警,这两个阈值对大多数中小团队是合理的起点。
4.2 新手最容易踩的坑:从“安装失败”到“面板空白”
现在把实战里的高频问题集中说一下。这些坑我基本都亲身踩过一遍,哪怕你只避掉其中两个,今天这一整篇也算没白看。
- mongodb_exporter 启动报
Authentication failed。前一步的连接串认证库写错了。记住db.createUser时use的库是哪个,连接串里就写哪个,通常都是admin。 - 启动报
--collector.collstats requires --collector.dbstats。这种情况直接两个参数一起开,因为collStats的数据依赖dbStats拉取的数据库列表。 - Prometheus 起不来,检查 YAML 缩进。Prometheus 对配置里的 Tab 零容忍,编辑器里把 “显示空白字符” 开关打开,确保所有缩进都是空格。
- Grafana 导入仪表板后一张图都没有,最常见原因是数据源没有设置为默认。导入面板时有个下拉框让你选数据源,如果界面里的面板用的是
DS_PROMETHEUS变量,数据源没匹配上就会这样。 - Mongo 和 exporter 之间网络不通。exporter 和 mongod 是不是在同一台机器?如果 exporter 配了
--web.listen-address=127.0.0.1:9216,Prometheus 却装在另一台机器上,自然抓取不到。监听地址要么改成0.0.0.0:9216,要么让 Prometheus 和 exporter 同机部署,二选一。 - 面板图表固定出现“无数据”但 PromQL 明明能查到。检查仪表板右上角的时间范围,刚开始采集的指标可能只有最近 15 分钟的数据,而默认时间范围是过去 6 小时,尝试切到
Last 15 minutes再看。 - 一直开着 collstats 采集,把 MongoDB 拖慢了。集合级统计是轮询扫描,采集范围大、频率高时对实例是有真实压力的。日常只开启实例级和
dbstats,collstats在专项排障时再临时打开。 - 用第三方 GUI 工具的“破解”渠道。我知道很多人喜欢拿 NoSQLBooster 这种工具连 Mongo,网上也确实流传过所谓破解版。这里我明确劝退:破解工具往往捆绑后门,而 GUI 工具连接字符串里存的可都是生产库的账号密码,何必拿生产安全换一个看似省钱的便利。官方 MongoDB Compass 已经免费且好用,监控连接请始终使用最小权限账号,不要图省事把业务账号填进任何工具里。
4.3 进阶技巧:从仪表板往下钻,定位慢查询和锁问题
仪表板能告诉你“现在慢”,但更要想办法回答“为什么慢”。Prometheus 的作用是缩小范围,具体原因还需要和 Mongo 的日志、profiler 配合。比如某个面板显示query的 QPS 没有明显上涨,但数据库整体 CPU 飙升,这时候不要急着去 mongod 里找慢查询,先在 Mongo 侧开启 profiling:
db.setProfilingLevel(1, { slowms: 200 })这里把超过 200ms 的查询都记录到system.profile集合里,积累几分钟后就可以去查:
db.system.profile.find({ op: "query" }).sort({ millis: -1 }).limit(20)同样的思路也可以用来看“查询和删除”这种操作慢在哪里:是COLLSCAN了,还是内存排序过大,还是锁等待时间太长。Prometheus 负责持续盯梢,profiler 负责精准打击,这俩组合起来才是完整的性能问题排查闭环。
这里再顺手分享一个锁相关的常用指标。MongoDB 的锁等待信息在serverStatus().locks里,对应到 Prometheus 里一般会有mongodb_mongod_locks_*之类的指标。你做性能分析时,可以加一个面板把全局锁的等待时间画出来。一旦发现写入慢的趋势,同时锁等待曲线也在上涨,基本可以断定是并发写冲突而不是磁盘 IO 的问题。这个判断依据能帮你少走很多弯路。
从整个项目体量看,这套“MongoDB 性能监控仪表板”并不复杂,难点在于把每个环节的细节都处理到位。我个人在实际操作中最大的体会是:抓取间隔不要过度追求短,15 秒已经能让绝大多数瞬时故障留下痕迹;collstats 这类重量级采集只在排障时临时开启,开一晚上你会发现数据库负载凭空高了一截。最后再说一个实用技巧:把 Grafana 面板导出的 JSON、prometheus.yml、告警规则一起放进 git 管理,等哪天机器要重装或者换团队接手,十分钟就能把这套系统原样拉起来。监控从来不是一个一次性搭建的活,它应该和你写的代码一样,有版本、有备份、有迭代。