前言
本次解读主要以阅读代码的逻辑编写,而非直接归总性的编写。虽然可能会在阅读时产生疑惑,但是胜在推敲理解的过程。
看前准备
因为Agent和Application的类都继承于EggCore类,看EggCore后再看本章会事半功倍。
+--------------+ 实例 +--------------+
| Agent 子进程 | --------> | Agent 类 |
+--------------+ +---------------+
/ \
child_process.fork / \ 继承
/ \
+---------------+ +-------------------+ 继承 +------------+ 继承 +------------+
| Master 主进程 | | EggApplication 类 | ------> | EggCore 类 | -----> | Koa |
+-------------- + +------------------ + +------------+ +------------+
\ /
\ / 继承
cluster.fork \ /
+---------------+ 实例 +----------------+
| Worker 子进程 | -------> | Application 类 |
+---------------+ +-----------------+
Agent和Worker机制——官方
Node.js 多进程方案貌似已经成型,这也是我们早期线上使用的方案。但后来我们发现有些工作其实不需要每个 Worker 都去做,如果都做,一来是浪费资源,更重要的是可能会导致多进程间资源访问冲突。举个例子:生产环境的日志文件我们一般会按照日期进行归档,在单进程模型下这再简单不过了:
- 每天凌晨 0 点,将当前日志文件按照日期进行重命名
- 销毁以前的文件句柄,并创建新的日志文件继续写入
试想如果现在是 4 个进程来做同样的事情,是不是就乱套了。所以,对于这一类后台运行的逻辑,我们希望将它们放到一个单独的进程上去执行,这个进程就叫 Agent Worker,简称 Agent。Agent 好比是 Master 给其他 Worker 请的一个『秘书』,它不对外提供服务,只给 App Worker 打工,专门处理一些公共事务。现在我们的多进程模型就变成下面这个样子了。
+--------+ +-------+
| Master |<-------->| Agent |
+--------+ +-------+
^ ^ ^
/ | \
/ | \
/ | \
v v v
+----------+ +----------+ +----------+
| Worker 1 | | Worker 2 | | Worker 3 |
+----------+ +----------+ +----------+
以官方给的例子总结:Master包工头、Worker工人、Agent工人的集体管理秘书。
Agent源码阅读
const AgentWorkerLoader = require('./loader').AgentWorkerLoader;
class Agent extends EggApplication { // Agent继承于EggApplication、EggApplication继承于EggCore
constructor(options = {}) {
options.type = 'agent'; // 定义类型,EggCore只接受agent或者application否则会报错,在上一章中Eggcore中可以看到
super(options);
this.loader.load(); // 执行agent_worker_loader.js中的load
this.dumpConfig(); // 在上面的load之后执行egg.js中的dumpConfig,保证了所有的修改被保存
// 保持连接,24小时执行一次
setInterval(() => {}, 24 * 60 * 60 * 1000);
// 异常处理
this._uncaughtExceptionHandler = this._uncaughtExceptionHandler.bind(this);
process.on('uncaughtException', this._uncaughtExceptionHandler);
}
get [EGG_LOADER]() {
return AgentWorkerLoader;
}
}
先看agent_worker_loader.js
的load方法
// 框架扩展
load() {
this.loadAgentExtend();
this.loadContextExtend();
this.loadCustomAgent();
}
再看对应执行的方法在egg-core下的lib/loader/mixin/entend.js
loadAgentExtend() {
this.loadExtend('agent', this.app);
},
loadContextExtend() {
this.loadExtend('context', this.app.context);
},
// 通过Object.defineProperty把相应的文件模块挂载到扩展的对象上
loadExtend(name, proto) {
this.timing.start(`Load extend/${name}.js`);
// All extend files
const filepaths = this.getExtendFilePaths(name);
// if use mm.env and serverEnv is not unittest
const isAddUnittest = 'EGG_MOCK_SERVER_ENV' in process.env && this.serverEnv !== 'unittest';
for (let i = 0, l = filepaths.length; i < l; i++) {
const filepath = filepaths[i];
filepaths.push(filepath + `.${this.serverEnv}`);
if (isAddUnittest) filepaths.push(filepath + '.unittest');
}
const mergeRecord = new Map();
for (let filepath of filepaths) {
filepath = this.resolveModule(filepath);
if (!filepath) {
continue;
} else if (filepath.endsWith('/index.js')) {
// TODO: remove support at next version
deprecate(`app/extend/${name}/index.js is deprecated, use app/extend/${name}.js instead`);
}
const ext = this.requireFile(filepath);
const properties = Object.getOwnPropertyNames(ext)
.concat(Object.getOwnPropertySymbols(ext));
for (const property of properties) {
if (mergeRecord.has(property)) {
debug('Property: "%s" already exists in "%s",it will be redefined by "%s"',
property, mergeRecord.get(property), filepath);
}
// Copy descriptor
let descriptor = Object.getOwnPropertyDescriptor(ext, property);
let originalDescriptor = Object.getOwnPropertyDescriptor(proto, property);
if (!originalDescriptor) {
// try to get descriptor from originalPrototypes
const originalProto = originalPrototypes[name];
if (originalProto) {
originalDescriptor = Object.getOwnPropertyDescriptor(originalProto, property);
}
}
if (originalDescriptor) {
// don't override descriptor
descriptor = Object.assign({}, descriptor);
if (!descriptor.set && originalDescriptor.set) {
descriptor.set = originalDescriptor.set;
}
if (!descriptor.get && originalDescriptor.get) {
descriptor.get = originalDescriptor.get;
}
}
Object.defineProperty(proto, property, descriptor);
mergeRecord.set(property, filepath);
}
debug('merge %j to %s from %s', Object.keys(ext), name, filepath);
}
this.timing.end(`Load extend/${name}.js`);
},
Application源码阅读
class Application extends EggApplication {
// Application继承于EggApplication、EggApplication继承于EggCore
// EggApplication对于EggCore增加了一些方法例如createAnonymousContext,可以在app的环境中使用const ctx = this.app.createAnonymousContext();
constructor(options = {}) {
options.type = 'application'; // 定义类型
super(options);
this.server = null;
try {
this.loader.load(); // 执行app_worker_loader.js中的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]();
}
// ...省略代码
}
再看app_worker_loader.js
的load方法
load() {
// 框架扩展
this.loadApplicationExtend();
this.loadRequestExtend();
this.loadResponseExtend();
this.loadContextExtend();
this.loadHelperExtend();
this.loadCustomLoader();
// app > plugin
this.loadCustomApp();
// app > plugin
this.loadService();
// app > plugin > core
this.loadMiddleware();
// app
this.loadController();
// app
this.loadRouter(); // Dependent on controllers
}
框架扩展可以查看官方文档:框架扩展
看完以上代码,应该已经拨开了egg框架所有的迷雾了,在上一章egg-core中讲到的集万千loader于一身EggLoader之后,通过这里的loader启动的时候做相应的执行。按顺序加载Service、Middleware、Controller、Router等等。
总结
这一章非常的短,因为上一章的egg-core才是egg的核心内容,Agent和Application启到的主要作用就是执行egg-core的核心内容。下一章将会对整个egg做一个总结。