MongoDB 作为文档型数据库的领导者,在现代应用架构中扮演着越来越重要的角色。然而,从开发环境迁移到生产环境时,许多团队都会遇到性能瓶颈、连接泄漏、安全漏洞等问题。本文将从实战角度出发,系统讲解 MongoDB 生产环境的慢查询分析、explain 深度解读、WiredTiger 内存调优、连接池配置、安全加固以及备份监控策略。
1. 慢查询分析:Profiling 与 currentOp
生产环境中最常见的问题就是慢查询拖垮整个集群。MongoDB 提供了完整的 Profiling 机制来捕获和分析慢查询。
1.1 开启 Profiling
Profiling 有三个级别:
- 0:关闭(默认)
- 1:仅记录慢查询(超过
slowms阈值的查询) - 2:记录所有操作
// 以 admin 身份连接到目标数据库
db = db.getSiblingDB('production_db');
// 开启 Profiling,记录超过 100ms 的查询
db.setProfilingLevel(1, { slowms: 100 });
// 确认 Profiling 状态
db.getProfilingStatus();
// 输出:{ "was" : 1, "slowms" : 100, "sampleRate" : 1.0 }
// 关闭 Profiling
db.setProfilingLevel(0);
// 记录所有操作(仅在调试时使用,生产环境慎用)
db.setProfilingLevel(2);
对于高并发场景,可以开启采样模式,只记录 10% 的慢查询,减少 Profiler 本身的开销:
// 采样率 10%,记录超过 200ms 的查询
db.setProfilingLevel(1, { slowms: 200, sampleRate: 0.1 });
// 查看采样设置
db.getProfilingStatus();
// 输出:{ "was" : 1, "slowms" : 200, "sampleRate" : 0.1 }
1.2 分析 system.profile 集合
慢查询记录存储在 database.system.profile 固定集合(capped collection)中,默认大小为 1MB。生产环境建议扩大容量,否则旧记录会被快速覆盖。
// 创建更大的 Profiling 集合(比如 256MB),先关闭 Profiling
db.setProfilingLevel(0);
// 删除旧的 profile 集合
db.system.profile.drop();
// 创建新的固定集合,256MB,最大 10000 条记录
db.createCollection("system.profile", { capped: true, size: 256*1024*1024, max: 10000 });
// 重新开启 Profiling
db.setProfilingLevel(1, { slowms: 100 });
下面是分析慢查询的核心脚本:
// 查询最近最耗时的操作,按执行时间降序
db.system.profile.find().sort({ millis: -1 }).limit(10).forEach(op => {
print("============================================================");
print("操作类型:", op.op);
print("命名空间:", op.ns);
print("执行耗时(毫秒):", op.millis);
print("扫描文档数:", op.docsExamined);
print("返回文档数:", op.nreturned || op.nresponse);
print("索引扫描数:", op.keysExamined);
print("查询条件:", JSON.stringify(op.query || op.command, null, 2));
print("执行计划:", JSON.stringify(op.planSummary));
print("时间戳:", op.ts);
print("============================================================");
});
// 统计最慢的集合和查询模式
db.system.profile.aggregate([
{ $match: { op: { $in: ["query", "command"] } } },
{ $group: {
_id: { ns: "$ns", planSummary: "$planSummary" },
avgTime: { $avg: "$millis" },
maxTime: { $max: "$millis" },
count: { $sum: 1 }
}},
{ $sort: { avgTime: -1 } },
{ $limit: 10 }
]).forEach(r => {
printjson(r);
});
1.3 使用 db.currentOp() 诊断实时性能问题
currentOp 可以查看当前正在执行的操作,是定位卡死查询和锁竞争的利器。
// 查看所有正在进行的操作
db.currentOp();
// 查看运行时间超过 5 秒的操作
db.currentOp({ "secs_running": { $gt: 5 }, "active": true });
// 查看正在等待锁的操作
db.currentOp({ "waitForLock": true });
// 查看特定集合上的操作
db.currentOp({ "ns": /production_db\.orders/ });
// 查看阻塞其他操作的长事务
db.adminCommand({
currentOp: true,
"secs_running": { $gt: 60 },
"active": true
});
一旦发现有害查询,可以立即终止:
// 先找到 opid
db.currentOp({ "ns": "production_db.orders", "secs_running": { $gt: 30 } });
// 终止特定操作(使用 admin 数据库)
db.getSiblingDB('admin').killOp(12345678);
在生产环境中,建议将 slowOpSampleRate 设置为合理的值,并结合日志分析:
// 查看当前服务器参数
db.adminCommand({ getParameter: "*" }).slowOpSampleRate;
// 动态修改慢查询日志采样率
db.adminCommand({ setParameter: 1, slowOpSampleRate: 1.0 });
2. explain() 深度解读:执行计划分析
MongoDB 的 explain() 是索引优化的核心工具。它提供三个详细级别:queryPlanner、executionStats 和 allPlansExecution。
2.1 explain 三种模式对比
| 模式 | 实际执行 | 返回内容 |
|---|---|---|
queryPlanner | 否 | 仅查询计划 |
executionStats | 是 | 实际执行统计 |
allPlansExecution | 是 | 所有候选计划统计 |
// queryPlanner 模式:不真正执行,只看到候选计划
db.orders.find({ status: "shipped" }).explain("queryPlanner");
// executionStats 模式:真正执行,看到实际扫描和返回数量
db.orders.find({ status: "shipped" }).explain("executionStats");
// allPlansExecution 模式:执行所有候选计划(最详细,开销也最大)
db.orders.find({ status: "shipped" }).explain("allPlansExecution");
2.2 executionStats 核心指标解读
const plan = db.orders.find({ status: "shipped", amount: { $gt: 1000 } })
.sort({ createdAt: -1 })
.limit(50)
.explain("executionStats");
printjson(plan);
关键输出字段的含义:
// 核心执行统计
plan.executionStats.totalDocsExamined; // 扫描的文档总数
plan.executionStats.totalKeysExamined; // 扫描的索引键总数
plan.executionStats.nReturned; // 返回的文档数
plan.executionStats.executionTimeMillis; // 执行耗时(毫秒)
plan.executionStats.executionStages.stage; // 根阶段类型
// 理想的执行计划应该满足:
// totalDocsExamined ≈ nReturned(每个返回文档只扫描一次)
// totalKeysExamined ≈ nReturned(索引精确匹配)
// executionTimeMillis 应该远小于业务容忍阈值
2.3 读懂 winningPlan
const explain = db.orders.find({ userId: "u12345", status: "pending" }).explain("executionStats");
// 查看获胜的查询计划
const winningPlan = explain.queryPlanner.winningPlan;
// 典型的 COLLSCAN(全表扫描)
// winningPlan.stage === "COLLSCAN"
// 意味着没有使用索引,必须扫描全部文档
// 典型的 IXSCAN(索引扫描)
// winningPlan.stage === "FETCH"
// winningPlan.inputStage.stage === "IXSCAN"
// winningPlan.inputStage.indexName === "userId_1_status_1"
// 一个使用了复合索引的良好执行计划
{
"stage": "LIMIT",
"limitAmount": 50,
"inputStage": {
"stage": "FETCH",
"inputStage": {
"stage": "IXSCAN",
"indexName": "status_1_createdAt_-1",
"keyPattern": { "status": 1, "createdAt": -1 },
"direction": "forward",
"indexBounds": {
"status": ["[\"shipped\", \"shipped\"]"],
"createdAt": ["[MaxKey, MinKey]"]
}
}
}
}
2.4 识别和解决低效查询
// CASE 1: 内存排序(SORT 阶段但没有使用索引排序)
// 如果在 executionStages 中看到 SORT stage,说明发生了内存排序
// 解决方案:创建包含排序字段的复合索引
db.orders.createIndex({ status: 1, createdAt: -1 });
// CASE 2: 回表过多(FETCH 阶段扫描了大量文档但返回很少)
// 当 totalDocsExamined >> nReturned 时发生
// 解决方案:使用覆盖索引(covered query)
db.orders.createIndex({ status: 1, createdAt: -1, userId: 1 });
// 查询只返回索引中的字段,可以完全避免 FETCH
db.orders.find({ status: "shipped" }, { userId: 1, createdAt: 1, _id: 0 })
.sort({ createdAt: -1 });
// CASE 3: 索引前缀缺失导致全表扫描
// 查询 { email: "x@y.com" } 但索引是 { userId: 1, email: 1 }
// 由于缺少 userId 条件,无法使用该索引
// 解决方案:为 email 单独创建索引,或调整索引顺序
db.users.createIndex({ email: 1 });
// 使用 update 的 explain 查看更新操作的执行计划
db.orders.explain("executionStats").update(
{ status: "pending", createdAt: { $lt: new Date(Date.now() - 7*24*60*60*1000) } },
{ $set: { status: "cancelled" } },
{ multi: true }
);
3. 内存调优:WiredTiger 缓存与内存压力
WiredTiger 是 MongoDB 的默认存储引擎,其内部缓存大小直接影响查询性能。
3.1 缓存大小计算与配置
默认情况下,WiredTiger 缓存大小为 max(0.5 * (RAM - 1GB), 256MB),即最多使用物理内存的一半(减去 1GB)。这个默认值对大多数场景是合理的,但在特定业务负载下需要调整。
# mongod.conf
storage:
wiredTiger:
engineConfig:
# 显式设置缓存大小(GB 或 MB)
cacheSizeGB: 8
# 或者使用 cacheSizeMB: 8192
# 是否压缩集合数据
collectionConfig:
blockCompressor: snappy
# 索引前缀压缩
indexConfig:
prefixCompression: true
# 启动时通过命令行参数指定缓存大小
mongod --wiredTigerCacheSizeGB 12 \
--dbpath /var/lib/mongodb \
--logpath /var/log/mongodb/mongod.log
3.2 内存调优黄金法则
生产环境的内存分配原则:
# 计算公式(适用于 Linux)
# WiredTiger Cache = (RAM - OS 预留 - 其他进程内存) * 0.6 ~ 0.7
# 举例:32GB RAM 的服务器
# OS 和内核预留:~2GB
# mongod 进程本身(连接、线程等):~2-4GB
# 其他进程(mongos、监控 agent):按需预留
# 推荐 WiredTiger Cache:16-20GB
# 对于索引密集型工作负载,可以适当降低缓存,给文件系统缓存留更多空间
# 因为 MongoDB 会利用操作系统的文件系统缓存来缓存压缩后的页面
3.3 内存压力指标与诊断
// 查看服务器状态和内存使用情况
db.serverStatus().wiredTiger.cache;
const cacheStats = db.serverStatus().wiredTiger.cache;
// 核心监控指标
const metrics = {
// 当前缓存中的数据量(字节)
bytesCurrentlyInCache: cacheStats["bytes currently in the cache"],
// 缓存中脏数据量,过高说明写入压力大
dirtyBytes: cacheStats["tracked dirty bytes in the cache"],
// 缓存页面读入次数
pagesReadIntoCache: cacheStats["pages read into cache"],
// 缓存页面写出次数
pagesWrittenFromCache: cacheStats["pages written from cache"],
// 从缓存中淘汰的页面数
pagesEvicted: cacheStats["unmodified pages evicted"],
// 缓存无法满足的读请求(需要从磁盘加载)
cacheMisses: cacheStats["pages read into cache requiring cache overflow repair"]
};
printjson(metrics);
// 计算缓存命中率(近似)
const status = db.serverStatus();
const wiredTiger = status.wiredTiger.cache;
const pagesRequested = wiredTiger["pages requested from the cache"];
const pagesRead = wiredTiger["pages read into cache"];
const hitRate = pagesRequested > 0
? ((pagesRequested - pagesRead) / pagesRequested * 100).toFixed(2)
: 100;
print("缓存命中率:", hitRate + "%");
// 低于 95% 通常意味着缓存过小或工作集远超内存
3.4 工作集(Working Set)分析
// 查看各集合的大小和文档数量,评估工作集
db.getCollectionNames().forEach(coll => {
const stats = db[coll].stats();
print(`${coll}: 文档数=${stats.count}, 数据大小=${(stats.size/1024/1024).toFixed(2)}MB, 索引大小=${(stats.totalIndexSize/1024/1024).toFixed(2)}MB`);
});
# 使用 mongostat 实时监控内存和 I/O
mongostat --host mongo-primary.example.com:27017 --username admin --password 'securePass' --authenticationDatabase admin
# 关键列说明:
# insert/query/update/delete: 每秒操作数
# getmore: cursor 获取更多数据
# command: 每秒命令数
# dirty: WT 缓存脏页百分比(超过 20% 需关注)
# used: WT 缓存使用率(接近 100% 时性能下降)
# flushes: checkpoint 频率
# vsize/res: 虚拟/物理内存
# qr|qw: 读/写队列长度(>0 说明有排队)
# ar|aw: 活跃读/写客户端数
4. 连接池优化:驱动层配置
不合理的连接池配置是导致应用延迟和 MongoDB 服务器资源耗尽的常见原因。
4.1 连接池核心参数
// Node.js MongoDB 驱动连接池配置
const { MongoClient } = require('mongodb');
const client = new MongoClient(uri, {
// 最大连接数(默认 100)
maxPoolSize: 200,
// 最小保留连接数(默认 0)
minPoolSize: 10,
// 连接最大空闲时间(毫秒),超过将被关闭(默认 0 表示不限制)
maxIdleTimeMS: 60000,
// 等待可用连接的最大时间(默认 0 表示无限等待,生产环境必须设置)
waitQueueTimeoutMS: 5000,
// 连接超时时间(默认 10000ms)
serverSelectionTimeoutMS: 30000,
// 单个操作的超时时间(MongoDB 4.4+)
timeoutMS: 30000,
// socket 连接超时
connectTimeoutMS: 10000,
// socket 读取超时
socketTimeoutMS: 0, // 0 表示不限制,长查询场景适用
// 心跳检测间隔
heartbeatFrequencyMS: 10000,
// 连接在关闭前可以复用的最大次数(防泄漏)
maxConnecting: 2 // 同时建立的连接数
});
# Python PyMongo 连接池配置
from pymongo import MongoClient
client = MongoClient(
"mongodb://user:pass@host1:27017,host2:27017/db?replicaSet=rs0",
maxPoolSize=200,
minPoolSize=10,
maxIdleTimeMS=60000,
waitQueueTimeoutMS=5000,
serverSelectionTimeoutMS=30000,
connectTimeoutMS=10000,
socketTimeoutMS=30000,
heartbeatFrequencyMS=10000
)
4.2 连接数规划公式
# 生产环境连接数计算
# 单个 MongoDB 服务器的最大连接数由系统限制和配置决定
# MongoDB 默认 maxConns 为 64000(受 ulimit -n 限制)
# 建议公式:
# maxPoolSize = (server_max_connections * 0.8) / application_instance_count
# 举例:
# MongoDB 服务器 maxConns = 64000
# 应用实例数 = 20
# 每个实例连接池 maxPoolSize = (64000 * 0.8) / 20 = 2560
# 保守设置通常为 100-500,取决于并发量
# 查看当前连接数
db.serverStatus().connections;
# 输出:{ "current": 245, "available": 63755, "totalCreated": 18500, "active": 32 }
4.3 连接泄漏排查
// 监控连接增长趋势
db.serverStatus().connections;
# 定时记录连接数,发现泄漏
# 每 30 秒采集一次连接数
while true; do
mongo --quiet --eval "printjson(db.serverStatus().connections)" \
mongodb://admin:pass@localhost:27017/admin
sleep 30
done
// 查看当前连接的客户端来源和状态
db.currentOp({ "active": true }).forEach(op => {
if (op.client) {
print(`客户端: ${op.client}, 应用: ${op.appName || 'unknown'}, 操作: ${op.op}`);
}
});
应用端最佳实践:
// Node.js:确保在应用关闭时正确关闭连接池
process.on('SIGINT', async () => {
console.log('正在关闭 MongoDB 连接池...');
await client.close();
process.exit(0);
});
// 连接字符串中直接指定连接池参数
const uri = 'mongodb://user:pass@host:27017/db?' +
'maxPoolSize=150&' +
'minPoolSize=10&' +
'waitQueueTimeoutMS=5000&' +
'maxIdleTimeMS=30000';
5. 连接字符串安全:SRV、TLS 与 authSource
生产环境的连接字符串必须包含安全相关的配置项。
5.1 标准安全连接字符串
// 基础安全连接:指定用户名、密码和认证数据库
const uri = 'mongodb://appuser:AppPassword123@mongo1.example.com:27017,mongo2.example.com:27017/production_db?replicaSet=rs0&authSource=admin';
// authSource=admin 表示用户凭证存储在 admin 数据库中
// 如果不指定 authSource,驱动默认在连接的数据库中查找用户
5.2 SRV 连接字符串
SRV 记录允许将多个节点的信息存储在 DNS 中,简化连接字符串:
# DNS SRV 记录示例(需要在 DNS 中配置)
# _mongodb._tcp.cluster0.example.com. SRV 0 0 27017 mongo1.example.com.
# _mongodb._tcp.cluster0.example.com. SRV 0 0 27017 mongo2.example.com.
# _mongodb._tcp.cluster0.example.com. SRV 0 0 27017 mongo3.example.com.
# TXT 记录可以存储默认的连接字符串选项
# cluster0.example.com. TXT "replicaSet=rs0&authSource=admin"
// 使用 SRV 连接字符串
const uri = 'mongodb+srv://appuser:AppPassword123@cluster0.example.com/production_db?retryWrites=true&w=majority';
// 驱动会:
// 1. 查询 DNS SRV 记录获取所有节点
// 2. 查询 DNS TXT 记录获取默认参数
// 3. 合并显式参数后建立连接
5.3 TLS/SSL 配置
// 启用 TLS(推荐生产环境使用)
const fs = require('fs');
const { MongoClient } = require('mongodb');
const client = new MongoClient(
'mongodb://appuser:pass@mongo1.example.com:27017/production_db?' +
'replicaSet=rs0&authSource=admin',
{
tls: true,
tlsCAFile: '/etc/ssl/certs/ca.crt',
tlsCertificateKeyFile: '/etc/ssl/certs/client.pem',
tlsAllowInvalidHostnames: false, // 生产环境必须为 false
tlsAllowInvalidCertificates: false // 生产环境必须为 false
}
);
# 使用 mongosh 通过 TLS 连接
mongosh \
"mongodb://appuser:pass@mongo1.example.com:27017/admin" \
--tls \
--tlsCAFile /etc/ssl/certs/ca.crt \
--tlsCertificateKeyFile /etc/ssl/certs/client.pem
# mongod.conf TLS 配置
net:
tls:
mode: requireTLS
certificateKeyFile: /etc/ssl/certs/server.pem
CAFile: /etc/ssl/certs/ca.crt
allowConnectionsWithoutCertificates: false
disabledProtocols: "TLS1_0,TLS1_1"
5.4 完整的生产级连接字符串
const productionUri = 'mongodb://prod_app:SecureP@ssw0rd@' +
'mongo1.db.internal:27017,' +
'mongo2.db.internal:27017,' +
'mongo3.db.internal:27017/' +
'ecommerce_db?' +
'replicaSet=rs0' +
'&authSource=admin' +
'&readPreference=primaryPreferred' +
'&w=majority' +
'&retryWrites=true' +
'&maxPoolSize=200' +
'&minPoolSize=20' +
'&waitQueueTimeoutMS=5000' +
'&serverSelectionTimeoutMS=30000' +
'&connectTimeoutMS=10000' +
'&socketTimeoutMS=30000' +
'&heartbeatFrequencyMS=10000' +
'&appname=order-service';
6. 安全加固:认证、授权与网络隔离
MongoDB 默认不启用认证,这在线上环境中是重大安全隐患。
6.1 启动认证并创建用户
# 首次启动时不启用认证(仅首次配置)
mongod --dbpath /var/lib/mongodb --port 27017
# 连接后创建管理员用户
mongosh --port 27017
// 在 admin 数据库中创建超级管理员
use admin;
db.createUser({
user: "adminRoot",
pwd: "StrongAdminPassword123!",
roles: [
{ role: "userAdminAnyDatabase", db: "admin" },
{ role: "dbAdminAnyDatabase", db: "admin" },
{ role: "readWriteAnyDatabase", db: "admin" },
{ role: "clusterAdmin", db: "admin" }
]
});
// 创建应用专用用户(最小权限原则)
use production_db;
db.createUser({
user: "app_user",
pwd: "AppSecurePass456!",
roles: [
{ role: "readWrite", db: "production_db" }
]
});
// 创建只读用户(用于报表、监控)
db.createUser({
user: "report_user",
pwd: "ReadOnlyPass789!",
roles: [
{ role: "read", db: "production_db" }
]
});
// 创建负责变更管理的用户(可以创建索引等)
db.createUser({
user: "db_admin",
pwd: "DbAdminPass012!",
roles: [
{ role: "dbAdmin", db: "production_db" },
{ role: "readWrite", db: "production_db" }
]
});
6.2 启用认证和网络安全配置
# /etc/mongod.conf
security:
authorization: enabled
# keyFile 用于副本集成员间认证(副本集/分片集群必须)
keyFile: /etc/mongodb/keyfile
# 使用 keyFile 时 TLS 内部认证更安全
# clusterAuthMode: x509
net:
port: 27017
# 仅绑定内网地址,禁止公网访问
bindIp: 127.0.0.1,10.0.1.10
# 或者使用 Unix socket(同一主机通信)
# unixDomainSocket:
# enabled: true
# pathPrefix: /var/run/mongodb
tls:
mode: requireTLS
certificateKeyFile: /etc/ssl/mongodb-server.pem
CAFile: /etc/ssl/ca.crt
# 生成 keyFile(用于副本集成员认证)
openssl rand -base64 756 > /etc/mongodb/keyfile
chmod 400 /etc/mongodb/keyfile
chown mongodb:mongodb /etc/mongodb/keyfile
6.3 角色管理与权限审计
use admin;
// 查看所有用户
db.getUsers();
// 查看特定用户的权限
db.getUser("app_user");
// 撤销不必要的权限
db.revokeRolesFromUser("app_user", [
{ role: "dbAdmin", db: "production_db" }
]);
// 添加额外权限
db.grantRolesToUser("report_user", [
{ role: "read", db: "analytics_db" }
]);
// 创建自定义角色(细粒度控制)
db.createRole({
role: "orderAppRole",
privileges: [
{
resource: { db: "production_db", collection: "orders" },
actions: [ "find", "insert", "update", "remove" ]
},
{
resource: { db: "production_db", collection: "customers" },
actions: [ "find", "insert" ]
},
{
resource: { db: "production_db", collection: "" },
actions: [ "listCollections" ]
}
],
roles: []
});
// 将自定义角色赋给用户
db.grantRolesToUser("app_user", [
{ role: "orderAppRole", db: "admin" }
]);
6.4 网络层安全加固
# 使用防火墙限制访问(ufw 示例)
sudo ufw default deny incoming
sudo ufw allow from 10.0.0.0/8 to any port 27017
sudo ufw allow from 172.16.0.0/12 to any port 27017
sudo ufw allow from 192.168.0.0/16 to any port 27017
sudo ufw enable
# iptables 规则示例
iptables -A INPUT -p tcp --dport 27017 -s 10.0.0.0/8 -j ACCEPT
iptables -A INPUT -p tcp --dport 27017 -j DROP
# 云安全组规则示例(AWS Security Group)
# Type: Custom TCP, Port: 27017, Source: sg-xxxxx(应用服务器安全组)
# 不要开放 0.0.0.0/0
# Docker Compose 网络隔离示例
version: '3.8'
services:
mongo:
image: mongo:7.0
container_name: mongo-primary
networks:
- backend
ports:
# 仅在需要外部工具访问时映射,否则不暴露
- "127.0.0.1:27017:27017"
environment:
MONGO_INITDB_ROOT_USERNAME: adminRoot
MONGO_INITDB_ROOT_PASSWORD_FILE: /run/secrets/mongo_root_pass
secrets:
- mongo_root_pass
volumes:
- mongo_data:/data/db
- ./mongod.conf:/etc/mongod.conf:ro
- ./keyfile:/etc/mongodb/keyfile:ro
command: ["mongod", "--config", "/etc/mongod.conf"]
networks:
backend:
internal: true # 禁止外部直接访问
volumes:
mongo_data:
secrets:
mongo_root_pass:
file: ./secrets/root_pass.txt
7. 备份策略:mongodump、mongorestore 与 Oplog
数据备份是数据库运维的生命线。MongoDB 提供了多种备份方案,需要根据 RPO(恢复点目标)和 RTO(恢复时间目标)选择。
7.1 逻辑备份:mongodump / mongorestore
# 全库备份(通过认证)
mongodump \
--host mongo-primary.example.com \
--port 27017 \
--username backup_user \
--password 'BackupPass123!' \
--authenticationDatabase admin \
--out /backup/mongodb/$(date +%Y%m%d_%H%M%S)
# 只备份特定数据库
mongodump \
--uri "mongodb://backup_user:pass@localhost:27017/production_db?authSource=admin" \
--out /backup/mongodb/$(date +%Y%m%d)
# 只备份特定集合
mongodump \
--db production_db \
--collection orders \
--query '{ "createdAt": { "$gte": { "$date": "2026-08-01T00:00:00Z" } } }' \
--out /backup/mongodb/orders_incremental
# 使用压缩(减少磁盘占用和传输时间)
mongodump \
--uri "mongodb://backup_user:pass@localhost:27017/?authSource=admin" \
--archive=/backup/mongodb/full_backup_$(date +%Y%m%d).gz \
--gzip
# 恢复备份
mongorestore \
--host mongo-primary.example.com \
--username restore_user \
--password 'RestorePass123!' \
--authenticationDatabase admin \
/backup/mongodb/20260813_020000/
# 从压缩归档恢复
mongorestore \
--uri "mongodb://restore_user:pass@localhost:27017/?authSource=admin" \
--archive=/backup/mongodb/full_backup_20260813.gz \
--gzip \
--drop # 恢复前删除目标集合
# 恢复单个集合到不同名称的集合
mongorestore \
--db production_db \
--collection orders_new \
/backup/mongodb/20260813/production_db/orders.bson
7.2 Oplog 备份:实现近实时恢复
# Oplog 是 MongoDB 的变更日志,可以通过 mongodump 备份
dump_oplog() {
local OUTDIR="/backup/mongodb/oplog_$(date +%Y%m%d_%H%M%S)"
mongodump \
--host mongo-primary.example.com \
--username backup_user \
--password 'BackupPass123!' \
--authenticationDatabase admin \
--db local \
--collection oplog.rs \
--out "$OUTDIR"
echo "Oplog 备份完成: $OUTDIR"
}
# 每隔 15 分钟增量备份 oplog(保留最近 4 小时)
*/15 * * * * /usr/local/bin/backup_oplog.sh
# 基于 oplog 的时间点恢复(Point-in-Time Recovery)
# 1. 先恢复到完整备份
mongorestore --db production_db /backup/mongodb/20260813_000000/production_db/
# 2. 应用 oplog 到特定时间点
mongorestore \
--oplogReplay \
--oplogLimit "1723488000:1" \
/backup/mongodb/oplog_20260813_001500/local/oplog.rs.bson
7.3 物理备份:文件系统级备份
对于 WiredTiger 存储引擎,可以使用文件系统快照或物理复制。
# LVM 快照备份(需要数据在 LVM 上)
# 1. 在 Primary 上锁定写入(副本集建议在 Secondary 上执行)
mongosh --eval "db.fsyncLock()"
# 2. 创建 LVM 快照
lvcreate --size 50G --snapshot --name mongo_snap /dev/vg0/mongo_data
# 3. 解锁
mongosh --eval "db.fsyncUnlock()"
# 4. 挂载快照并复制数据
mkdir -p /mnt/mongo_snap
mount /dev/vg0/mongo_snap /mnt/mongo_snap
rsync -avz /mnt/mongo_snap/ /backup/mongodb/physical/$(date +%Y%m%d)/
umount /mnt/mongo_snap
lvremove -y /dev/vg0/mongo_snap
# 云存储快照(AWS EBS 示例)
# 1. 锁定数据库
mongosh --host mongo-secondary.internal:27017 --eval "db.fsyncLock()"
# 2. 创建 EBS 快照
aws ec2 create-snapshot \
--volume-id vol-xxxxxxxxxxxxx \
--description "MongoDB daily backup $(date +%Y-%m-%d)"
# 3. 解锁
mongosh --host mongo-secondary.internal:27017 --eval "db.fsyncUnlock()"
7.4 自动化备份脚本
#!/bin/bash
# /usr/local/bin/mongodb_backup.sh
set -euo pipefail
BACKUP_DIR="/backup/mongodb"
DATE=$(date +%Y%m%d_%H%M%S)
RETENTION_DAYS=30
MONGO_URI="mongodb://backup_user:BackupPass123!@localhost:27017/?authSource=admin"
S3_BUCKET="s3://company-mongodb-backups"
# 创建备份目录
mkdir -p "$BACKUP_DIR"
# 全量逻辑备份
mongodump \
--uri "$MONGO_URI" \
--archive="$BACKUP_DIR/full_backup_$DATE.gz" \
--gzip \
--oplog
# 上传到 S3
aws s3 cp "$BACKUP_DIR/full_backup_$DATE.gz" "$S3_BUCKET/full/"
# 清理本地旧备份
find "$BACKUP_DIR" -name "full_backup_*.gz" -mtime +$RETENTION_DAYS -delete
# 清理 S3 旧备份
aws s3 ls "$S3_BUCKET/full/" | awk '{print $4}' | sort -r | tail -n +31 | \
xargs -I {} aws s3 rm "$S3_BUCKET/full/{}"
echo "备份完成: full_backup_$DATE.gz"
8. 日志轮转与监控
完善的日志和监控体系是提前发现问题、快速定位故障的基础。
8.1 MongoDB 日志轮转配置
# mongod.conf 日志配置
systemLog:
destination: file
path: /var/log/mongodb/mongod.log
logAppend: true
logRotate: reopen # 支持通过 SIGUSR1 或命令轮转
# 组件日志级别(生产环境建议保持默认或 warn)
component:
command:
verbosity: 0
control:
verbosity: 0
geo:
verbosity: 0
index:
verbosity: 0
network:
verbosity: 0
query:
verbosity: 0 # 调为 1 可记录慢查询详情
replication:
verbosity: 0
storage:
verbosity: 0
journal:
verbosity: 0
write:
verbosity: 0
# 手动触发日志轮转
mongosh --eval "db.adminCommand({ logRotate: 1 })"
# 或者使用 SIGUSR1 信号
kill -SIGUSR1 $(pgrep mongod)
# logrotate 配置(Linux 系统级管理)
# /etc/logrotate.d/mongodb
/var/log/mongodb/mongod.log {
daily
rotate 30
compress
delaycompress
missingok
notifempty
create 0640 mongodb mongodb
sharedscripts
postrotate
/bin/kill -SIGUSR1 $(cat /var/lib/mongodb/mongod.lock 2>/dev/null) 2>/dev/null || true
endscript
}
8.2 Prometheus 监控 exporter
# docker-compose.yml - MongoDB Exporter
version: '3.8'
services:
mongodb-exporter:
image: percona/mongodb_exporter:0.40
container_name: mongo_exporter
command:
- "--mongodb.uri=mongodb://exporter:ExporterPass@mongo-primary:27017/admin?ssl=false"
- "--collect-all"
- "--discovering-mode"
environment:
MONGODB_USER: exporter
MONGODB_PASSWORD: ExporterPass
ports:
- "9216:9216"
networks:
- monitoring
prometheus:
image: prom/prometheus:v2.53
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
- prometheus_data:/prometheus
ports:
- "9090:9090"
networks:
- monitoring
grafana:
image: grafana/grafana:10.4
volumes:
- grafana_data:/var/lib/grafana
ports:
- "3000:3000"
networks:
- monitoring
networks:
monitoring:
volumes:
prometheus_data:
grafana_data:
# prometheus.yml 配置
global:
scrape_interval: 15s
scrape_configs:
- job_name: 'mongodb'
static_configs:
- targets: ['mongodb-exporter:9216']
metrics_path: /metrics
scrape_interval: 15s
- job_name: 'mongodb-node'
static_configs:
- targets: ['node-exporter:9100']
# Prometheus 查询示例:
# MongoDB 连接使用率
(mongodb_connections{state="current"} / mongodb_connections{state="available"}) * 100
# WT 缓存使用率
mongodb_ss_wt_cache_bytes_currently_in_the_cache /
mongodb_ss_wt_cache_maximum_bytes_configured * 100
# 慢查询率(每分钟)
rate(mongodb_ss_opcounters_query[1m])
# 复制滞后时间
mongodb_mongod_replset_member_replication_lag
# 磁盘使用率
(mongodb_ss_disk_partition_space_used_bytes /
mongodb_ss_disk_partition_space_total_bytes) * 100
8.3 MongoDB Atlas 监控集成
如果使用 MongoDB Atlas,可以直接利用其内置监控和 alert 功能:
# Atlas CLI 监控查询示例
atlas metrics databases describe production_db \
--projectId 5f3e... \
--granularity PT1M \
--period PT1H
# Atlas 告警可以通过 API 或控制台配置
# 关键告警项:
# - Query Targeting: Scanned Objects / Returned > 1000
# - Connection Pool utilization > 80%
# - System CPU (User) > 80%
# - Replication lag > 60 seconds
# - Disk space utilization > 85%
# Datadog MongoDB 集成(另一种商业监控方案)
# /etc/datadog-agent/conf.d/mongo.d/conf.yaml
init_config:
instances:
- hosts:
- mongo-primary.example.com:27017
username: datadog
password: 'DatadogPass123!'
database: admin
options:
authSource: admin
serverSelectionTimeoutMS: 30000
replica_check: true
collections_indexes_stats: true
// 自定义健康检查脚本
const { MongoClient } = require('mongodb');
async function healthCheck() {
const client = new MongoClient(process.env.MONGO_URI);
try {
await client.connect();
const admin = client.db('admin');
const status = await admin.command({ serverStatus: 1 });
const checks = {
status: 'healthy',
uptime: status.uptime,
connections: status.connections.current,
connectionsAvailable: status.connections.available,
wtCacheUsed: status.wiredTiger.cache['bytes currently in the cache'],
wtCacheMax: status.wiredTiger.cache['maximum bytes configured'],
qpCounters: status.opcounters,
timestamp: new Date()
};
// 检查告警阈值
if (checks.connections / (checks.connections + checks.connectionsAvailable) > 0.8) {
checks.status = 'warning';
checks.message = 'Connection pool high utilization';
}
console.log(JSON.stringify(checks, null, 2));
process.exit(0);
} catch (err) {
console.error(JSON.stringify({ status: 'unhealthy', error: err.message }));
process.exit(1);
} finally {
await client.close();
}
}
healthCheck();
总结
MongoDB 生产环境优化是一个系统工程,需要从查询层、内存层、连接层、安全层、备份层和监控层全面考虑:
- 慢查询分析:善用
setProfilingLevel、system.profile和db.currentOp(),建立慢查询治理流程。 - 执行计划:掌握
explain("executionStats"),关注totalDocsExamined与nReturned的比例,避免全表扫描和内存排序。 - 内存调优:合理配置 WiredTiger Cache(通常为物理内存的 50%-70%),监控
bytes currently in the cache和dirty bytes。 - 连接池:根据并发量设置
maxPoolSize和minPoolSize,务必配置waitQueueTimeoutMS防止雪崩。 - 连接安全:使用 TLS、SRV 记录、
authSource和最小权限原则,拒绝明文传输和公网暴露。 - 安全加固:启用认证、创建角色分离的用户、限制
bindIp和网络访问,定期审计权限。 - 备份策略:逻辑备份(mongodump)+ Oplog 增量备份实现近实时恢复,物理备份(快照)用于快速恢复。
- 日志监控:配置 logrotate 避免磁盘占满,使用 Prometheus + Grafana 或 Atlas 建立可视化监控体系。
只有在生产环境部署前完成上述所有优化和加固工作,才能确保 MongoDB 集群在高负载下的稳定性和安全性。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。