初次提交
This commit is contained in:
295
db-monitor.js
Normal file
295
db-monitor.js
Normal file
@@ -0,0 +1,295 @@
|
||||
const { getDB, dbConfig } = require('./database');
|
||||
const { logger } = require('./config/logger');
|
||||
|
||||
/**
|
||||
* 数据库连接监控工具
|
||||
* 用于诊断和监控数据库连接池状态
|
||||
*/
|
||||
class DatabaseMonitor {
|
||||
/**
|
||||
* 获取连接池详细状态
|
||||
* @returns {Object} 连接池状态信息
|
||||
*/
|
||||
/**
|
||||
* 获取连接池详细状态
|
||||
* @returns {Object} 连接池状态信息
|
||||
*/
|
||||
getPoolStatus() {
|
||||
try {
|
||||
const pool = getDB();
|
||||
|
||||
const status = {
|
||||
// 基本连接信息
|
||||
totalConnections: pool._allConnections ? pool._allConnections.length : 0,
|
||||
freeConnections: pool._freeConnections ? pool._freeConnections.length : 0,
|
||||
acquiringConnections: pool._acquiringConnections ? pool._acquiringConnections.length : 0,
|
||||
|
||||
// 计算使用率
|
||||
connectionLimit: dbConfig.connectionLimit,
|
||||
usageRate: 0,
|
||||
|
||||
// 配置信息
|
||||
config: {
|
||||
connectionLimit: dbConfig.connectionLimit,
|
||||
acquireTimeout: dbConfig.acquireTimeout,
|
||||
timeout: dbConfig.timeout,
|
||||
idleTimeout: dbConfig.idleTimeout,
|
||||
maxLifetime: dbConfig.maxLifetime,
|
||||
queueLimit: dbConfig.queueLimit,
|
||||
host: dbConfig.host,
|
||||
database: dbConfig.database
|
||||
},
|
||||
|
||||
// 时间戳
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
|
||||
// 计算连接使用率
|
||||
if (status.connectionLimit > 0) {
|
||||
const usedConnections = status.totalConnections - status.freeConnections;
|
||||
status.usageRate = Math.round((usedConnections / status.connectionLimit) * 100);
|
||||
}
|
||||
|
||||
return status;
|
||||
} catch (error) {
|
||||
logger.error('Failed to get pool status', { error: error.message });
|
||||
return {
|
||||
error: error.message,
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试数据库连接
|
||||
* @returns {Object} 连接测试结果
|
||||
*/
|
||||
async testConnection() {
|
||||
const startTime = Date.now();
|
||||
let connection;
|
||||
|
||||
try {
|
||||
const pool = getDB();
|
||||
|
||||
// 获取连接
|
||||
const acquireStart = Date.now();
|
||||
connection = await pool.getConnection();
|
||||
const acquireTime = Date.now() - acquireStart;
|
||||
|
||||
// 执行测试查询
|
||||
const queryStart = Date.now();
|
||||
const [result] = await connection.execute('SELECT 1 as test, NOW() as server_time');
|
||||
const queryTime = Date.now() - queryStart;
|
||||
|
||||
const totalTime = Date.now() - startTime;
|
||||
|
||||
return {
|
||||
success: true,
|
||||
acquireTime,
|
||||
queryTime,
|
||||
totalTime,
|
||||
serverTime: result[0].server_time,
|
||||
connectionId: connection.threadId,
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
const totalTime = Date.now() - startTime;
|
||||
|
||||
logger.error('Database connection test failed', {
|
||||
error: error.message,
|
||||
totalTime
|
||||
});
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error: error.message,
|
||||
errorCode: error.code,
|
||||
totalTime,
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
} finally {
|
||||
if (connection) {
|
||||
connection.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行连接池诊断
|
||||
* @returns {Object} 诊断结果
|
||||
*/
|
||||
async diagnose() {
|
||||
const poolStatus = this.getPoolStatus();
|
||||
const connectionTest = await this.testConnection();
|
||||
|
||||
const diagnosis = {
|
||||
poolStatus,
|
||||
connectionTest,
|
||||
issues: [],
|
||||
recommendations: [],
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
|
||||
// 分析潜在问题
|
||||
if (poolStatus.usageRate > 90) {
|
||||
diagnosis.issues.push('连接池使用率过高 (>90%)');
|
||||
diagnosis.recommendations.push('考虑增加连接池大小或优化查询性能');
|
||||
}
|
||||
|
||||
if (poolStatus.freeConnections === 0) {
|
||||
diagnosis.issues.push('没有空闲连接可用');
|
||||
diagnosis.recommendations.push('立即检查是否存在连接泄漏或增加连接池大小');
|
||||
}
|
||||
|
||||
if (!connectionTest.success) {
|
||||
diagnosis.issues.push(`数据库连接失败: ${connectionTest.error}`);
|
||||
diagnosis.recommendations.push('检查数据库服务器状态和网络连接');
|
||||
} else {
|
||||
if (connectionTest.acquireTime > 5000) {
|
||||
diagnosis.issues.push('获取连接耗时过长 (>5秒)');
|
||||
diagnosis.recommendations.push('检查连接池配置和数据库负载');
|
||||
}
|
||||
|
||||
if (connectionTest.queryTime > 1000) {
|
||||
diagnosis.issues.push('查询响应时间过长 (>1秒)');
|
||||
diagnosis.recommendations.push('检查数据库性能和网络延迟');
|
||||
}
|
||||
}
|
||||
|
||||
return diagnosis;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成监控报告
|
||||
* @returns {string} 格式化的监控报告
|
||||
*/
|
||||
async generateReport() {
|
||||
const diagnosis = await this.diagnose();
|
||||
|
||||
let report = '\n=== 数据库连接监控报告 ===\n';
|
||||
report += `生成时间: ${diagnosis.timestamp}\n\n`;
|
||||
|
||||
// 连接池状态
|
||||
report += '【连接池状态】\n';
|
||||
if (diagnosis.poolStatus.error) {
|
||||
report += `错误: ${diagnosis.poolStatus.error}\n`;
|
||||
} else {
|
||||
report += `总连接数: ${diagnosis.poolStatus.totalConnections}\n`;
|
||||
report += `空闲连接: ${diagnosis.poolStatus.freeConnections}\n`;
|
||||
report += `获取中连接: ${diagnosis.poolStatus.acquiringConnections}\n`;
|
||||
report += `连接限制: ${diagnosis.poolStatus.connectionLimit}\n`;
|
||||
report += `使用率: ${diagnosis.poolStatus.usageRate}%\n`;
|
||||
}
|
||||
|
||||
// 连接测试
|
||||
report += '\n【连接测试】\n';
|
||||
if (diagnosis.connectionTest.success) {
|
||||
report += `状态: 成功\n`;
|
||||
report += `获取连接耗时: ${diagnosis.connectionTest.acquireTime}ms\n`;
|
||||
report += `查询耗时: ${diagnosis.connectionTest.queryTime}ms\n`;
|
||||
report += `总耗时: ${diagnosis.connectionTest.totalTime}ms\n`;
|
||||
report += `连接ID: ${diagnosis.connectionTest.connectionId}\n`;
|
||||
report += `服务器时间: ${diagnosis.connectionTest.serverTime}\n`;
|
||||
} else {
|
||||
report += `状态: 失败\n`;
|
||||
report += `错误: ${diagnosis.connectionTest.error}\n`;
|
||||
report += `错误代码: ${diagnosis.connectionTest.errorCode || 'N/A'}\n`;
|
||||
report += `总耗时: ${diagnosis.connectionTest.totalTime}ms\n`;
|
||||
}
|
||||
|
||||
// 问题和建议
|
||||
if (diagnosis.issues.length > 0) {
|
||||
report += '\n【发现的问题】\n';
|
||||
diagnosis.issues.forEach((issue, index) => {
|
||||
report += `${index + 1}. ${issue}\n`;
|
||||
});
|
||||
}
|
||||
|
||||
if (diagnosis.recommendations.length > 0) {
|
||||
report += '\n【建议】\n';
|
||||
diagnosis.recommendations.forEach((rec, index) => {
|
||||
report += `${index + 1}. ${rec}\n`;
|
||||
});
|
||||
}
|
||||
|
||||
if (diagnosis.issues.length === 0) {
|
||||
report += '\n【状态】\n数据库连接正常,未发现问题。\n';
|
||||
}
|
||||
|
||||
report += '\n=== 报告结束 ===\n';
|
||||
|
||||
return report;
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动实时监控
|
||||
* @param {number} interval 监控间隔(毫秒),默认30秒
|
||||
*/
|
||||
startMonitoring(interval = 30000) {
|
||||
console.log('启动数据库连接实时监控...');
|
||||
|
||||
const monitor = async () => {
|
||||
try {
|
||||
const status = this.getPoolStatus();
|
||||
|
||||
// 只在有问题时输出详细信息
|
||||
if (status.usageRate > 80 || status.freeConnections < 2) {
|
||||
console.warn('数据库连接池警告:', {
|
||||
usageRate: `${status.usageRate}%`,
|
||||
freeConnections: status.freeConnections,
|
||||
totalConnections: status.totalConnections
|
||||
});
|
||||
}
|
||||
|
||||
// 减少频繁的日志记录,只在有问题时记录
|
||||
if (status.usageRate > 80 || status.freeConnections < 2) {
|
||||
logger.warn('Database pool status warning', status);
|
||||
}
|
||||
// 注释掉正常情况下的日志记录
|
||||
// logger.info('Database pool status', status);
|
||||
|
||||
} catch (error) {
|
||||
console.error('监控过程中发生错误:', error);
|
||||
logger.error('Database monitoring error', { error: error.message });
|
||||
}
|
||||
};
|
||||
|
||||
// 立即执行一次
|
||||
monitor();
|
||||
|
||||
// 定期执行
|
||||
const intervalId = setInterval(monitor, interval);
|
||||
|
||||
// 返回停止函数
|
||||
return () => {
|
||||
clearInterval(intervalId);
|
||||
console.log('数据库连接监控已停止');
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 创建单例实例
|
||||
const dbMonitor = new DatabaseMonitor();
|
||||
|
||||
// 如果直接运行此文件,执行诊断
|
||||
if (require.main === module) {
|
||||
(async () => {
|
||||
try {
|
||||
// 初始化数据库
|
||||
const { initDB } = require('./database');
|
||||
await initDB();
|
||||
|
||||
console.log('正在执行数据库连接诊断...');
|
||||
const report = await dbMonitor.generateReport();
|
||||
console.log(report);
|
||||
|
||||
process.exit(0);
|
||||
} catch (error) {
|
||||
console.error('诊断失败:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
module.exports = dbMonitor;
|
||||
Reference in New Issue
Block a user