前言

本次解读主要以阅读代码的逻辑编写,而非直接归总性的编写。虽然可能会在阅读时产生疑惑,但是胜在推敲理解的过程。

看前准备

因为Agent和Application的类都继承于EggCore类,看EggCore后再看本章会事半功倍。

  1. +--------------+ 实例 +--------------+
  2. | Agent 子进程 | --------> | Agent |
  3. +--------------+ +---------------+
  4. / \
  5. child_process.fork / \ 继承
  6. / \
  7. +---------------+ +-------------------+ 继承 +------------+ 继承 +------------+
  8. | Master 主进程 | | EggApplication | ------> | EggCore | -----> | Koa |
  9. +-------------- + +------------------ + +------------+ +------------+
  10. \ /
  11. \ / 继承
  12. cluster.fork \ /
  13. +---------------+ 实例 +----------------+
  14. | Worker 子进程 | -------> | Application |
  15. +---------------+ +-----------------+

Agent和Worker机制——官方

Node.js 多进程方案貌似已经成型,这也是我们早期线上使用的方案。但后来我们发现有些工作其实不需要每个 Worker 都去做,如果都做,一来是浪费资源,更重要的是可能会导致多进程间资源访问冲突。举个例子:生产环境的日志文件我们一般会按照日期进行归档,在单进程模型下这再简单不过了:

  1. 每天凌晨 0 点,将当前日志文件按照日期进行重命名
  2. 销毁以前的文件句柄,并创建新的日志文件继续写入

试想如果现在是 4 个进程来做同样的事情,是不是就乱套了。所以,对于这一类后台运行的逻辑,我们希望将它们放到一个单独的进程上去执行,这个进程就叫 Agent Worker,简称 Agent。Agent 好比是 Master 给其他 Worker 请的一个『秘书』,它不对外提供服务,只给 App Worker 打工,专门处理一些公共事务。现在我们的多进程模型就变成下面这个样子了。

  1. +--------+ +-------+
  2. | Master |<-------->| Agent |
  3. +--------+ +-------+
  4. ^ ^ ^
  5. / | \
  6. / | \
  7. / | \
  8. v v v
  9. +----------+ +----------+ +----------+
  10. | Worker 1 | | Worker 2 | | Worker 3 |
  11. +----------+ +----------+ +----------+

以官方给的例子总结:Master包工头、Worker工人、Agent工人的集体管理秘书。

Agent源码阅读

  1. const AgentWorkerLoader = require('./loader').AgentWorkerLoader;
  2. class Agent extends EggApplication { // Agent继承于EggApplication、EggApplication继承于EggCore
  3. constructor(options = {}) {
  4. options.type = 'agent'; // 定义类型,EggCore只接受agent或者application否则会报错,在上一章中Eggcore中可以看到
  5. super(options);
  6. this.loader.load(); // 执行agent_worker_loader.js中的load
  7. this.dumpConfig(); // 在上面的load之后执行egg.js中的dumpConfig,保证了所有的修改被保存
  8. // 保持连接,24小时执行一次
  9. setInterval(() => {}, 24 * 60 * 60 * 1000);
  10. // 异常处理
  11. this._uncaughtExceptionHandler = this._uncaughtExceptionHandler.bind(this);
  12. process.on('uncaughtException', this._uncaughtExceptionHandler);
  13. }
  14. get [EGG_LOADER]() {
  15. return AgentWorkerLoader;
  16. }
  17. }

先看agent_worker_loader.js的load方法

  1. // 框架扩展
  2. load() {
  3. this.loadAgentExtend();
  4. this.loadContextExtend();
  5. this.loadCustomAgent();
  6. }

