295 lines
8.8 KiB
JavaScript
295 lines
8.8 KiB
JavaScript
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; |