一旦所有数据库都成功连接,只初始化服务的更好方法是什么

what would be a better approach to only initialize a service once all databases are succesfully connected

我遇到一个问题,一个服务需要使用 MySQL 池连接到三个数据库,我只想在所有数据库都成功连接后启动服务,我想出了一个可行的方法,但我很确定它可以做得更好,因为我觉得当前的有点过于复杂,所以任何输入或指向设计模式的指针都将不胜感激。

这是我数据库中的代码 class:

module.exports = class DB {
// this is making the constructor async as I couldn't think of another way
// because we don't want to start the service until we make sure that we're connected to the 
// database, the constructor then resolves to an instance of the class, so when initilizing a class
// we need to await to get back the instance.
constructor() {
    return (async () => {
        this.retries = 0;
        await this.initialize();
        return this;
    })();

}

async initialize() {
    logger.info("initialzing database connections");

    this.db = mysql.createPool(config.get('db'));
    this.db.asyncQuery = util.promisify(this.db.query);

    this.statisticsDb = mysql.createPool(config.get('statisticsDb'));

    this.provisioningDb = mysql.createPool(config.get('provisioningDb'));


    const success = await this.testConnections();

    // retry up to 4 times, if all of them fail we will shutdown the process.
    while (this.retries <= 4) {
        if (success) break;

        if (this.retries > 3) {
            Logger.error(`Error connecting to one of the databases pools, number of retries is ${this.retries}, will exit`);
            process.exit(1);
        }
        logger.error(`Error connecting to one of the databases pools, number of retries is ${this.retries}, will retry in 2 seconds`)
        this.retries++;
        // this basically pauses excution for 2 seconds.
        await new Promise(resolve => setTimeout(resolve, 2000));
        await this.initialize();
    }
}



/**
 * Tries to get a connection from each pool, if one of the connections fail, it resolves to false
 * otherwise it resolves to true
 */
async testConnections() {

    const isMainConnected = await this.testDatabasePoolConnection(this.db, 'main');
    const isStatisticsConnected = await this.testDatabasePoolConnection(this.statisticsDb, 'statistics');
    const isprovisioningConnected = await this.testDatabasePoolConnection(this.provisioningDb, 'provisioning');

    if (isMainConnected.connected && isStatisticsConnected.connected && isprovisioningConnected.connected) {
        this.retries = 0;
        logger.info('All databases successfully connected.')
        return true;
    }

    return false;
}

    /**
 * Given a mysql pool, tests if the pool is connected successfully.
 * @param {mysql.Pool} dbPoolInstance the mysql pool we want to test
 * @param {string} dbName the name of the pool for logging purposes
 * @returns resolves to an object, either with error property if there was an error or a connected property if pool is working.
 */
async testDatabasePoolConnection(dbPoolInstance, dbName) {
    try {
        dbPoolInstance.asyncQuery = util.promisify(dbPoolInstance.query);
        let testQuery = 'SELECT id FROM test LIMIT 1';
        await dbPoolInstance.asyncQuery(testQuery);
        logger.info(`${dbName} database successfully connected`);
        return { connected: true }
    } catch (e) {
        logger.error(`${dbName} database connection failed, Error: ${e}`);
        return { error: e };
    }
}

这里是 index.js 文件(简体):

initalize();


async function initalize() {
    try {
        const db = await new DB()

        await cleanUpSomeData(db);

        new Server(db);

} catch (e) {
    Logger.error(`Error while trying to intialize the service ${JSON.stringify(e)}, will shutdown the service`);
    process.exit(1);
}

}

您可以将重试逻辑与数据库逻辑分开。创建一个通用的 async 重试函数,它会重新运行给定的函数,直到达到 returns true 或 n 重试。示例:

const asyncRetry = async (fn, retries) => {
    
    // for logging..
    let retriesLeft = retries


    do {
        console.log('retries left:', retriesLeft)

        retriesLeft--
        try {
            let result = fn()

            // wait for promises  if it is one..
            if(result && typeof result.then === 'function') {
                if(await result) {
                    return true
                }
            } else if(result) {
                return true
            }

        } catch(err) {
            console.error(err)
        }
    } while(retriesLeft > 0)

    console.log('Still Failed after ' + (retries+1) + ' attempts')

    return false
}

有了这个助手,您可以简化您的 DB class 并从外部应用重试,在我看来它使它变得更加清晰。 可能如下所示:


module.exports = class DB {
    constructor() {
        // constructor is empty. Call initialize() after from 
        // the outside and not in the constructor.
    }
    
    async initialize() {
        logger.info("initialzing database connections");
    
        this.db = mysql.createPool(config.get('db'));
        this.db.asyncQuery = util.promisify(this.db.query);
    
        this.statisticsDb = mysql.createPool(config.get('statisticsDb'));
    
        this.provisioningDb = mysql.createPool(config.get('provisioningDb'));
    
    
        // return result directly!
        return await this.testConnections();
    
    }

    async testConnections() {
    
        const isMainConnected = await this.testDatabasePoolConnection(this.db, 'main');
        const isStatisticsConnected = await this.testDatabasePoolConnection(this.statisticsDb, 'statistics');
        const isprovisioningConnected = await this.testDatabasePoolConnection(this.provisioningDb, 'provisioning');
    
        if (isMainConnected.connected && isStatisticsConnected.connected && isprovisioningConnected.connected) {
            logger.info('All databases successfully connected.')
            return true;
        }
    
        return false;
    }
    
        /**
     * Given a mysql pool, tests if the pool is connected successfully.
     * @param {mysql.Pool} dbPoolInstance the mysql pool we want to test
     * @param {string} dbName the name of the pool for logging purposes
     * @returns resolves to an object, either with error property if there was an error or a connected property if pool is working.
     */
    async testDatabasePoolConnection(dbPoolInstance, dbName) {
        try {
            dbPoolInstance.asyncQuery = util.promisify(dbPoolInstance.query);
            let testQuery = 'SELECT id FROM test LIMIT 1';
            await dbPoolInstance.asyncQuery(testQuery);
            logger.info(`${dbName} database successfully connected`);
            return { connected: true }
        } catch (e) {
            logger.error(`${dbName} database connection failed, Error: ${e}`);
            return { error: e };
        }
    }
}

您的最终通话将如下所示:




async function initalize(retries) {

    await cleanUpSomeData(db);

    const success = await asyncRetry(() => {
        return db.testConnections()
    }, retries)

    if(success) {
        Logger.log('database successfully connected')
    } else {
        Logger.error('failed to connect to database')
        process.exit(1)
    }

    await cleanUpSomeData(db);

    new Server(db);

} catch (e) {
    Logger.error(`Error while trying to intialize the service ${JSON.stringify(e)}, will shutdown the service`);
    process.exit(1);
}

// try connecting with a maximum of 5 retries (6 attempts at all) 
initalize(5);

连接之间的超时可以在 asyncRetry() 内实现,例如使用另一个参数 timeout

如果有什么不清楚的地方,请告诉我。