再看对应执行的方法在egg-core下的lib/loader/mixin/entend.js

  1. loadAgentExtend() {
  2. this.loadExtend('agent', this.app);
  3. },
  4. loadContextExtend() {
  5. this.loadExtend('context', this.app.context);
  6. },
  7. // 通过Object.defineProperty把相应的文件模块挂载到扩展的对象上
  8. loadExtend(name, proto) {
  9. this.timing.start(`Load extend/${name}.js`);
  10. // All extend files
  11. const filepaths = this.getExtendFilePaths(name);
  12. // if use mm.env and serverEnv is not unittest
  13. const isAddUnittest = 'EGG_MOCK_SERVER_ENV' in process.env && this.serverEnv !== 'unittest';
  14. for (let i = 0, l = filepaths.length; i < l; i++) {
  15. const filepath = filepaths[i];
  16. filepaths.push(filepath + `.${this.serverEnv}`);
  17. if (isAddUnittest) filepaths.push(filepath + '.unittest');
  18. }
  19. const mergeRecord = new Map();
  20. for (let filepath of filepaths) {
  21. filepath = this.resolveModule(filepath);
  22. if (!filepath) {
  23. continue;
  24. } else if (filepath.endsWith('/index.js')) {
  25. // TODO: remove support at next version
  26. deprecate(`app/extend/${name}/index.js is deprecated, use app/extend/${name}.js instead`);
  27. }
  28. const ext = this.requireFile(filepath);
  29. const properties = Object.getOwnPropertyNames(ext)
  30. .concat(Object.getOwnPropertySymbols(ext));
  31. for (const property of properties) {
  32. if (mergeRecord.has(property)) {
  33. debug('Property: "%s" already exists in "%s",it will be redefined by "%s"',
  34. property, mergeRecord.get(property), filepath);
  35. }
  36. // Copy descriptor
  37. let descriptor = Object.getOwnPropertyDescriptor(ext, property);
  38. let originalDescriptor = Object.getOwnPropertyDescriptor(proto, property);
  39. if (!originalDescriptor) {
  40. // try to get descriptor from originalPrototypes
  41. const originalProto = originalPrototypes[name];
  42. if (originalProto) {
  43. originalDescriptor = Object.getOwnPropertyDescriptor(originalProto, property);
  44. }
  45. }
  46. if (originalDescriptor) {
  47. // don't override descriptor
  48. descriptor = Object.assign({}, descriptor);
  49. if (!descriptor.set && originalDescriptor.set) {
  50. descriptor.set = originalDescriptor.set;
  51. }
  52. if (!descriptor.get && originalDescriptor.get) {
  53. descriptor.get = originalDescriptor.get;
  54. }
  55. }
  56. Object.defineProperty(proto, property, descriptor);
  57. mergeRecord.set(property, filepath);
  58. }
  59. debug('merge %j to %s from %s', Object.keys(ext), name, filepath);
  60. }
  61. this.timing.end(`Load extend/${name}.js`);
  62. },

Application源码阅读

  1. class Application extends EggApplication {
  2. // Application继承于EggApplication、EggApplication继承于EggCore
  3. // EggApplication对于EggCore增加了一些方法例如createAnonymousContext,可以在app的环境中使用const ctx = this.app.createAnonymousContext();
  4. constructor(options = {}) {
  5. options.type = 'application'; // 定义类型
  6. super(options);
  7. this.server = null;
  8. try {
  9. this.loader.load(); // 执行app_worker_loader.js中的load
  10. } catch (e) {
  11. // close gracefully
  12. this[CLUSTER_CLIENTS].forEach(cluster.close);
  13. throw e;
  14. }
  15. // dump config after loaded, ensure all the dynamic modifications will be recorded
  16. const dumpStartTime = Date.now();
  17. this.dumpConfig();
  18. this.coreLogger.info('[egg:core] dump config after load, %s', ms(Date.now() - dumpStartTime));
  19. this[WARN_CONFUSED_CONFIG]();
  20. this[BIND_EVENTS]();
  21. }
  22. // ...省略代码
  23. }

再看app_worker_loader.js的load方法

  1. load() {
  2. // 框架扩展
  3. this.loadApplicationExtend();
  4. this.loadRequestExtend();
  5. this.loadResponseExtend();
  6. this.loadContextExtend();
  7. this.loadHelperExtend();
  8. this.loadCustomLoader();
  9. // app > plugin
  10. this.loadCustomApp();
  11. // app > plugin
  12. this.loadService();
  13. // app > plugin > core
  14. this.loadMiddleware();
  15. // app
  16. this.loadController();
  17. // app
  18. this.loadRouter(); // Dependent on controllers
  19. }

框架扩展可以查看官方文档:框架扩展
看完以上代码,应该已经拨开了egg框架所有的迷雾了,在上一章egg-core中讲到的集万千loader于一身EggLoader之后,通过这里的loader启动的时候做相应的执行。按顺序加载Service、Middleware、Controller、Router等等。

总结

这一章非常的短,因为上一章的egg-core才是egg的核心内容,Agent和Application启到的主要作用就是执行egg-core的核心内容。下一章将会对整个egg做一个总结。