207 lines
6.2 KiB
TypeScript
207 lines
6.2 KiB
TypeScript
// =====================================================
|
|
// 資料庫連線監控服務
|
|
// =====================================================
|
|
|
|
import { db } from './database';
|
|
import { dbFailover } from './database-failover';
|
|
|
|
export interface ConnectionStats {
|
|
totalConnections: number;
|
|
activeConnections: number;
|
|
idleConnections: number;
|
|
queuedRequests: number;
|
|
maxConnections: number;
|
|
usagePercentage: number;
|
|
isHealthy: boolean;
|
|
lastCheck: Date;
|
|
}
|
|
|
|
export class ConnectionMonitor {
|
|
private static instance: ConnectionMonitor;
|
|
private stats: ConnectionStats | null = null;
|
|
private checkInterval: NodeJS.Timeout | null = null;
|
|
private isMonitoring = false;
|
|
|
|
private constructor() {}
|
|
|
|
public static getInstance(): ConnectionMonitor {
|
|
if (!ConnectionMonitor.instance) {
|
|
ConnectionMonitor.instance = new ConnectionMonitor();
|
|
}
|
|
return ConnectionMonitor.instance;
|
|
}
|
|
|
|
// 開始監控
|
|
public startMonitoring(intervalMs: number = 30000): void {
|
|
if (this.isMonitoring) {
|
|
console.log('⚠️ 連線監控已在運行中');
|
|
return;
|
|
}
|
|
|
|
this.isMonitoring = true;
|
|
console.log(`🔍 開始連線監控,檢查間隔: ${intervalMs}ms`);
|
|
|
|
this.checkInterval = setInterval(async () => {
|
|
await this.checkConnectionHealth();
|
|
}, intervalMs);
|
|
|
|
// 立即執行一次檢查
|
|
this.checkConnectionHealth();
|
|
}
|
|
|
|
// 停止監控
|
|
public stopMonitoring(): void {
|
|
if (this.checkInterval) {
|
|
clearInterval(this.checkInterval);
|
|
this.checkInterval = null;
|
|
}
|
|
this.isMonitoring = false;
|
|
console.log('🛑 連線監控已停止');
|
|
}
|
|
|
|
// 檢查連線健康狀態
|
|
public async checkConnectionHealth(): Promise<ConnectionStats> {
|
|
try {
|
|
const stats = await this.getConnectionStats();
|
|
this.stats = stats;
|
|
|
|
// 記錄連線狀態
|
|
console.log(`📊 資料庫連線狀態: ${stats.activeConnections}/${stats.maxConnections} (${stats.usagePercentage.toFixed(1)}%)`);
|
|
|
|
// 檢查是否健康
|
|
if (stats.usagePercentage > 80) {
|
|
console.warn(`⚠️ 資料庫連線使用率過高: ${stats.usagePercentage.toFixed(1)}%`);
|
|
}
|
|
|
|
if (stats.usagePercentage > 95) {
|
|
console.error(`🚨 資料庫連線使用率危險: ${stats.usagePercentage.toFixed(1)}%`);
|
|
}
|
|
|
|
return stats;
|
|
} catch (error) {
|
|
console.error('❌ 連線健康檢查失敗:', error);
|
|
return {
|
|
totalConnections: 0,
|
|
activeConnections: 0,
|
|
idleConnections: 0,
|
|
queuedRequests: 0,
|
|
maxConnections: 0,
|
|
usagePercentage: 0,
|
|
isHealthy: false,
|
|
lastCheck: new Date()
|
|
};
|
|
}
|
|
}
|
|
|
|
// 獲取連線統計
|
|
public async getConnectionStats(): Promise<ConnectionStats> {
|
|
try {
|
|
// 檢查備援狀態
|
|
const failoverStatus = db.getFailoverStatus();
|
|
|
|
if (failoverStatus?.isEnabled) {
|
|
// 使用備援系統獲取統計
|
|
const result = await dbFailover.queryOne<{
|
|
current_connections: number;
|
|
max_connections: number;
|
|
}>('SHOW STATUS LIKE "Threads_connected"');
|
|
|
|
const maxResult = await dbFailover.queryOne<{
|
|
Variable_name: string;
|
|
Value: string;
|
|
}>('SHOW VARIABLES LIKE "max_connections"');
|
|
|
|
const currentConnections = result?.current_connections || 0;
|
|
const maxConnections = parseInt(maxResult?.Value || '0');
|
|
const usagePercentage = maxConnections > 0 ? (currentConnections / maxConnections) * 100 : 0;
|
|
|
|
return {
|
|
totalConnections: currentConnections,
|
|
activeConnections: currentConnections,
|
|
idleConnections: 0, // MySQL 不直接提供空閒連線數
|
|
queuedRequests: 0,
|
|
maxConnections,
|
|
usagePercentage,
|
|
isHealthy: usagePercentage < 90,
|
|
lastCheck: new Date()
|
|
};
|
|
} else {
|
|
// 使用主資料庫獲取統計
|
|
const result = await db.queryOne<{
|
|
current_connections: number;
|
|
max_connections: number;
|
|
}>('SHOW STATUS LIKE "Threads_connected"');
|
|
|
|
const maxResult = await db.queryOne<{
|
|
Variable_name: string;
|
|
Value: string;
|
|
}>('SHOW VARIABLES LIKE "max_connections"');
|
|
|
|
const currentConnections = result?.current_connections || 0;
|
|
const maxConnections = parseInt(maxResult?.Value || '0');
|
|
const usagePercentage = maxConnections > 0 ? (currentConnections / maxConnections) * 100 : 0;
|
|
|
|
return {
|
|
totalConnections: currentConnections,
|
|
activeConnections: currentConnections,
|
|
idleConnections: 0,
|
|
queuedRequests: 0,
|
|
maxConnections,
|
|
usagePercentage,
|
|
isHealthy: usagePercentage < 90,
|
|
lastCheck: new Date()
|
|
};
|
|
}
|
|
} catch (error) {
|
|
console.error('獲取連線統計失敗:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
// 獲取當前統計
|
|
public getCurrentStats(): ConnectionStats | null {
|
|
return this.stats;
|
|
}
|
|
|
|
// 強制清理連線
|
|
public async forceCleanup(): Promise<void> {
|
|
try {
|
|
console.log('🧹 開始強制清理資料庫連線...');
|
|
|
|
// 獲取當前連線數
|
|
const stats = await this.getConnectionStats();
|
|
console.log(`清理前連線數: ${stats.activeConnections}`);
|
|
|
|
// 強制關閉所有空閒連線
|
|
try {
|
|
const { db } = await import('./database');
|
|
await db.close();
|
|
console.log('✅ 資料庫連線池已關閉');
|
|
|
|
// 等待連線完全關閉
|
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
|
|
// 重新初始化
|
|
const { Database } = await import('./database');
|
|
const newDb = Database.getInstance();
|
|
console.log('✅ 資料庫連線池已重新初始化');
|
|
|
|
} catch (error) {
|
|
console.error('❌ 強制清理連線池失敗:', error);
|
|
}
|
|
|
|
console.log('✅ 連線清理完成');
|
|
} catch (error) {
|
|
console.error('❌ 連線清理失敗:', error);
|
|
}
|
|
}
|
|
|
|
// 檢查是否正在監控
|
|
public isCurrentlyMonitoring(): boolean {
|
|
return this.isMonitoring;
|
|
}
|
|
}
|
|
|
|
// 導出單例實例
|
|
export const connectionMonitor = ConnectionMonitor.getInstance();
|