MongoDB监控完全指南
目录
- 监控内存使用情况
- 计算工作集的大小
- 跟踪性能情况
- 跟踪剩余空间
- 监控复制情况
一、监控内存使用情况
1.1 核心知识点
MongoDB使用内存映射文件来管理数据,监控内存至关重要:
- WiredTiger缓存:MongoDB默认使用WiredTiger存储引擎
- 内存映射:操作系统管理内存与磁盘的映射
- 常驻内存:实际占用的物理内存
- 虚拟内存:进程可访问的总内存空间
1.2 监控命令详解
1.2.1 serverStatus命令
javascript
// 连接到MongoDB
// mongo --host localhost --port 27017
// 获取内存统计信息
const memStats = db.serverStatus().mem;
print("=== 内存使用统计 ===");
print(`常驻内存: ${memStats.resident} MB`);
print(`虚拟内存: ${memStats.virtual} MB`);
print(`内存映射: ${memStats.mapped} MB`);
print(`映射文件数量: ${memStats.mappedWithJournal}`);
// 获取WiredTiger缓存统计
const wiredTigerStats = db.serverStatus().wiredTiger.cache;
print("\n=== WiredTiger缓存统计 ===");
print(`当前缓存大小: ${(wiredTigerStats['current bytes'] / 1024 / 1024).toFixed(2)} MB`);
print(`最大缓存配置: ${(wiredTigerStats['maximum bytes configured'] / 1024 / 1024).toFixed(2)} MB`);
print(`脏数据字节: ${(wiredTigerStats['tracked dirty bytes in the cache'] / 1024 / 1024).toFixed(2)} MB`);
print(`读入缓存页数: ${wiredTigerStats['bytes read into cache']}`);
print(`从缓存写出页数: ${wiredTigerStats['bytes written from cache']}`);
1.2.2 mongostat工具
bash
# 实时监控内存使用(每秒输出一次)
mongostat --host localhost:27017 --rowcount 10 1
# 输出说明:
# res: 常驻内存
# virt: 虚拟内存
# mapped: 映射内存
# non-mapped: 未映射内存
1.2.3 操作系统级别监控脚本
bash
#!/bin/bash
# mongodb_memory_monitor.sh
echo "=== MongoDB进程内存使用 ==="
# 获取MongoDB进程PID
MONGOD_PID=$(pgrep mongod)
# 使用ps命令查看内存
ps -p $MONGOD_PID -o pid,vsz,rss,comm,pmem
# 使用top命令(非交互模式)
top -p $MONGOD_PID -b -n 1 | grep mongod
# 使用/proc文件系统
echo -e "\n=== 详细内存信息 ==="
cat /proc/$MONGOD_PID/status | grep -E "VmRSS|VmSize|VmPeak"
1.3 内存告警脚本
javascript
// mongodb_memory_alert.js
// 内存监控告警脚本
function monitorMemory() {
const memStats = db.serverStatus().mem;
const cacheStats = db.serverStatus().wiredTiger.cache;
// 设置告警阈值
const residentThreshold = 80; // 80%物理内存
const dirtyCacheThreshold = 25; // 25%脏数据
// 获取系统总内存
const totalMem = (memStats.resident / memStats.resident) * 100; // 实际需要获取系统总内存
// 检查常驻内存
if (memStats.resident > residentThreshold * 1024) { // 假设总内存为16GB
print(`⚠️ 告警:常驻内存过高 ${memStats.resident}MB`);
// 可以发送邮件或写入日志
}
// 检查脏数据比例
const maxCache = cacheStats['maximum bytes configured'];
const dirtyBytes = cacheStats['tracked dirty bytes in the cache'];
const dirtyPercent = (dirtyBytes / maxCache) * 100;
if (dirtyPercent > dirtyCacheThreshold) {
print(`⚠️ 告警:脏缓存比例过高 ${dirtyPercent.toFixed(2)}%`);
print("建议:检查写入负载或增加缓存大小");
}
// 检查页面错误
const pageFaults = db.serverStatus().extra_info.page_faults;
print(`页面错误数: ${pageFaults}`);
if (pageFaults > 1000) {
print("⚠️ 页面错误过多,可能存在内存不足");
}
}
// 执行监控
monitorMemory();
// 定时执行(每60秒)
setInterval(monitorMemory, 60000);
二、计算工作集的大小
2.1 核心知识点
工作集是MongoDB频繁访问的数据和索引的总和:
- 工作集计算:索引 + 常用数据
- 内存需求:工作集应小于可用RAM
- 热数据识别:最近访问频率高的数据
- 冷数据识别:长期未访问的数据
2.2 计算索引大小
javascript
// calculate_working_set.js
// 计算工作集大小的完整脚本
// 1. 计算所有索引的总大小
function calculateTotalIndexSize() {
let totalIndexSize = 0;
const databases = db.adminCommand({ listDatabases: 1 }).databases;
databases.forEach(dbInfo => {
const currentDb = db.getSiblingDB(dbInfo.name);
const collections = currentDb.getCollectionInfos();
collections.forEach(coll => {
const stats = currentDb.getCollection(coll.name).stats();
if (stats.indexSizes) {
for (let indexName in stats.indexSizes) {
totalIndexSize += stats.indexSizes[indexName];
print(`${dbInfo.name}.${coll.name}.${indexName}: ${(stats.indexSizes[indexName] / 1024 / 1024).toFixed(2)} MB`);
}
}
});
});
return totalIndexSize;
}
// 2. 计算热数据大小
function calculateHotDataSize(daysThreshold = 7) {
let hotDataSize = 0;
const databases = db.adminCommand({ listDatabases: 1 }).databases;
databases.forEach(dbInfo => {
const currentDb = db.getSiblingDB(dbInfo.name);
const collections = currentDb.getCollectionInfos();
collections.forEach(coll => {
const collName = coll.name;
const collection = currentDb.getCollection(collName);
// 查找最近访问的数据(假设有lastAccessTime字段)
try {
const recentDocs = collection.find({
lastAccessTime: { $gte: new Date(Date.now() - daysThreshold * 24 * 60 * 60 * 1000) }
}).limit(10000);
// 估算大小
recentDocs.forEach(doc => {
hotDataSize += Object.bsonsize(doc);
});
} catch(e) {
// 如果没有时间戳字段,使用采样
const sampleDocs = collection.find().limit(1000);
let avgDocSize = 0;
let count = 0;
sampleDocs.forEach(doc => {
avgDocSize += Object.bsonsize(doc);
count++;
});
avgDocSize = avgDocSize / count;
const totalDocs = collection.countDocuments();
// 假设20%的数据是热数据
hotDataSize += (totalDocs * 0.2 * avgDocSize);
}
});
});
return hotDataSize;
}
// 3. 计算工作集大小
function calculateWorkingSet() {
print("=== 计算工作集大小 ===\n");
// 获取索引总大小
const indexSize = calculateTotalIndexSize();
print(`\n总索引大小: ${(indexSize / 1024 / 1024).toFixed(2)} MB`);
// 获取热数据大小
const hotDataSize = calculateHotDataSize(7);
print(`热数据大小: ${(hotDataSize / 1024 / 1024).toFixed(2)} MB`);
// 工作集 = 索引 + 热数据
const workingSet = indexSize + hotDataSize;
print(`\n=== 工作集总大小 ===`);
print(`工作集大小: ${(workingSet / 1024 / 1024).toFixed(2)} MB`);
print(`建议内存大小: ${(workingSet / 1024 / 1024 / 1024).toFixed(2)} GB`);
// 获取当前内存
const memStats = db.serverStatus().mem;
print(`\n当前常驻内存: ${memStats.resident} MB`);
if (memStats.resident < (workingSet / 1024 / 1024)) {
print("⚠️ 警告:当前内存小于工作集,可能导致性能问题");
} else {
print("✅ 内存配置合理");
}
return workingSet;
}
// 执行计算
calculateWorkingSet();
2.3 使用MongoDB聚合框架分析访问模式
javascript
// access_pattern_analysis.js
// 分析数据访问模式以确定工作集
// 创建访问日志集合(需要开启分析日志)
db.setProfilingLevel(2); // 开启数据库分析,记录所有操作
// 分析最近访问的集合
function analyzeAccessPattern() {
// 查看系统.profile集合
const profileCollection = db.getSiblingDB("test").system.profile;
// 分析最近1小时的查询模式
const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000);
const accessStats = profileCollection.aggregate([
{
$match: {
ts: { $gte: oneHourAgo },
op: { $in: ["query", "find", "getmore"] }
}
},
{
$group: {
_id: {
ns: "$ns",
op: "$op"
},
count: { $sum: 1 },
avgDocsReturned: { $avg: "$nreturned" },
avgDuration: { $avg: "$millis" }
}
},
{
$project: {
namespace: "$_id.ns",
operation: "$_id.op",
frequency: "$count",
avgDocsReturned: 1,
avgDuration: 1,
estimatedWorkingSetImpact: {
$multiply: ["$count", "$avgDocsReturned"]
}
}
},
{
$sort: { frequency: -1 }
}
]).toArray();
print("=== 数据访问模式分析 ===");
accessStats.forEach(stat => {
print(`命名空间: ${stat.namespace}`);
print(` 操作次数: ${stat.frequency}`);
print(` 平均返回文档数: ${stat.avgDocsReturned.toFixed(2)}`);
print(` 平均耗时: ${stat.avgDuration.toFixed(2)}ms`);
print(` 工作集影响指数: ${stat.estimatedWorkingSetImpact}`);
print("---");
});
}
// 关闭分析(生产环境注意性能影响)
// db.setProfilingLevel(0);
analyzeAccessPattern();
三、跟踪性能情况
3.1 核心性能指标
- 操作延迟:读写操作的平均时间
- 锁百分比:数据库锁使用率
- 队列长度:等待执行的请求数
- 连接数:当前活跃连接
- QPS/TPS:每秒查询/事务数
3.2 性能监控脚本
javascript
// performance_monitor.js
// 全面的性能监控脚本
class MongoDBPerformanceMonitor {
constructor(interval = 5000) {
this.interval = interval;
this.baseline = null;
}
// 获取基础性能数据
getPerformanceMetrics() {
const stats = db.serverStatus();
const replStats = db.getReplicationInfo ? db.getReplicationInfo() : null;
return {
// 操作计数器
opCounters: stats.opcounters,
// 连接信息
connections: stats.connections,
// 锁信息
locks: stats.locks,
// 全局锁
globalLock: stats.globalLock,
// 网络统计
network: stats.network,
// 操作延迟
opLatencies: stats.opLatencies,
// 数据库统计
dbStats: db.stats()
};
}
// 监控QPS
monitorQPS() {
let prevOps = null;
return setInterval(() => {
const currentOps = db.serverStatus().opcounters;
if (prevOps) {
const qps = {
insert: currentOps.insert - prevOps.insert,
query: currentOps.query - prevOps.query,
update: currentOps.update - prevOps.update,
delete: currentOps.delete - prevOps.delete,
getmore: currentOps.getmore - prevOps.getmore,
command: currentOps.command - prevOps.command
};
print("\n=== 每秒操作数 (QPS) ===");
for (let [op, count] of Object.entries(qps)) {
if (count > 0) {
print(`${op}: ${count} ops/sec`);
}
}
// QPS告警
if (qps.query > 1000) {
print("⚠️ 高查询负载!");
}
}
prevOps = currentOps;
}, 1000);
}
// 监控锁使用率
monitorLockPercentage() {
return setInterval(() => {
const stats = db.serverStatus();
const globalLock = stats.globalLock;
const locks = stats.locks;
// 计算锁使用率
const totalTime = globalLock.totalTime;
const lockTime = globalLock.lockTime;
const lockPercentage = (lockTime / totalTime) * 100;
print(`\n=== 锁使用率 ===`);
print(`全局锁使用率: ${lockPercentage.toFixed(2)}%`);
if (lockPercentage > 30) {
print("⚠️ 锁争用严重,可能存在慢查询");
}
// 显示各数据库锁信息
for (let [dbName, lockInfo] of Object.entries(locks)) {
if (lockInfo.acquireCount && lockInfo.acquireWaitCount) {
const waitRatio = (lockInfo.acquireWaitCount / lockInfo.acquireCount) * 100;
if (waitRatio > 10) {
print(`⚠️ ${dbName} 锁等待率: ${waitRatio.toFixed(2)}%`);
}
}
}
}, this.interval);
}
// 监控操作延迟
monitorLatency() {
return setInterval(() => {
const latencies = db.serverStatus().opLatencies;
print("\n=== 操作延迟统计 (微秒) ===");
const ops = {
reads: latencies.reads,
writes: latencies.writes,
commands: latencies.commands
};
for (let [op, data] of Object.entries(ops)) {
if (data && data.latency) {
const avgLatency = data.latency / data.ops;
print(`${op}:`);
print(` 总操作数: ${data.ops}`);
print(` 平均延迟: ${avgLatency.toFixed(2)} μs`);
print(` 总延迟: ${data.latency} μs`);
// 延迟告警
if (avgLatency > 100000) { // 100ms
print(` ⚠️ ${op} 操作延迟过高!`);
}
}
}
}, this.interval);
}
// 监控连接数
monitorConnections() {
return setInterval(() => {
const conn = db.serverStatus().connections;
print("\n=== 连接状态 ===");
print(`当前连接数: ${conn.current}`);
print(`可用连接数: ${conn.available}`);
print(`总创建连接数: ${conn.totalCreated}`);
const usagePercentage = (conn.current / (conn.current + conn.available)) * 100;
print(`连接使用率: ${usagePercentage.toFixed(2)}%`);
if (usagePercentage > 80) {
print("⚠️ 连接数接近上限!");
}
}, this.interval);
}
// 监控慢查询
monitorSlowQueries(thresholdMs = 100) {
// 设置慢查询阈值
db.setProfilingLevel(1, thresholdMs);
return setInterval(() => {
const slowQueries = db.system.profile.find({
millis: { $gt: thresholdMs },
ts: { $gt: new Date(Date.now() - 5 * 60 * 1000) }
}).toArray();
if (slowQueries.length > 0) {
print(`\n=== 最近5分钟慢查询 (${slowQueries.length}条) ===`);
slowQueries.forEach(query => {
print(`操作: ${query.op}`);
print(`命名空间: ${query.ns}`);
print(`耗时: ${query.millis}ms`);
print(`查询: ${JSON.stringify(query.query)}`);
print(`---`);
});
}
}, 300000); // 每5分钟检查一次
}
// 生成性能报告
generatePerformanceReport() {
const metrics = this.getPerformanceMetrics();
print("\n========== MongoDB性能报告 ==========");
// 连接分析
print("\n【连接状态】");
print(`活跃连接: ${metrics.connections.current}`);
print(`连接池大小: ${metrics.connections.available}`);
// 操作分析
print("\n【操作统计】");
const totalOps = Object.values(metrics.opCounters).reduce((a, b) => a + b, 0);
print(`总操作数: ${totalOps}`);
// 锁分析
print("\n【锁统计】");
const lockPercentage = (metrics.globalLock.lockTime / metrics.globalLock.totalTime) * 100;
print(`全局锁使用率: ${lockPercentage.toFixed(2)}%`);
print(`活跃客户端: ${metrics.globalLock.activeClients.total}`);
print(` 读者: ${metrics.globalLock.activeClients.readers}`);
print(` 写者: ${metrics.globalLock.activeClients.writers}`);
// 网络分析
print("\n【网络流量】");
print(`网络输入: ${(metrics.network.bytesIn / 1024 / 1024).toFixed(2)} MB`);
print(`网络输出: ${(metrics.network.bytesOut / 1024 / 1024).toFixed(2)} MB`);
print(`请求数: ${metrics.network.numRequests}`);
print("\n======================================\n");
}
// 启动完整监控
startFullMonitoring() {
print("启动MongoDB性能监控...\n");
// 启动各个监控器
this.qpsMonitor = this.monitorQPS();
this.lockMonitor = this.monitorLockPercentage();
this.latencyMonitor = this.monitorLatency();
this.connectionMonitor = this.monitorConnections();
this.slowQueryMonitor = this.monitorSlowQueries(100);
// 每30秒生成一次报告
this.reportInterval = setInterval(() => {
this.generatePerformanceReport();
}, 30000);
print("监控已启动,按 Ctrl+C 停止\n");
}
// 停止监控
stopMonitoring() {
clearInterval(this.qpsMonitor);
clearInterval(this.lockMonitor);
clearInterval(this.latencyMonitor);
clearInterval(this.connectionMonitor);
clearInterval(this.slowQueryMonitor);
clearInterval(this.reportInterval);
print("\n监控已停止");
}
}
// 使用示例
const monitor = new MongoDBPerformanceMonitor(5000);
monitor.startFullMonitoring();
// 运行60秒后停止
setTimeout(() => {
monitor.stopMonitoring();
}, 60000);
3.3 使用mongotop和mongostat
bash
#!/bin/bash
# mongodb_performance_tools.sh
echo "=== 使用mongotop监控集合级别性能 ==="
# 每5秒输出一次,持续10次
mongotop --host localhost:27017 --rowcount 10 5
echo -e "\n=== 使用mongostat监控实例级别性能 ==="
# 每2秒输出一次,持续20次
mongostat --host localhost:27017 --rowcount 20 2 \
--discover \
--noheaders
# 输出到CSV文件
mongostat --host localhost:27017 --rowcount 60 1 \
--csv > mongodb_stats_$(date +%Y%m%d_%H%M%S).csv
四、跟踪剩余空间
4.1 核心知识点
- 数据文件:MongoDB的数据存储文件
- 命名空间文件:存储集合和索引的元数据
- 预分配文件:提前分配的空间
- 碎片空间:删除数据后未回收的空间
4.2 空间监控脚本
javascript
// space_monitor.js
// 磁盘空间监控脚本
class DiskSpaceMonitor {
constructor(alertThreshold = 80) { // 80%阈值
this.alertThreshold = alertThreshold;
}
// 获取数据库级别空间使用
getDatabaseSpaceUsage() {
const adminDb = db.getSiblingDB("admin");
const dbs = adminDb.runCommand({ listDatabases: 1 }).databases;
let totalDataSize = 0;
let totalIndexSize = 0;
let totalStorageSize = 0;
print("\n=== 数据库空间使用详情 ===");
print("数据库名称\t\t数据大小(MB)\t索引大小(MB)\t存储大小(MB)\t空闲空间(MB)");
print("=".repeat(80));
dbs.forEach(dbInfo => {
const currentDb = db.getSiblingDB(dbInfo.name);
const stats = currentDb.stats();
const dataSizeMB = (stats.dataSize / 1024 / 1024).toFixed(2);
const indexSizeMB = (stats.indexSize / 1024 / 1024).toFixed(2);
const storageSizeMB = (stats.storageSize / 1024 / 1024).toFixed(2);
const freeSpaceMB = (stats.storageSize - stats.dataSize) / 1024 / 1024;
totalDataSize += stats.dataSize;
totalIndexSize += stats.indexSize;
totalStorageSize += stats.storageSize;
print(`${dbInfo.name.padEnd(20)}\t${dataSizeMB}\t\t${indexSizeMB}\t\t${storageSizeMB}\t\t${freeSpaceMB.toFixed(2)}`);
});
print("=".repeat(80));
print(`总计:\t\t\t${(totalDataSize/1024/1024).toFixed(2)}\t\t${(totalIndexSize/1024/1024).toFixed(2)}\t\t${(totalStorageSize/1024/1024).toFixed(2)}`);
return { totalDataSize, totalIndexSize, totalStorageSize };
}
// 获取集合级别空间使用
getCollectionSpaceUsage(databaseName) {
const currentDb = db.getSiblingDB(databaseName);
const collections = currentDb.getCollectionInfos();
print(`\n=== ${databaseName} 数据库集合空间详情 ===`);
print("集合名称\t\t文档数\t\t数据大小(MB)\t索引大小(MB)\t空闲空间(MB)\t碎片率(%)");
print("=".repeat(90));
collections.forEach(collInfo => {
const collName = collInfo.name;
const stats = currentDb.getCollection(collName).stats();
const dataSizeMB = (stats.size / 1024 / 1024).toFixed(2);
const indexSizeMB = (stats.totalIndexSize / 1024 / 1024).toFixed(2);
const storageSizeMB = (stats.storageSize / 1024 / 1024).toFixed(2);
const freeSpaceMB = (stats.storageSize - stats.size) / 1024 / 1024;
const fragmentation = ((stats.storageSize - stats.size) / stats.storageSize * 100).toFixed(2);
print(`${collName.padEnd(20)}\t${stats.count}\t\t${dataSizeMB}\t\t${indexSizeMB}\t\t${freeSpaceMB.toFixed(2)}\t\t${fragmentation}`);
// 高碎片率告警
if (fragmentation > 30) {
print(` ⚠️ ${collName} 碎片率过高,建议执行compact`);
}
});
}
// 检查数据文件预分配
checkDataFiles() {
// 使用listFiles命令查看数据目录(需要文件系统访问)
const dbPath = db.adminCommand({ getCmdLineOpts: 1 }).parsed.storage.dbPath;
print(`\n=== 数据文件信息 ===");
print(`数据目录: ${dbPath}`);
// 模拟文件检查(实际需要fs模块)
print("建议检查以下文件:");
print("- .wt 文件: WiredTiger数据文件");
print("- journal/: 预写日志文件");
print("- diagnostic.data/: 诊断数据");
}
// 预测空间增长
predictGrowth(daysHistory = 30) {
print("\n=== 空间增长预测 ===");
// 获取当前数据库大小
const currentStats = db.stats();
const currentSize = currentStats.dataSize;
// 获取历史数据(需要从监控系统获取)
// 这里模拟增长计算
const estimatedDailyGrowth = currentSize * 0.05; // 假设5%日增长
const growthPerWeek = estimatedDailyGrowth * 7;
const growthPerMonth = estimatedDailyGrowth * 30;
print(`当前数据大小: ${(currentSize/1024/1024/1024).toFixed(2)} GB`);
print(`预估日增长: ${(estimatedDailyGrowth/1024/1024).toFixed(2)} MB`);
print(`预估周增长: ${(growthPerWeek/1024/1024).toFixed(2)} MB`);
print(`预估月增长: ${(growthPerMonth/1024/1024/1024).toFixed(2)} GB`);
// 检查磁盘剩余空间
const diskSpace = this.checkDiskSpace();
if (diskSpace) {
const daysUntilFull = (diskSpace.free) / (estimatedDailyGrowth / 1024 / 1024 / 1024);
print(`\n⚠️ 预计 ${daysUntilFull.toFixed(0)} 天后磁盘空间不足`);
}
}
// 检查磁盘空间(模拟)
checkDiskSpace() {
// 实际环境中需要使用exec或文件系统API
return {
total: 500, // GB
used: 300, // GB
free: 200 // GB
};
}
// 自动回收空间建议
suggestSpaceReclamation() {
print("\n=== 空间回收建议 ===");
const databases = db.adminCommand({ listDatabases: 1 }).databases;
databases.forEach(dbInfo => {
const currentDb = db.getSiblingDB(dbInfo.name);
const collections = currentDb.getCollectionInfos();
collections.forEach(collInfo => {
const stats = currentDb.getCollection(collInfo.name).stats();
const fragmentation = (stats.storageSize - stats.size) / stats.storageSize;
if (fragmentation > 0.3) {
print(`\n集合: ${dbInfo.name}.${collInfo.name}`);
print(` 碎片率: ${(fragmentation * 100).toFixed(2)}%`);
print(` 建议执行: db.getSiblingDB("${dbInfo.name}").${collInfo.name}.runCommand("compact")`);
}
// 检查TTL索引
const indexes = currentDb.getCollection(collInfo.name).getIndexes();
const hasTTL = indexes.some(idx => idx.expireAfterSeconds);
if (!hasTTL && stats.count > 1000000) {
print(`\n建议为 ${dbInfo.name}.${collInfo.name} 创建TTL索引以自动清理旧数据`);
}
});
});
}
// 生成空间报告
generateSpaceReport() {
print("\n" + "=".repeat(60));
print("MongoDB磁盘空间监控报告");
print("=".repeat(60));
this.getDatabaseSpaceUsage();
this.predictGrowth();
this.suggestSpaceReclamation();
// 检查是否需要告警
const totalSize = this.getDatabaseSpaceUsage().totalStorageSize;
const diskSpace = this.checkDiskSpace();
if (diskSpace) {
const usagePercent = (diskSpace.used / diskSpace.total) * 100;
if (usagePercent > this.alertThreshold) {
print(`\n🚨 紧急告警:磁盘使用率 ${usagePercent.toFixed(2)}% 超过阈值 ${this.alertThreshold}%`);
print("建议立即采取行动:");
print("1. 清理历史数据");
print("2. 执行compact命令回收空间");
print("3. 考虑增加磁盘容量");
}
}
print("=".repeat(60));
}
}
// 使用示例
const spaceMonitor = new DiskSpaceMonitor(75);
spaceMonitor.generateSpaceReport();
spaceMonitor.getCollectionSpaceUsage("test");
4.3 定期清理和空间回收
javascript
// space_management.js
// 空间管理自动化脚本
// 1. 自动清理过期数据
function autoCleanupExpiredData() {
print("开始清理过期数据...");
const databases = db.adminCommand({ listDatabases: 1 }).databases;
let cleanedCount = 0;
databases.forEach(dbInfo => {
const currentDb = db.getSiblingDB(dbInfo.name);
const collections = currentDb.getCollectionInfos();
collections.forEach(collInfo => {
// 检查是否有TTL索引
const indexes = currentDb.getCollection(collInfo.name).getIndexes();
const ttlIndex = indexes.find(idx => idx.expireAfterSeconds);
if (ttlIndex) {
print(`集合 ${dbInfo.name}.${collInfo.name} 已配置TTL自动清理`);
} else {
// 手动清理超过90天的数据
const ninetyDaysAgo = new Date();
ninetyDaysAgo.setDate(ninetyDaysAgo.getDate() - 90);
const result = currentDb.getCollection(collInfo.name).deleteMany({
createdAt: { $lt: ninetyDaysAgo }
});
cleanedCount += result.deletedCount;
if (result.deletedCount > 0) {
print(`清理 ${dbInfo.name}.${collInfo.name}: ${result.deletedCount} 条记录`);
}
}
});
});
print(`总共清理 ${cleanedCount} 条记录`);
return cleanedCount;
}
// 2. 执行空间压缩
function compactDatabase(databaseName) {
print(`开始压缩数据库: ${databaseName}`);
const currentDb = db.getSiblingDB(databaseName);
const collections = currentDb.getCollectionInfos();
collections.forEach(collInfo => {
const collName = collInfo.name;
print(`压缩集合: ${databaseName}.${collName}`);
try {
// 执行compact命令
const result = currentDb.runCommand({
compact: collName,
force: true // 主节点上执行(慎用)
});
if (result.ok) {
print(`✅ ${collName} 压缩成功`);
} else {
print(`❌ ${collName} 压缩失败: ${result.errmsg}`);
}
} catch(e) {
print(`压缩错误: ${e.message}`);
}
});
}
// 3. 设置定期清理任务
function scheduleCleanupTask() {
// 使用MongoDB的调度(需要创建单独的任务集合)
const tasksCollection = db.getSiblingDB("admin").tasks;
// 创建清理任务
tasksCollection.insertOne({
taskType: "cleanup",
scheduledTime: new Date(Date.now() + 24 * 60 * 60 * 1000), // 24小时后
status: "pending",
createdAt: new Date()
});
print("已创建定时清理任务");
}
// 4. 监控碎片并自动压缩
function monitorAndCompact() {
const highFragmentationCollections = [];
const databases = db.adminCommand({ listDatabases: 1 }).databases;
databases.forEach(dbInfo => {
const currentDb = db.getSiblingDB(dbInfo.name);
const collections = currentDb.getCollectionInfos();
collections.forEach(collInfo => {
const stats = currentDb.getCollection(collInfo.name).stats();
const fragmentation = (stats.storageSize - stats.size) / stats.storageSize;
if (fragmentation > 0.3 && stats.size > 1024 * 1024 * 100) { // 大于100MB且碎片率>30%
highFragmentationCollections.push({
database: dbInfo.name,
collection: collInfo.name,
fragmentation: fragmentation,
size: stats.size
});
}
});
});
if (highFragmentationCollections.length > 0) {
print("发现高碎片率集合:");
highFragmentationCollections.forEach(coll => {
print(`- ${coll.database}.${coll.collection}: ${(coll.fragmentation * 100).toFixed(2)}%`);
});
// 自动压缩(需谨慎)
// highFragmentationCollections.forEach(coll => {
// compactDatabase(coll.database);
// });
}
}
// 执行空间管理
// autoCleanupExpiredData();
// monitorAndCompact();
五、监控复制情况
5.1 核心知识点
- 复制延迟:从节点落后主节点的时间
- Oplog:操作日志,记录所有写操作
- 心跳检测:监控副本集成员健康状态
- 选举过程:主节点选举的时间和结果
5.2 复制监控脚本
javascript
// replication_monitor.js
// 复制集监控完整脚本
class ReplicationMonitor {
constructor(alertThresholdSeconds = 10) {
this.alertThreshold = alertThresholdSeconds;
}
// 获取复制集状态
getReplicaSetStatus() {
const status = rs.status();
print("\n=== 复制集状态 ===");
print(`复制集名称: ${status.set}`);
print(`当前时间: ${new Date(status.date).toLocaleString()}`);
print(`我的状态: ${status.myState}`);
// 成员状态映射
const stateMap = {
0: "STARTUP",
1: "PRIMARY",
2: "SECONDARY",
3: "RECOVERING",
4: "FATAL",
5: "STARTUP2",
6: "UNKNOWN",
7: "ARBITER",
8: "DOWN",
9: "ROLLBACK"
};
print("\n成员列表:");
print("=".repeat(100));
print("名称\t\t\t状态\t\t延迟(秒)\t操作时间\t\t健康");
print("=".repeat(100));
status.members.forEach(member => {
const state = stateMap[member.state] || "UNKNOWN";
const latency = member.pingMs;
const optimeDate = new Date(member.optimeDate).toLocaleString();
const health = member.health === 1 ? "✅" : "❌";
print(`${member.name.padEnd(20)}\t${state.padEnd(12)}\t${latency}\t\t${optimeDate}\t${health}`);
});
return status;
}
// 监控复制延迟
monitorReplicationLag() {
const status = rs.status();
let maxLag = 0;
let laggingMembers = [];
// 获取主节点的optime
const primary = status.members.find(m => m.state === 1);
if (!primary) {
print("❌ 未找到主节点");
return;
}
const primaryOptime = primary.optime;
// 计算每个从节点的延迟
status.members.forEach(member => {
if (member.state === 2) { // SECONDARY
const lag = (primaryOptime.ts - member.optime.ts) / 1000; // 转换为秒
if (lag > maxLag) {
maxLag = lag;
}
if (lag > this.alertThreshold) {
laggingMembers.push({
name: member.name,
lag: lag,
state: member.state
});
}
print(`${member.name} 复制延迟: ${lag.toFixed(2)} 秒`);
}
});
// 发送告警
if (laggingMembers.length > 0) {
print("\n🚨 复制延迟告警:");
laggingMembers.forEach(member => {
print(`- ${member.name}: ${member.lag.toFixed(2)} 秒延迟`);
});
// 分析延迟原因
this.analyzeReplicationLag(laggingMembers);
}
return { maxLag, laggingMembers };
}
// 分析复制延迟原因
analyzeReplicationLag(laggingMembers) {
print("\n=== 复制延迟分析 ===");
laggingMembers.forEach(member => {
print(`\n分析 ${member.name}:`);
// 连接到从节点检查
const slaveConn = new Mongo(member.name);
const slaveDb = slaveConn.getDB("admin");
// 检查从节点负载
const slaveStats = slaveDb.serverStatus();
print(` CPU使用率: ${slaveStats.systemInfo?.cpuLoadAverage || "N/A"}`);
print(` 连接数: ${slaveStats.connections.current}`);
// 检查从节点网络
const networkStats = slaveStats.network;
print(` 网络输入: ${(networkStats.bytesIn / 1024 / 1024).toFixed(2)} MB`);
print(` 网络输出: ${(networkStats.bytesOut / 1024 / 1024).toFixed(2)} MB`);
// 检查从节点磁盘
const slaveDbStats = slaveDb.stats();
print(` 数据大小: ${(slaveDbStats.dataSize / 1024 / 1024 / 1024).toFixed(2)} GB`);
// 建议
if (slaveStats.connections.current > 1000) {
print(" 💡 建议: 连接数过高,考虑增加连接池大小或应用层限流");
}
if (networkStats.bytesIn > 100 * 1024 * 1024) { // 100MB
print(" 💡 建议: 网络负载过高,检查是否有大量数据同步");
}
});
}
// 监控Oplog
monitorOplog() {
print("\n=== Oplog监控 ===");
const localDb = db.getSiblingDB("local");
const oplogRS = localDb.getCollection("oplog.rs");
// 获取oplog统计
const oplogStats = localDb.stats();
const firstEntry = oplogRS.find().sort({ $natural: 1 }).limit(1).next();
const lastEntry = oplogRS.find().sort({ $natural: -1 }).limit(1).next();
// 计算oplog大小
const oplogSize = oplogStats.storageSize;
print(`Oplog总大小: ${(oplogSize / 1024 / 1024).toFixed(2)} MB`);
// 计算oplog时间范围
const firstTime = new Date(firstEntry.ts.t * 1000);
const lastTime = new Date(lastEntry.ts.t * 1000);
const timeSpan = (lastTime - firstTime) / 1000 / 3600; // 小时
print(`Oplog覆盖时间: ${firstTime.toLocaleString()} 至 ${lastTime.toLocaleString()}`);
print(`Oplog时长: ${timeSpan.toFixed(2)} 小时`);
// 检查oplog是否足够
if (timeSpan < 24) {
print(`⚠️ Oplog窗口仅 ${timeSpan.toFixed(2)} 小时,小于24小时,建议增加oplog大小`);
// 增加oplog大小的建议
print("💡 建议执行:");
print(" use local");
print(" db.oplog.rs.stats()");
print(" # 修改oplog大小需要重新配置复制集");
}
// 统计oplog操作类型
const sampleOps = oplogRS.find().limit(1000).toArray();
const opCounts = {};
sampleOps.forEach(op => {
opCounts[op.op] = (opCounts[op.op] || 0) + 1;
});
print("\nOplog操作分布(采样1000条):");
const opMap = {
'i': '插入',
'u': '更新',
'd': '删除',
'c': '命令',
'n': '无操作'
};
for (let [op, count] of Object.entries(opCounts)) {
const percent = (count / 1000 * 100).toFixed(2);
print(` ${opMap[op] || op}: ${count} (${percent}%)`);
}
}
// 检查复制集健康度
checkReplicationHealth() {
print("\n=== 复制集健康检查 ===");
const status = rs.status();
let healthy = true;
const issues = [];
// 检查成员数量
if (status.members.length < 3) {
issues.push("成员数量少于3,不具备高可用性");
healthy = false;
}
// 检查主节点
const primary = status.members.find(m => m.state === 1);
if (!primary) {
issues.push("无主节点,复制集不可用");
healthy = false;
} else {
print(`✅ 主节点: ${primary.name}`);
}
// 检查从节点健康
const secondaries = status.members.filter(m => m.state === 2);
const unhealthySecondaries = secondaries.filter(m => m.health !== 1);
if (unhealthySecondaries.length > 0) {
issues.push(`${unhealthySecondaries.length} 个从节点不健康`);
healthy = false;
}
// 检查投票权
const votingMembers = status.members.filter(m => m.state !== 7); // 排除arbiter
if (votingMembers.length % 2 === 0) {
issues.push("投票节点数为偶数,建议添加仲裁节点");
}
// 输出健康报告
if (healthy) {
print("✅ 复制集健康状况良好");
} else {
print("❌ 发现以下问题:");
issues.forEach(issue => {
print(` - ${issue}`);
});
}
return { healthy, issues };
}
// 监控选举
monitorElections() {
print("\n=== 选举监控 ===");
// 从日志或系统集合中获取选举信息
const localDb = db.getSiblingDB("local");
// 查看选举历史(需要启用系统分析)
try {
const electionLog = localDb.getCollection("system.replset").find({
msg: { $regex: "election", $options: "i" }
}).limit(10).toArray();
if (electionLog.length > 0) {
print("最近的选举事件:");
electionLog.forEach(event => {
print(` ${event.msg} - ${new Date(event.ts).toLocaleString()}`);
});
} else {
print("未发现最近的选举事件");
}
} catch(e) {
print("无法获取选举日志");
}
// 检查选举优先级
const conf = rs.conf();
print("\n成员选举优先级:");
conf.members.forEach(member => {
print(`${member.host}: 优先级 ${member.priority}`);
if (member.priority === 0) {
print(` ⚠️ ${member.host} 永远不会成为主节点`);
}
});
}
// 执行完整复制监控
fullReplicationMonitoring() {
print("\n" + "=".repeat(60));
print("MongoDB复制集监控报告");
print("=".repeat(60));
// 检查是否在复制集中
try {
rs.status();
} catch(e) {
print("当前实例不在复制集中或未启用复制功能");
return;
}
this.getReplicaSetStatus();
this.monitorReplicationLag();
this.monitorOplog();
this.checkReplicationHealth();
this.monitorElections();
print("=".repeat(60));
}
// 持续监控复制延迟
startContinuousMonitoring(intervalSeconds = 30) {
print(`开始持续监控复制延迟 (间隔: ${intervalSeconds}秒)`);
const interval = setInterval(() => {
const { maxLag, laggingMembers } = this.monitorReplicationLag();
// 如果延迟超过阈值,记录时间戳
if (maxLag > this.alertThreshold) {
const timestamp = new Date().toISOString();
print(`[${timestamp}] 复制延迟峰值: ${maxLag.toFixed(2)} 秒`);
// 可以在这里添加webhook或邮件通知
// sendAlert(`复制延迟告警: ${maxLag} 秒`);
}
}, intervalSeconds * 1000);
return interval;
}
}
// 使用示例
const replMonitor = new ReplicationMonitor(10); // 10秒告警阈值
// 执行一次性完整检查
replMonitor.fullReplicationMonitoring();
// 启动持续监控(每30秒)
// const monitorInterval = replMonitor.startContinuousMonitoring(30);
// 停止监控(需要时)
// clearInterval(monitorInterval);
5.3 复制集性能测试脚本
javascript
// replication_performance_test.js
// 复制集性能测试
function testReplicationPerformance() {
print("开始复制集性能测试...");
const testDb = db.getSiblingDB("replication_test");
const testCollection = testDb.test_performance;
// 清理测试数据
testCollection.drop();
// 测试数据量
const testCount = 10000;
const batchSize = 1000;
print(`插入 ${testCount} 条测试数据...`);
// 批量插入测试
let totalInsertTime = 0;
for (let i = 0; i < testCount; i += batchSize) {
const batch = [];
for (let j = 0; j < batchSize && i + j < testCount; j++) {
batch.push({
_id: i + j,
timestamp: new Date(),
data: `test_data_${i + j}`,
randomValue: Math.random()
});
}
const start = Date.now();
testCollection.insertMany(batch);
const duration = Date.now() - start;
totalInsertTime += duration;
print(`已插入 ${Math.min(i + batchSize, testCount)} 条,耗时 ${duration}ms`);
}
print(`\n插入完成,总耗时: ${totalInsertTime}ms`);
print(`平均插入速度: ${(testCount / (totalInsertTime / 1000)).toFixed(2)} 条/秒`);
// 等待复制
print("\n等待数据复制到从节点...");
sleep(5000);
// 检查复制状态
const status = rs.status();
const primary = status.members.find(m => m.state === 1);
print("\n=== 复制性能验证 ===");
status.members.forEach(member => {
if (member.state === 2) {
const slaveConn = new Mongo(member.name);
const slaveDb = slaveConn.getDB("replication_test");
const slaveCount = slaveDb.test_performance.countDocuments();
print(`${member.name}:`);
print(` 文档数: ${slaveCount}`);
print(` 同步状态: ${slaveCount === testCount ? "✅ 已同步" : "⚠️ 未完全同步"}`);
if (slaveCount !== testCount) {
print(` 差异: ${testCount - slaveCount} 条文档`);
}
}
});
// 清理测试数据
testCollection.drop();
print("\n测试完成,已清理测试数据");
}
// 执行性能测试
testReplicationPerformance();
总结
最佳实践建议
-
内存监控:
- 保持工作集大小小于物理内存
- 监控页面错误率
- 定期检查缓存命中率
-
磁盘空间:
- 设置80%的磁盘使用告警阈值
- 定期执行compact释放空间
- 使用TTL索引自动清理过期数据
-
性能监控:
- 关注慢查询,优化索引
- 监控锁等待率,避免锁争用
- 跟踪连接数,防止连接泄漏
-
复制监控:
- 保持复制延迟小于10秒
- 确保oplog窗口大于24小时
- 定期检查复制集健康状态
常用监控命令速查
javascript
// 快速状态检查
rs.status() // 复制集状态
db.serverStatus() // 服务器状态
db.stats() // 数据库统计
db.currentOp() // 当前操作
// 命令行工具
mongostat --host localhost // 实时统计
mongotop // 集合级别统计
mongod --dbpath /data/db --repair // 修复和压缩