MongoDB入门学习教程,从入门到精通,MongoDB监控完全指南(22)

MongoDB监控完全指南

目录

  1. 监控内存使用情况
  2. 计算工作集的大小
  3. 跟踪性能情况
  4. 跟踪剩余空间
  5. 监控复制情况

一、监控内存使用情况

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();

总结

最佳实践建议

  1. 内存监控

    • 保持工作集大小小于物理内存
    • 监控页面错误率
    • 定期检查缓存命中率
  2. 磁盘空间

    • 设置80%的磁盘使用告警阈值
    • 定期执行compact释放空间
    • 使用TTL索引自动清理过期数据
  3. 性能监控

    • 关注慢查询,优化索引
    • 监控锁等待率,避免锁争用
    • 跟踪连接数,防止连接泄漏
  4. 复制监控

    • 保持复制延迟小于10秒
    • 确保oplog窗口大于24小时
    • 定期检查复制集健康状态

常用监控命令速查

javascript 复制代码
// 快速状态检查
rs.status()                    // 复制集状态
db.serverStatus()              // 服务器状态
db.stats()                     // 数据库统计
db.currentOp()                 // 当前操作

// 命令行工具
mongostat --host localhost    // 实时统计
mongotop                       // 集合级别统计
mongod --dbpath /data/db --repair  // 修复和压缩
相关推荐
℡終嚸♂6802 小时前
SQL 注入与 ThinkPHP 漏洞技术讲义
数据库·sql
杰克尼2 小时前
redis(day07-Redis 最佳实践)
数据库·redis·缓存
倔强的石头1062 小时前
表空间自动目录创建与存储管理实践:参数化配置与性能优化
数据库·oracle·性能优化
不剪发的Tony老师2 小时前
Goose:一款成熟灵活的数据库变更管理工具
数据库
_李小白2 小时前
【OSG学习笔记】Day 46: CameraManipulator(相机操控器)
笔记·数码相机·学习
草莓熊Lotso2 小时前
Linux 线程深度剖析:线程 ID 本质、地址空间布局与 pthread 源码全解
android·linux·运维·服务器·数据库·c++
AcrelGHP2 小时前
安科瑞AIM-T系列工业IT绝缘监测及故障定位解决方案为关键供电场所筑牢安全防线
大数据·运维·数据库
fīɡЙtīиɡ ℡2 小时前
【Mysql——MVCC】
数据库·mysql
XDHCOM2 小时前
ORA-31477: LogMiner会话清理失败,Oracle报错故障修复远程处理,快速解决,数据安全无忧
数据库·oracle