修正資料庫未關閉問題

This commit is contained in:
2025-09-21 21:23:47 +08:00
parent 36e29c5a3f
commit 38ae30d611
11 changed files with 684 additions and 111 deletions

206
lib/connection-monitor.ts Normal file
View File

@@ -0,0 +1,206 @@
// =====================================================
// 資料庫連線監控服務
// =====================================================
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();