Files
ai-showcase-platform/lib/database-failover.js

433 lines
16 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use strict";
// =====================================================
// 資料庫備援連線服務
// =====================================================
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.dbFailover = exports.DatabaseFailover = void 0;
const promise_1 = __importDefault(require("mysql2/promise"));
// 主機資料庫配置
const masterConfig = {
host: process.env.DB_HOST || 'mysql.theaken.com',
port: parseInt(process.env.DB_PORT || '33306'),
user: process.env.DB_USER || 'AI_Platform',
password: process.env.DB_PASSWORD || 'Aa123456',
database: process.env.DB_NAME || 'db_AI_Platform',
charset: 'utf8mb4',
timezone: '+08:00',
connectionLimit: 10,
queueLimit: 0,
idleTimeout: 300000,
ssl: false,
};
// 備機資料庫配置
const slaveConfig = {
host: process.env.SLAVE_DB_HOST || '122.100.99.161',
port: parseInt(process.env.SLAVE_DB_PORT || '43306'),
user: process.env.SLAVE_DB_USER || 'A999',
password: process.env.SLAVE_DB_PASSWORD || '1023',
database: process.env.SLAVE_DB_NAME || 'db_AI_Platform', // 修正為 AI 平台資料庫
charset: 'utf8mb4',
timezone: '+08:00',
connectionLimit: 10,
queueLimit: 0,
idleTimeout: 300000,
ssl: false,
};
// 備援資料庫管理類
class DatabaseFailover {
constructor() {
this.masterPool = null;
this.slavePool = null;
this.healthCheckInterval = null;
this.status = {
isEnabled: process.env.DB_FAILOVER_ENABLED === 'true',
currentDatabase: 'master',
masterHealthy: false,
slaveHealthy: false,
lastHealthCheck: 0,
consecutiveFailures: 0,
};
// 同步初始化連接池
this.initializePoolsSync();
this.startHealthCheck();
}
static getInstance() {
if (!DatabaseFailover.instance) {
DatabaseFailover.instance = new DatabaseFailover();
}
return DatabaseFailover.instance;
}
// 同步初始化連接池
initializePoolsSync() {
try {
console.log('🚀 開始同步初始化資料庫備援系統...');
// 直接創建連接池,不等待測試
this.masterPool = promise_1.default.createPool(masterConfig);
console.log('✅ 主機資料庫連接池創建成功');
this.slavePool = promise_1.default.createPool(slaveConfig);
console.log('✅ 備機資料庫連接池創建成功');
// 設置默認使用主機
this.status.currentDatabase = 'master';
this.status.masterHealthy = true; // 假設主機健康,後續健康檢查會驗證
console.log('🎯 當前使用資料庫: 主機');
// 異步測試連接
this.testConnectionsAsync();
}
catch (error) {
console.error('❌ 資料庫連接池同步初始化失敗:', error);
}
}
// 異步測試連接
async testConnectionsAsync() {
try {
await this.testConnections();
}
catch (error) {
console.error('❌ 異步連接測試失敗:', error);
}
}
// 初始化連接池
async initializePools() {
try {
console.log('🚀 開始初始化資料庫備援系統...');
// 先測試主機連接
console.log('📡 測試主機資料庫連接...');
const masterHealthy = await this.testMasterConnection();
if (masterHealthy) {
console.log('✅ 主機資料庫連接正常,使用主機');
this.status.currentDatabase = 'master';
this.status.masterHealthy = true;
// 初始化主機連接池
this.masterPool = promise_1.default.createPool(masterConfig);
console.log('✅ 主機資料庫連接池初始化成功');
}
else {
console.log('❌ 主機資料庫連接失敗,測試備機...');
// 測試備機連接
const slaveHealthy = await this.testSlaveConnection();
if (slaveHealthy) {
console.log('✅ 備機資料庫連接正常,切換到備機');
this.status.currentDatabase = 'slave';
this.status.slaveHealthy = true;
// 初始化備機連接池
this.slavePool = promise_1.default.createPool(slaveConfig);
console.log('✅ 備機資料庫連接池初始化成功');
}
else {
console.log('❌ 主機和備機都無法連接,嘗試初始化主機連接池');
this.masterPool = promise_1.default.createPool(masterConfig);
this.status.currentDatabase = 'master';
}
}
// 初始化另一個連接池(用於健康檢查)
if (this.status.currentDatabase === 'master' && !this.slavePool) {
this.slavePool = promise_1.default.createPool(slaveConfig);
console.log('✅ 備機資料庫連接池初始化成功(用於健康檢查)');
}
else if (this.status.currentDatabase === 'slave' && !this.masterPool) {
this.masterPool = promise_1.default.createPool(masterConfig);
console.log('✅ 主機資料庫連接池初始化成功(用於健康檢查)');
}
console.log(`🎯 當前使用資料庫: ${this.status.currentDatabase === 'master' ? '主機' : '備機'}`);
}
catch (error) {
console.error('❌ 資料庫連接池初始化失敗:', error);
}
}
// 測試主機連接
async testMasterConnection() {
try {
const connection = await promise_1.default.createConnection(masterConfig);
await connection.ping();
await connection.end();
return true;
}
catch (error) {
console.error('主機資料庫連接失敗:', error.message);
return false;
}
}
// 測試備機連接
async testSlaveConnection() {
try {
const connection = await promise_1.default.createConnection(slaveConfig);
await connection.ping();
await connection.end();
return true;
}
catch (error) {
console.error('備機資料庫連接失敗:', error.message);
return false;
}
}
// 測試資料庫連接
async testConnections() {
// 測試主機
try {
if (this.masterPool) {
const connection = await this.masterPool.getConnection();
await connection.ping();
connection.release();
this.status.masterHealthy = true;
console.log('主機資料庫連接正常');
}
}
catch (error) {
this.status.masterHealthy = false;
console.error('主機資料庫連接失敗:', error);
}
// 測試備機
try {
if (this.slavePool) {
const connection = await this.slavePool.getConnection();
await connection.ping();
connection.release();
this.status.slaveHealthy = true;
console.log('備機資料庫連接正常');
}
}
catch (error) {
this.status.slaveHealthy = false;
console.error('備機資料庫連接失敗:', error);
}
}
// 開始健康檢查
startHealthCheck() {
if (!this.status.isEnabled)
return;
const interval = parseInt(process.env.DB_HEALTH_CHECK_INTERVAL || '30000');
this.healthCheckInterval = setInterval(async () => {
await this.performHealthCheck();
}, interval);
}
// 執行健康檢查
async performHealthCheck() {
const now = Date.now();
this.status.lastHealthCheck = now;
// 檢查主機
if (this.masterPool) {
try {
const connection = await this.masterPool.getConnection();
await connection.ping();
connection.release();
this.status.masterHealthy = true;
}
catch (error) {
this.status.masterHealthy = false;
console.error('主機資料庫健康檢查失敗:', error);
}
}
// 檢查備機
if (this.slavePool) {
try {
const connection = await this.slavePool.getConnection();
await connection.ping();
connection.release();
this.status.slaveHealthy = true;
}
catch (error) {
this.status.slaveHealthy = false;
console.error('備機資料庫健康檢查失敗:', error);
}
}
// 決定當前使用的資料庫
this.determineCurrentDatabase();
}
// 決定當前使用的資料庫
determineCurrentDatabase() {
const previousDatabase = this.status.currentDatabase;
if (this.status.masterHealthy) {
if (this.status.currentDatabase !== 'master') {
console.log('🔄 主機資料庫恢復,切換回主機');
this.status.currentDatabase = 'master';
this.status.consecutiveFailures = 0;
}
}
else if (this.status.slaveHealthy) {
if (this.status.currentDatabase !== 'slave') {
console.log('🔄 主機資料庫故障,切換到備機');
this.status.currentDatabase = 'slave';
this.status.consecutiveFailures++;
}
}
else {
this.status.consecutiveFailures++;
console.error('❌ 主機和備機資料庫都無法連接');
}
// 記錄狀態變化
if (previousDatabase !== this.status.currentDatabase) {
console.log(`📊 資料庫狀態變化: ${previousDatabase}${this.status.currentDatabase}`);
}
}
// 獲取當前連接池
getCurrentPool() {
if (this.status.currentDatabase === 'master') {
if (this.masterPool) {
return this.masterPool;
}
else if (this.slavePool) {
// 主機不可用,嘗試使用備機
console.log('⚠️ 主機連接池不可用,嘗試使用備機');
this.status.currentDatabase = 'slave';
return this.slavePool;
}
}
else if (this.status.currentDatabase === 'slave') {
if (this.slavePool) {
return this.slavePool;
}
else if (this.masterPool) {
// 備機不可用,嘗試使用主機
console.log('⚠️ 備機連接池不可用,嘗試使用主機');
this.status.currentDatabase = 'master';
return this.masterPool;
}
}
console.error('❌ 沒有可用的資料庫連接池');
return null;
}
// 獲取連接
async getConnection() {
const pool = this.getCurrentPool();
if (!pool) {
throw new Error('沒有可用的資料庫連接');
}
let retries = 0;
const maxRetries = parseInt(process.env.DB_RETRY_ATTEMPTS || '3');
const retryDelay = parseInt(process.env.DB_RETRY_DELAY || '2000');
while (retries < maxRetries) {
try {
return await pool.getConnection();
}
catch (error) {
console.error(`資料庫連接失敗 (嘗試 ${retries + 1}/${maxRetries}):`, error.message);
if (error.code === 'ECONNRESET' || error.code === 'PROTOCOL_CONNECTION_LOST') {
// 觸發健康檢查
await this.performHealthCheck();
retries++;
if (retries < maxRetries) {
console.log(`等待 ${retryDelay}ms 後重試...`);
await new Promise(resolve => setTimeout(resolve, retryDelay));
continue;
}
}
throw error;
}
}
throw new Error('資料庫連接失敗,已達到最大重試次數');
}
// 執行查詢
async query(sql, params) {
const connection = await this.getConnection();
try {
// 將 undefined 值轉換為 null避免 MySQL 驅動錯誤
const sanitizedParams = params ? params.map(param => param === undefined ? null : param) : [];
const [rows] = await connection.execute(sql, sanitizedParams);
return rows;
}
finally {
connection.release();
}
}
// 執行單一查詢
async queryOne(sql, params) {
try {
const results = await this.query(sql, params);
return results.length > 0 ? results[0] : null;
}
catch (error) {
console.error('資料庫單一查詢錯誤:', error);
throw error;
}
}
// 執行插入
async insert(sql, params) {
const connection = await this.getConnection();
try {
// 將 undefined 值轉換為 null避免 MySQL 驅動錯誤
const sanitizedParams = params ? params.map(param => param === undefined ? null : param) : [];
const [result] = await connection.execute(sql, sanitizedParams);
return result;
}
finally {
connection.release();
}
}
// 執行更新
async update(sql, params) {
const connection = await this.getConnection();
try {
// 將 undefined 值轉換為 null避免 MySQL 驅動錯誤
const sanitizedParams = params ? params.map(param => param === undefined ? null : param) : [];
const [result] = await connection.execute(sql, sanitizedParams);
return result;
}
finally {
connection.release();
}
}
// 執行刪除
async delete(sql, params) {
const connection = await this.getConnection();
try {
// 將 undefined 值轉換為 null避免 MySQL 驅動錯誤
const sanitizedParams = params ? params.map(param => param === undefined ? null : param) : [];
const [result] = await connection.execute(sql, sanitizedParams);
return result;
}
finally {
connection.release();
}
}
// 開始事務
async beginTransaction() {
const connection = await this.getConnection();
await connection.beginTransaction();
return connection;
}
// 提交事務
async commit(connection) {
await connection.commit();
connection.release();
}
// 回滾事務
async rollback(connection) {
await connection.rollback();
connection.release();
}
// 獲取備援狀態
getStatus() {
return { ...this.status };
}
// 強制切換到指定資料庫
async switchToDatabase(database) {
if (database === 'master' && this.status.masterHealthy) {
this.status.currentDatabase = 'master';
return true;
}
else if (database === 'slave' && this.status.slaveHealthy) {
this.status.currentDatabase = 'slave';
return true;
}
return false;
}
// 關閉所有連接池
async close() {
if (this.healthCheckInterval) {
clearInterval(this.healthCheckInterval);
}
if (this.masterPool) {
await this.masterPool.end();
}
if (this.slavePool) {
await this.slavePool.end();
}
}
}
exports.DatabaseFailover = DatabaseFailover;
// 導出單例實例
exports.dbFailover = DatabaseFailover.getInstance();