由于egg生命周期中,加载配置文件在configWillLoad之前,并且configWillLoad只支持同步方法,导致在实践中希望从配置中心拉取配置,或者希望异步方式加载配置文件的方式无法实现。 官方在issues中提出的在agent进程中获取配置,并写入文件,在worker进程中加载该文件的方式。 经过实践测试发现,好像并不能实现需求,因为,按照官方文档,egg源码,实践发现,agent的生命周期与app的生命周期几乎一样(此处指加载方式与生命周期函数),所以配置文件的加载依然在configWillLoad之前,并且如上所述,此函数不支持异步方法。在现实应用中陷入了僵局,秉持着继续试用egg框架不更换框架的前提下,我使用了一种曲线救国的方式,放弃egg-script的启动方式。
注:项目中使用了etcd获取配置
1、首先改变egg启动方式,放弃egg-script的启动,使用egg-cluster自定义启动文件index.js
2、项目目录下的index.js
//index.js
'use strict';
const startCluster = require('egg-cluster').startCluster;
const Etcd3 = require('etcd3');
const fs = require('fs');
const moment = require('moment');
//node env 与 egg env 映射
const env = {
dev: 'local',
local: 'local',
development: 'local',
test: 'testing',
testing: 'testing',
prod: 'production',
production: 'production',
product: 'production'
}
//egg-cluster启动配置
const serverOptions = {
baseDir: __dirname,
};
//etcd配置
const etcdConfig = {};
//正式服启动环境变量必须,防止连接到测试服
if (!process.env.NODE_ENV && !process.env.EGG_SERVER_ENV) {
const errMessage = moment().format('YYYY-MM-DD HH:mm:ss') + 'There is no NODE_ENV or EGG_SERVER_ENV'
fs.writeFileSync('./logs/startError.log', errMessage, { flag: 'w' })
process.exit(1);
} else if (process.env.NODE_ENV) {
//node环境映射到egg环境
process.env.EGG_SERVER_ENV = env[process.env.NODE_ENV]
}
if (process.env.EGG_SERVER_ENV === 'local') {
serverOptions.workers = 1;
etcdConfig.hosts = [
"http://127.0.0.1:32771",
"http://127.0.0.1:32769",
"http://127.0.0.1:32773"
]
} else if (process.env.EGG_SERVER_ENV === 'testing') {
serverOptions.workers = 1;
etcdConfig.hosts = [
"http://127.0.0.1:32771",
"http://127.0.0.1:32769",
"http://127.0.0.1:32773"
]
} else if (process.env.EGG_SERVER_ENV === 'production') {
serverOptions.workers = 2;
etcdConfig.hosts = [
"127.0.0.1:32771",
"127.0.0.1:32769",
"127.0.0.1:32773"
]
} else {
const errMessage = moment().format('YYYY-MM-DD HH:mm:ss') + 'There is no NODE_ENV or EGG_SERVER_ENV';
fs.writeFileSync('./logs/startError.log', errMessage, { flag: 'w' })
process.exit(1)
}
(async () => {
const etcd = new Etcd3.Etcd3(etcdConfig);
try {
const res = await etcd.getAll();
//获取到etcd的配置后,写入文件
fs.writeFileSync("./config/etcd.json", JSON.stringify(res), { flag: 'w' })
} catch (error) {
const errMessage = moment().format('YYYY-MM-DD HH:mm:ss')
+ 'There is an error occured for getting config from Etcd =>'
+ error.toString()
fs.writeFileSync('./logs/startError.log', errMessage, { flag: 'w' })
process.exit(1)
}
startCluster(serverOptions);
})();
3、写入config目录下的配置文件etcd.json
//etcd.json
{
"MYSQL_DBNAME": "test",
"MYSQL_PORT": "3306",
"MYSQL_READ_HOST": "127.0.0.1",
"MYSQL_READ_PASSWORD": "test",
"MYSQL_READ_USERNAME": "test"
}
4、在config中使用配置文件
//config.env.js
'use strict';
const etcd = require('./etcd.json');
const MYSQL_DBNAME = etcd.MYSQL_DBNAME
const MYSQL_HOST_READ = etcd.MYSQL_READ_HOST
const MYSQL_PORT_READ = etcd.KANKANTAOBAO_MYSQL_PORT
const MYSQL_USERNAME_READ = etcd.MYSQL_READ_USERNAME
const MYSQL_PASSWORD_READ = etcd.MYSQL_READ_PASSWORD
module.exports = appInfo => {
return {
sequelize: {
datasources: [
{
dialect: 'mysql',
delegate: 'test',
baseDir: 'model/test',
database: MYSQL_TAOBAO_DBNAME,
logQueryParameters: true,
replication: {
read: [
{
database: MYSQL_DBNAME,
host: MYSQL_HOST_READ,
port: MYSQL_PORT_READ,
username: MYSQL_USERNAME_READ,
password: MYSQL_PASSWORD_READ,
}
],
write: {
}
},
freezeTableName: false,
underscored: true,
timezone: '+08:00',
}
]
},
}
};
5、关于此种方式的思考
在我们项目的实际部署使用中,测试服使用pm2统一维护node项目,在正式服中,用docker部署项目,所以几乎是用不到egg-script的维护功能,所以,此种方式可以在当前是比较好的解决方式,如果大家有什么更好的方式,或者我的错误,欢迎指点。
兄弟你这个方案太复杂了。。。。有个更简单的,通过execSync去执行一个外部的异步脚本,比如那个脚本是用来获取配置中心的配置,然后在那个脚本结尾输出结果,在execsync的返回值中即可拿到那些配置。execsync会阻断整个服务的启动,放在config will load方法中就可以。如需具体的案例代码晚点可以贴上来。
@satancp 正解
你可以在 agent 的生命周期里面去获取,然后写入文件。
在 app 这边直接 require
兄弟你这个方案太复杂了。。。。有个更简单的,通过execSync去执行一个外部的异步脚本,比如那个脚本是用来获取配置中心的配置,然后在那个脚本结尾输出结果,在execsync的返回值中即可拿到那些配置。execsync会阻断整个服务的启动,放在config will load方法中就可以。如需具体的案例代码晚点可以贴上来。
学习了兄弟!
@satancp 有代码例子么?最近正好卡在这里了。
@xyyxlq 我今天晚点把案例发这边,可能比较晚,大概10点多的样子
先赞为敬,但是通过agent去写入文件没有解决的问题是,agent进程也会读取配置并加载插件的,比如sequelize,这个在agent的willReady里面去写入文件,还是会导致插件加载失败(因为此时配置文件还没有从配置中心拉取到)。我想问题应该在此处吧。
这个没办法,除非改了各个插件的初始化时机。agent 一般不需要 seq 吧。
实在复杂到这个程度了话,你肯定都镜像化了,直接在启动前读取配置就好了。
好的,我现在的解决就是在启动前先去读取配置写入文件的方式。[Facepalm]有个场景就是在agent中去订阅redis。所以,哈哈,问题不大。
Most helpful comment
提供一个临时方案:https://github.com/atian25/egg-remote-config