egg框架啟動分析
egg框架目前已經(jīng)使用了大半年醒叁。框架因koa特色泊业、強約束的feel把沼、“為了毫無保留榨干服務(wù)器性能”的slogan,脫穎而出∮跛牛框架使用大半年挺舒適饮睬,入手坡度不算太高。學(xué)而不思則罔篮奄,對這階段的也就和工作內(nèi)容做簡要總結(jié)捆愁,如果有任何錯誤,還請積極指正窟却,感謝昼丑。
有不少文章已經(jīng)對相關(guān)內(nèi)容作了闡述,感謝相關(guān)作者间校。值得深讀
所謂萬事開頭難矾克,對于egg的分析我也從其啟動上著手,egg框架生態(tài)十分完整憔足,致使閱讀代碼又愛又恨胁附,愛其結(jié)構(gòu)恨其調(diào)用關(guān)系鏈(懂了就好了),從啟動方式切入分析滓彰,主體引用鏈為
egg-bin -> egg-cluster -> egg -> egg-core
文章按照啟動鏈的順序?qū)gg-bin和egg-cluster做分析控妻。
寫在前面
文章著重對egg的啟動構(gòu)建做梳理總結(jié),建議把相關(guān)源碼下載下來揭绑,在node_modules下看難度有點大弓候,時常進去后出不來了。應(yīng)用層的使用脫離不了對架構(gòu)層的理解他匪。有任何問題還請告知菇存。
egg-bin
egg-bin基于common-bin(相關(guān)介紹源tao_npm,是node_modules中對于bin相關(guān)操作的共用組件庫),common-bin其重要地位可想而知邦蜜。發(fā)現(xiàn)大師的寫法也著實厲害依鸥,兩個文件足以覆蓋意蘊,command.js + helper.js完成任務(wù)悼沈。
egg-bin框架作為啟動指令包贱迟,需要對指令參數(shù)進行有效解讀姐扮,包內(nèi)使用yargs解析參數(shù),官方文檔對egg-bin的使用作了有效講解衣吠,相關(guān)地址,我們在開發(fā)的時候經(jīng)常會用到的指令均來自與此依賴庫茶敏。“egg-bin dev”作為開發(fā)啟動指令重重之中缚俏,做詳細分析惊搏。
egg-bin dev
egg-bin cov
egg-bin test
...
package.json文件中對egg-bin指令作了文件指向
"bin": {
"egg-bin": "bin/egg-bin.js",
...
},
相關(guān)文件也寫的相當(dāng)完美,掛載了所有index.js
文件的暴露文件袍榆。
bin/egg-bin.js
文件內(nèi)容
const Command = require('..');
new Command().start();
./index.js
文件內(nèi)容
class EggBin extends Command {
constructor(rawArgv) {
super(rawArgv);
this.usage = 'Usage: egg-bin [command] [options]';
// load directory
this.load(path.join(__dirname, 'lib/cmd'));
}
}
在依賴庫的index下胀屿,有相關(guān)exports.當(dāng)執(zhí)行指令時,‘dev’參數(shù)將作為process.argv進入到common-bin/command.js
的構(gòu)造函數(shù)中塘揣,
class CommonBin {
constructor(rawArgv) {
this.rawArgv = rawArgv || process.argv.slice(2);
...
this.yargs = yargs(this.rawArgv);
...
}
}
process.argv的相關(guān)介紹請參考node官方文檔,執(zhí)行后argv參數(shù)樣式基本為
this.rawArgv = [
"dev"
]
剩下的都是內(nèi)部參數(shù)調(diào)整包裝包雀,接著看上文index.js
的第二步,this.load
方法亲铡,繼承關(guān)系才写,實現(xiàn)在common-bin/command.js
中
// fullPath value is 'lib/cmd'
load(fullPath) {
...
// load entire directory
const files = fs.readdirSync(fullPath);
const names = [];
for (const file of files) {
if (path.extname(file) === '.js') {
const name = path.basename(file).replace(/\.js$/, '');
names.push(name);
this.add(name, path.join(fullPath, file));
}
}
}
簡單說,這個地方掃描了lib/cmd
下所有js文件奖蔓,將其名稱正則后赞草,KV形式加入到對應(yīng)序列用作存取使用。this.add就是一個this[COMMANDS]的set方法吆鹤,不粘貼了厨疙。
再然后便是new Commnad().start()
方法,自然也是在common-bin包下疑务,不廢話沾凄,上代碼
start() {
co(function* () {
const index = this.rawArgv.indexOf('--get-yargs-completions');
if (index !== -1) {
this.rawArgv.splice(index, 2, `--AUTO_COMPLETIONS=${this.rawArgv.join(',')}`);
}
yield this[DISPATCH]();
}.bind(this)).catch(this.errorHandler.bind(this));
}
大名鼎鼎的co
包還是很有魅力的,具體請文章序幕文章知允。重要的在yield this[DISPATCH]()
下撒蟀,繼續(xù)上代碼
* [DISPATCH]() {
// if sub command exist
if (this[COMMANDS].has(commandName)) {
const Command = this[COMMANDS].get(commandName);
...
const command = this.getSubCommandInstance(Command, rawArgv);
yield command[DISPATCH]();
return;
}
...
// print completion for bash
if (context.argv.AUTO_COMPLETIONS) {
...
} else {
// handle by self
yield this.helper.callFn(this.run, [ context ], this);
}
}
其實說到底,也是去實例化了子實例温鸽,也就是本文的/lib/cmd/dev.js
的[DISPATCH]
方法(在此前this[COMMANDS]
的set方法還記得嘛),cmd/dev.js不實現(xiàn)DISPATCH保屯,因此直接跳過。然后到最后去執(zhí)行cmd/dev.js的run()
* run(context) {
...
yield this.helper.forkNode(this.serverBin, devArgs, options);
}
constructor(){
...
this.serverBin = path.join(__dirname, '../start-cluster');
}
很明顯啦涤垫,forkNode(先按照child_process.fork
去理解),對應(yīng)的start-cluster的內(nèi)容為
require(options.framework).startCluster(options);
而option.framework指的就是egg項目姑尺,egg的index.js文件中也指出
exports.startCluster = require('egg-cluster').startCluster;
因此指向調(diào)用的就是egg-cluster啦。
至此告一段落蝠猬。egg-bin的dev指令流程也簡單的梳理了一下切蟋,得益于common-bin,使得egg-bin的代碼不是那么多層級吱雏,當(dāng)然egg-bin不止只有dev
一個使用對象敦姻,其他的瘾境,下篇文章再做分析吧~
稍微休息一下,然后繼續(xù)吧..镰惦、
egg-cluster
egg-cluster的啟動繼承ready+events模式迷守,關(guān)于get-ready,這里不做過多敘述旺入,直接上代碼
const ready = require('get-ready');
const obj = {};
ready.mixin(obj);
// register a callback
obj.ready(() => console.log('ready func 1'));
obj.ready(() => console.log('ready func 2'));
console.log('ready1');
// mark ready
obj.ready(true);
console.log('ready2');
因此得出的console日志如下
/usr/local/bin/node ../ready.js
ready1
ready2
ready func 1
ready func 2
Process finished with exit code 0
結(jié)果很明顯了兑凿,對于mixin的對象,ready方法有鏈?zhǔn)叫Ч瘃虼嗽趀gg-cluster/index.js中有這么一句
exports.startCluster = function(options, callback){
new Master(options).ready(callback);
};
而作為Master.js的constructor上有這么一段
class Master extends EventEmitter{
constructor(){
...
this.ready(() => {
...
});
}
}
因此cluster將會先執(zhí)行構(gòu)造方法里面的ready礼华,然后暴露到callback對象上。至于events呢拗秘,注意listener/on/once/emit方法即可圣絮,具體還請移步大師講述官方文檔。
在master.js的constructor上雕旨,把關(guān)于當(dāng)前的調(diào)查信息羅列出來
class Master extends EventEmitter{
constructor(options) {
...
this.ready(() => {
...
const action = 'egg-ready';
this.messenger.send({ action, to: 'parent', data: { ... });
this.messenger.send({ action, to: 'app', data: this.options });
this.messenger.send({ action, to: 'agent', data: this.options });
});
this.on('agent-exit', this.onAgentExit.bind(this));
this.on('agent-start', this.onAgentStart.bind(this));
this.on('app-exit', this.onAppExit.bind(this));
this.on('app-start', this.onAppStart.bind(this));
this.on('reload-worker', this.onReload.bind(this));
// fork app workers after agent started
this.once('agent-start', this.forkAppWorkers.bind(this));
...
detectPort((err, port) => {
...
this.options.clusterPort = port;
this.forkAgentWorker();
});
}
}
這里又有一個核心大佬放出的detect-port
包github倉庫這個包的作用就是在做一系列規(guī)范化檢查后使用net.createServer
嘗試創(chuàng)建查看端口是否可用扮匠,如果不可用端口+1(使用時7001被占用系統(tǒng)會啟動在7002也是因為這個端口預(yù)創(chuàng)建測試放出探針結(jié)果)自然在master.constructor
上的cb方法就祈禱作用了,會調(diào)用master.js#forkAgentWorker
凡涩,這個方法主要以child_process.fork
啟動了Agent棒搜,Agent繼承鏈,Agent >> Egg.Agent >> Egg.EggApplication活箕。
這里還得提前插一嘴力麸,關(guān)于messenger
,因為接下來的調(diào)用都和消息有關(guān),而egg的內(nèi)部消息傳輸方向有相關(guān)規(guī)定,且傳輸消息均由messenger
承擔(dān)育韩。
messenger
在master.js
的constructor階段創(chuàng)建克蚂,其作用指向便是消息傳輸,在源碼注釋里文件里面有個圖
* master messenger,provide communication between parent, master, agent and app.
*
* ┌────────┐
* │ parent │
* /└────────┘\
* / | \
* / ┌────────┐ \
* / │ master │ \
* / └────────┘ \
* / / \ \
* ┌───────┐ ┌───────┐
* │ agent │ ------- │ app │
* └───────┘ └───────┘
作為調(diào)用端座慰,也就是當(dāng)前請求startCluster的對象陨舱,擁有對process的動作監(jiān)聽,其便是(parent)版仔,在egg-cluster
中index.js
里創(chuàng)建了master
游盲,在master
中依靠接下來的順序創(chuàng)建了agent
和app
,于是四者便連同在一起蛮粮。在messenger中益缎,作為消息的發(fā)送傳遞者,有相關(guān)數(shù)據(jù)指向
調(diào)用方法 | 發(fā)送對象 | to | 備注 |
---|---|---|---|
sendToMaster | this.master.emit | master | |
sendToParent | process.send | parent | |
sendToAppWorker | sendMessage | app | data里receiverPid是全部app_worker的pid |
sendToAgentWorker | sendmessage | agent |
Q: master和parent區(qū)別是什么然想?
從流程上來看莺奔,parent(current process)上創(chuàng)建master
先放出一張啟動順序的消息傳輸圖片,記錄下啟動順序(非全量記錄)
按照這個順序变泄,我們詳述
0. 放出探針(detect-port),獲取可用端口
1. (序號1 --> 3)端口獲取成功 發(fā)送’agent-start‘消息內(nèi)容
和上面貼出代碼一致令哟,this.forkAgentWorker()
方法恼琼,會調(diào)用入child_process
去fork
文件agent_worker.js
const Agent = require(options.framework).Agent;
const agent = new Agent(options);
agent.ready(err => {
// don't send started message to master when start error
if (err) return;
...
process.send({ action: 'agent-start', to: 'master' });
});
這里比較好理解,目前已知option.framework
就是/{項目目錄}/node_module/egg
,因此屏富,這fork的就是egg.Agent
,當(dāng)fork完畢后晴竞,Agent
基于events
調(diào)用this.ready(true)
,便會process發(fā)出消息agent-start
指向master,而為子進程的agent的消息事件,在master上早就等著你了狠半,看這master.js
this.on('agent-start', this.onAgentStart.bind(this));
...
this.once('agent-start', this.forkAppWorkers.bind(this));
on和once的區(qū)別就不講了噩死,當(dāng)首次觸發(fā)agent-start
時,回調(diào)到第二行代碼中神年,執(zhí)行forkAppWorker
方法已维,至此,agent啟動完畢已日,接下來就是啟動worker(app)垛耳。于是乎,很順利的就跳轉(zhuǎn)回master.js
去執(zhí)行接下來的邏輯鏈了捂敌。這里我嚴重艾扮、嚴重既琴、嚴重提示一下占婉,對于創(chuàng)建agent的,是childprocess.fork()
甫恩,先留著逆济,以后做分析。
觸發(fā)到forkAppworker
方法磺箕,貼出大概代碼
forkAppWorkers() {
cfork({
exec: this.getAppWorkerFile(),
...
});
cluster.on('fork', worker => {
...
});
cluster.on('disconnect', worker => {
...
});
cluster.on('exit', (worker, code, signal) => {
...
});
cluster.on('listening', (worker, address) => {
this.messenger.send({
action: 'app-start',
data: { workerPid: worker.process.pid, address },
to: 'master',
from: 'app',
});
});
}
自然和fork出Agent類似奖慌,用了cfork去操作(還是一樣,cfork做分篇介紹)松靡,當(dāng)fork監(jiān)聽途中简僧,會觸發(fā)放出app-start
消息至master,至此雕欺,完成序號1 -> 序號3的消息發(fā)送岛马。
我們趁熱打鐵,繼續(xù)走...
2. (序號3 --> 4)agent啟動成功屠列,發(fā)送’app-start‘消息內(nèi)容
當(dāng)收到前序(序號3:action = 'app-start')啦逆,查找master中相關(guān)監(jiān)聽實現(xiàn)
this.on('app-start', this.onAppStart.bind(this));
onAppStart(data) {
...
const remain = this.isAllAppWorkerStarted ? 0 : this.options.workers - this.startSuccessCount;
...
// send message to agent with alive workers
this.messenger.send({
action: 'egg-pids',
to: 'agent',
data: this.workerManager.getListeningWorkerIds(),
});
...
// Send egg-ready when app is started after launched
if (this.isAllAppWorkerStarted) {
this.messenger.send({ action: 'egg-ready', to: 'app', data: this.options });
}
...
this.isAllAppWorkerStarted = true;
...
if (this.options.sticky) {
this.startMasterSocketServer(err => {
if (err) return this.ready(err);
this.ready(true);
});
} else {
this.ready(true);
}
}
我們能看到,在app啟動方法中會發(fā)送egg-pids
消息至agent
中笛洛,并且判斷isAllAppworkerStarted
夏志,確認都啟動后放出消息egg-ready
至app
關(guān)于egg-pids
,找了一大圈沒有發(fā)現(xiàn)監(jiān)聽方法苛让,在/test/fixture
下有幾個測試demo是對其做了監(jiān)聽沟蔑,在egg
包中湿诊,ipc有個Messenger
做了監(jiān)聽,因此推測當(dāng)前消息是暴露給相關(guān)周邊開發(fā)者瘦材,更有效獲取pid用途,或者是更高深層層次的用法枫吧,我這個小白未能領(lǐng)悟到精神。
引用一下文章序中Egg 源碼分析之 egg-cluster說的比較重要的內(nèi)容
sticky 模式:Master 負責(zé)統(tǒng)一監(jiān)聽對外端口宇色,然后根據(jù)用戶 ip 轉(zhuǎn)發(fā)到固定的 Worker 子進程上九杂,每個 Worker 自己啟動了一個新的本地服務(wù)
非 sticky 模式:每個Worker 都統(tǒng)一啟動服務(wù)監(jiān)聽外部端口
繼續(xù)看,出發(fā)了ready()
,監(jiān)聽事件
this.ready(() => {
...
const action = 'egg-ready';
this.messenger.send({ action, to: 'parent', data: { port: this[REALPORT], address: this[APP_ADDRESS] } });
this.messenger.send({ action, to: 'app', data: this.options });
this.messenger.send({ action, to: 'agent', data: this.options });
// start check agent and worker status
if (this.isProduction) {
this.workerManager.startCheck();
}
});
至此宣蠕,在cluster包下的啟動主流程粗略梳理了一下例隆。大家可以緩口氣,剩下的就是消息egg-ready
消息在Agent
抢蚀、App(Worker)
镀层、Master
中分別作了什么呢?這得看下一個包-egg
皿曲。
egg & egg-core
打個比方唱逢,如果說整個框架組成了一個屋子,egg-bin可以說是門,egg-cluster是客廳,我想egg就是臥室了吧,egg的重要可想而知,對于Master屋休、Agent坞古、和Worker(App),其中Master在egg-cluster中定義劫樟,Agent和Worker皆為egg包下的繼承自EggApplication對象痪枫,
上幾個篇幅已經(jīng)簡單說明了Master的初始化創(chuàng)建情況,接下來我們繼續(xù)聊聊Agent和Worker叠艳。
egg.Agent
agent在官方文章中有相關(guān)定義:
Agent 好比是 Master 給其他 Worker 請的一個『秘書』奶陈,它不對外提供服務(wù),只給 App Worker 打工附较,專門處理一些公共事務(wù)
Agent 雖然是 App Worker 的『小秘』吃粒,但是業(yè)務(wù)相關(guān)的工作不應(yīng)該放到 Agent 上去做,不然把她累垮了就不好了
由于 Agent 的特殊定位拒课,我們應(yīng)該保證它相對穩(wěn)定徐勃。當(dāng)它發(fā)生未捕獲異常,框架不會像 App Worker 一樣讓他退出重啟捕发,而是記錄異常日志疏旨、報警等待人工處理
結(jié)合官方文檔的定義,Agent有如下特點:輕量扎酷、捕獲異常檐涝、少量業(yè)務(wù)代碼
我們先看下Agent是怎么實現(xiàn)的:
Agent對象在egg-cluster創(chuàng)建環(huán)節(jié)中被創(chuàng)建出來,繼承自egg.Agent
對象,該對象繼承EggApplication
,且loader為./lib/loader/agent_worker_loader.js
文件谁榜,繼承自egg-core.eggLoader
對象幅聘,
整體繼承鏈如上,繼續(xù)梳理窃植。
我們先看到對象的constructor
./lib/agent.js
class Agent extends EggApplication {
constructor(options = {}) {
options.type = 'agent';
super(options);
this.loader.load();
// dump config after loaded, ensure all the dynamic modifications will be recorded
this.dumpConfig();
// keep agent alive even it doesn't have any io tasks
setInterval(() => {}, 24 * 60 * 60 * 1000);
this._uncaughtExceptionHandler = this._uncaughtExceptionHandler.bind(this);
process.on('uncaughtException', this._uncaughtExceptionHandler);
}
}
整個構(gòu)造方法做了四件事
- 完成父類構(gòu)建帝蒿、[seq1]
- 驅(qū)動loader執(zhí)行l(wèi)oad方法,[seq2]
- dump相關(guān)配置文件進入./run目錄下巷怜、[seq3]
- 監(jiān)聽異常事件葛超。 [seq4]
steo0. 有些話得說在前面
為什么說有些東西要在前面說明,因為在閱讀源碼的時候延塑,發(fā)現(xiàn)這是最基類的設(shè)計調(diào)用绣张,我先做告知,方便理解关带。
Q1:loader是個什么侥涵?
loader加載器在egg-core包下有相關(guān)實現(xiàn)定義,翻看源碼大致能看到這么個東西 egg-core/lib/loader/egg-loader.js
...
const loaders = [
require('./mixin/plugin'),
require('./mixin/config'),
require('./mixin/extend'),
require('./mixin/custom'),
require('./mixin/service'),
require('./mixin/middleware'),
require('./mixin/controller'),
require('./mixin/router'),
require('./mixin/custom_loader'),
];
for (const loader of loaders) {
Object.assign(EggLoader.prototype, loader);
}
module.exports = EggLoader;
很明顯能看到宋雏,當(dāng)前export對象做了mixin,這也是我感覺egg這么靈活的優(yōu)點芜飘。我們能夠看到作為外圍暴露的loader對象,將會擁有./mixin/[xxxx].js(表中增加的)所有function磨总。所以在子類\實現(xiàn)類下可以隨意調(diào)用任何位置的func嗦明,如果有l(wèi)oader方法想查找實現(xiàn),需要尋找到相關(guān)對象(一般從名字上能分辨出來)舍败。
Q2:loader的指向?
我在閱讀源碼的時候就會發(fā)現(xiàn),agent其實使用agent_loader,而原來本身也有l(wèi)oader,那在EggApplication里面調(diào)用的基礎(chǔ)Egg-Core.loader到底是哪個呢?
答:因為是Symbol,是最外圍定義的那個對象.如果不確定的地方建議debug一下就了解了.
分割線,讓我們繼續(xù)
step1. 我們先看下在父類做了什么./lib/egg.js
(Agent和App都繼承當(dāng)前對象)
父類劍指EggApplication文件招狸,在EggApplication的constructor上,摘要部分信息 ./lib/egg.js
constructor(options = {}) {
options.mode = options.mode || 'cluster';
super(options);
...
this.loader.loadConfig();
...
this.ready(() => process.nextTick(() => {
const dumpStartTime = Date.now();
this.dumpConfig();
this.dumpTiming();
}));
...
this._unhandledRejectionHandler = this._unhandledRejectionHandler.bind(this);
process.on('unhandledRejection', this._unhandledRejectionHandler);
this[CLUSTER_CLIENTS] = [];
this.cluster = (clientClass, options) => {
options = Object.assign({}, this.config.clusterClient, options, {...});
const client = cluster(clientClass, options);
this._patchClusterClient(client);
return client;
};
}
為了簡化理解難度邻薯,我們先抓幾個重要的流程做分析(來自1步驟,這里編號seq1)
- super方法 [seql1.1]
- 驅(qū)使loader.loadConfig() [seq1.2]
- events的ready事件 [seq1.3]
- cluster的初始化 [seq1.4]
我們先跳過seq1.1
(不然量太大了乘凸,扛不住哈哈)厕诡,回來后補,
seq1.2
關(guān)于loader.loadConfig()
是loader的mixin里面(曾經(jīng)的我年輕了,這里有個指向問題,指向的其實不是egg-core.loader,需要先觀察創(chuàng)建這個對象有沒有相關(guān)指向,config.js
Agent
有指向,是agent_work_loader
,也是繼承了這個基類,但是需要看其多態(tài)復(fù)寫),相關(guān)實現(xiàn)請參考源碼营勤,agent_worker.js
class AgentWorkerLoader extends EggLoader {
loadConfig() {
this.loadPlugin();
super.loadConfig();
}
}
強制先執(zhí)行了loadPlugin()
,加載各類插件,相關(guān)加載數(shù)據(jù)請參考源碼,egg-core/lib/mixin/plugin.js
,我摘取幾個順序的方法調(diào)用
this._extendPlugins(this.allPlugins, eggPlugins);
this._extendPlugins(this.allPlugins, appPlugins);
this._extendPlugins(this.allPlugins, customPlugins);
可以看到,先加載了egg-framework本身的框架,然后用戶的app中實現(xiàn)插件和custom自定義插件
關(guān)于super.loadConfig()
就完全是基類方法實現(xiàn)啦,簡述就是讀取了所有你的配置文件灵嫌,具體讀取順序參考源碼注釋,留作記錄葛作。
// plugin config.default
// framework config.default
// app config.default
// plugin config.{env}
// framework config.{env}
// app config.{env}
seq1.3
當(dāng)有ready(true)的時候會觸發(fā)方法,目前沒有仔細去尋找,留作下一階段任務(wù),dumpConfig()
seq1.4
是cluster的初始化,因為是在Application上的,所以無論是agent還是app都可以使用,這里的用途建議參考官方文檔-多進程增強模式和cluster-client這兩個文檔,這里我也有很多疑惑,稍后回來鉆研.
step2. this.loader.load()方法
按照之前Q1的敘述,loader為加載器,但是有個特殊的時,這里的使用對象是個symbol,在相關(guān)的class的get/set方法上做了定義,因此在Agent里面,自然也要尋找相關(guān)定義值,如下圖
./lib/agent.js
const EGG_LOADER = Symbol.for('egg#loader');
const AgentWorkerLoader = require('./loader').AgentWorkerLoader;
...
class Agent extends EggApplication {
...
get [EGG_LOADER]() {
return AgentWorkerLoader;
}
...
}
抽取相關(guān)比較重要的,可以看到,作為Agent,是使用了AgentWorkerLoader,因此我們打開一下這個文件
./lib/loader/agent_loader.js
'use strict';
const EggLoader = require('egg-core').EggLoader;
/**
* Agent worker process loader
* @see https://github.com/eggjs/egg-loader
*/
class AgentWorkerLoader extends EggLoader {
/**
* loadPlugin first, then loadConfig
*/
loadConfig() {
this.loadPlugin();
super.loadConfig();
}
load() {
this.loadAgentExtend();
this.loadContextExtend();
this.loadCustomAgent();
}
}
module.exports = AgentWorkerLoader;
量不大,直接全復(fù)制過來了.我們能夠發(fā)現(xiàn),執(zhí)行的load()方法其實就是三個方法的合集,對于所有插件的執(zhí)行方法理解在其他文章中,稍后整理后放出
step3. dump相關(guān)配置
dumpConfig方法在agent調(diào)用了多次,相關(guān)源碼
dumpConfig() {
const rundir = this.config.rundir;
try {
/* istanbul ignore if */
if (!fs.existsSync(rundir)) fs.mkdirSync(rundir);
// get dumpped object
const { config, meta } = this.dumpConfigToObject();
// dump config
const dumpFile = path.join(rundir, `${this.type}_config.json`);
fs.writeFileSync(dumpFile, CircularJSON.stringify(config, null, 2));
// dump config meta
const dumpMetaFile = path.join(rundir, `${this.type}_config_meta.json`);
fs.writeFileSync(dumpMetaFile, CircularJSON.stringify(meta, null, 2));
} catch (err) {
this.coreLogger.warn(`dumpConfig error: ${err.message}`);
}
}
關(guān)于里面dumpConfigToObject
方法,實則是讀取了全局配置this.config + 所有plugins指向值,做成json輸出到文件,如下源碼所示
dumpConfigToObject() {
let ignoreList;
try {
// support array and set
ignoreList = Array.from(this.config.dump.ignore);
} catch (_) {
ignoreList = [];
}
const json = extend(true, {}, { config: this.config, plugins: this.plugins });
utils.convertObject(json, ignoreList);
return {
config: json,
meta: this.loader.configMeta,
};
}
自然,在egg框架啟動的run/${type}_config.json
文件肯定就有啦,application和agent將會產(chǎn)生如下文件,meta對應(yīng)文件指向,config.json為配置值
- {project_root}/run/
- agent_config.json
- agent_config_meta.json
- application_config.json
- application_config_meta.json
step4. 對異常的捕獲
Agent的作用其中一點就是捕獲異常,對于node異常,可以對全局Process監(jiān)聽uncaughtException事件,做出處理.
egg上加了對日志的打印邏輯處理,可自行查看源碼
至此,egg.Agent構(gòu)造流程梳理完畢.
egg.Application
app也就是worker(下面統(tǒng)一說app吧,方便理解).
app的相對于agent都是繼承同一個對象,業(yè)務(wù)方向能夠感受到更加針對通信,先翻出源碼
application.js
class Application extends EggApplication {
/**
* @class
* @param {Object} options - see {@link EggApplication}
*/
constructor(options = {}) {
options.type = 'application';
super(options);
// will auto set after 'server' event emit
this.server = null;
try {
this.loader.load();
} catch (e) {
// close gracefully
this[CLUSTER_CLIENTS].forEach(cluster.close);
throw e;
}
// dump config after loaded, ensure all the dynamic modifications will be recorded
const dumpStartTime = Date.now();
this.dumpConfig();
this.coreLogger.info('[egg:core] dump config after load, %s', ms(Date.now() - dumpStartTime));
this[WARN_CONFUSED_CONFIG]();
this[BIND_EVENTS]();
}
}
大體方向和agent差不多,具體可以產(chǎn)分成
- 父類構(gòu)建(之前淺談過了,這里就先跳過啦)
- loader的load方法 (Step.2)
- dumpConfig方法(Step.3)
- 觸發(fā) WARN_COUFUSED_CONFIG(Step.4)
- 觸發(fā) BIND_EVENTS(Step.5)
step2. loader.load
在app_worker_loader.js上,對load的定義比agent多點,也取決于設(shè)計理念的不同,相關(guān)源碼,還是一樣,對于插件的方法將會統(tǒng)一整理
class AppWorkerLoader extends EggLoader{
load() {
// app > plugin > core
this.loadApplicationExtend();
this.loadRequestExtend();
this.loadResponseExtend();
this.loadContextExtend();
this.loadHelperExtend();
// app > plugin
this.loadCustomApp();
// app > plugin
this.loadService();
// app > plugin > core
this.loadMiddleware();
// app
this.loadController();
// app
this.loadRouter(); // Dependent on controllers
this.loadCustomLoader();
}
}
Step3. dumpConfig方法
這里的dumConfig指向的不再單純只是父類方法(Agent是直接應(yīng)用父類方法的.)config和plugins的相關(guān)信息
./application.js
/**
* save routers to `run/router.json`
* @private
*/
dumpConfig() {
super.dumpConfig();
// dump routers to router.json
const rundir = this.config.rundir;
const FULLPATH = this.loader.FileLoader.FULLPATH;
try {
const dumpRouterFile = path.join(rundir, 'router.json');
const routers = [];
for (const layer of this.router.stack) {
routers.push({
name: layer.name,
methods: layer.methods,
paramNames: layer.paramNames,
path: layer.path,
regexp: layer.regexp.toString(),
stack: layer.stack.map(stack => stack[FULLPATH] || stack._name || stack.name || 'anonymous'),
});
}
fs.writeFileSync(dumpRouterFile, JSON.stringify(routers, null, 2));
} catch (err) {
this.coreLogger.warn(`dumpConfig router.json error: ${err.message}`);
}
}
這里的方法將會吧router的相關(guān)信息打印到run/router.json文件中寿羞。也符合app應(yīng)當(dāng)承擔(dān)的router路由業(yè)務(wù)設(shè)計理念。
step4. 觸發(fā)WARN_COUFUSED_CONFIG方法
能夠看到對一個全局this.config.confusedConfiguration對象作出遍歷合法判斷赂蠢,(貿(mào)貿(mào)然來句,這個框架各種this對象,有必要深挖一下,整理一下)后續(xù)分析吧
step5. 觸發(fā)BIND_EVENTS
待分析绪穆。
關(guān)于對啟動后的http/tcp/socket的監(jiān)聽邏輯。未完待續(xù)