一旦所有数据库都成功连接,只初始化服务的更好方法是什么
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
。
如果有什么不清楚的地方,请告诉我。
我遇到一个问题,一个服务需要使用 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
。
如果有什么不清楚的地方,请告诉我。