(三)egg-核心功能

(三)egg-核心功能

    • (1)应用部署
    • (2)日志
    • (3)HttpClient
    • (4)session
    • (5)多进程和进程间通信
      • 1、Cluster
      • 2、多进程模型

(1)应用部署

1、构建
从源码到真正运行,一般会拆分为构建和部署两个步骤,做到一次构建多次部署。
js语言构建的过程主要是下载依赖,一般安装依赖会指定node_env=production或者只安装dependencies的依赖,构建完成之后打包成tgz文件,部署时解压启动即可。

2、部署
部署服务器需要预装Node.js,框架支持的版本为>=8.0.0
框架内置了egg-cluster来启动Master进程,Master有足够的稳定性,不再需要使用pm2等进程守护模块。同时框架也提供了egg-scripts来支持线上环境的运行和停止。

3、监控
对服务进行性能监控,内存泄露分析以及鼓掌排除等。

  • Node.js性能平台(alinode)
  • NSolid

(2)日志

  • 所有日志文件默认都放在${appInfo.root}/logs/${appInfo.name} 路径下,例如 /home/admin/logs/example-app。
  • 在本地开发环境 (env: local) 和单元测试环境 (env: unittest),为了避免冲突以及集中管理,日志会打印在项目目录下的 logs 目录,例如 /path/to/example-app/logs/example-app。
    自定义日志路径:
// config/config.${env}.js
exports.logger = {
  dir: '/path/to/your/custom/log/dir',
};

日志分类:

  • appLogger ${appInfo.name}-web.log,例如 example-app-web.log,应用相关日志,供应用开发者使用的日志。
  • coreLogger egg-web.log 框架内核、插件日志。
  • errorLogger common-error.log 实际一般不会直接使用它,任何 logger 的 .error() 调用输出的日志都会重定向到这里,重点通过查看此日志定位异常。
  • agentLogger egg-agent.log agent 进程日志,框架和使用到 agent 进程执行任务的插件会打印一些日志到这里。

日志打印:


//处理请求时的日志
ctx.logger.debug('debug info');
ctx.logger.info('some request data: %j', ctx.request.body);
ctx.logger.warn('WARNNING!!!!');

// 错误日志记录,直接会将错误日志完整堆栈信息记录下来,并且输出到 errorLog 中
// 为了保证异常可追踪,必须保证所有抛出的异常都是 Error 类型,因为只有 Error 类型才会带上堆栈信息,定位到问题。
ctx.logger.error(new Error('whoops'));

//应用级别日志
// app.js
module.exports = app => {
  app.logger.debug('debug info');
  app.logger.info('启动耗时 %d ms', Date.now() - start);
  app.logger.warn('warning!');

  app.logger.error(someErrorObj);
};

// agent Logger
// agent.js
module.exports = agent => {
  agent.logger.debug('debug info');
  agent.logger.info('启动耗时 %d ms', Date.now() - start);
  agent.logger.warn('warning!');

  agent.logger.error(someErrorObj);
};

日志级别:

NONE、DEBUG、INFO、WARN、ERROR五个级别。
默认只会输出INFO及以上级别的日志到文件中,可以修改输出到文件日志的级别

为避免一些插件的调试日志在生产环境打印导致性能问题,生产环境默认禁止打印DEBUG级别的日志,可以打开allowDebugAtProd配置项在生产环境答应debug日志进行调试。

修改输出到终端日志的级别:

//打印所有级别日志到终端
// config/config.${env}.js
exports.logger = {
  consoleLevel: 'DEBUG',
};

//关闭所有打印到终端的日志
// config/config.${env}.js
exports.logger = {
  consoleLevel: 'NONE',
};

//开启正式环境下终端日志的输出
// config/config.${env}.js
exports.logger = {
  disableConsoleAfterReady: false,
};

日志切割:

1、框架默认按照天进行切割,在每日凌晨按照.log.YYYY-MM-DD文件名进行切割
2、按照文件大小进行切割

//当文件超过2G时进行切割
// config/config.${env}.js
const path = require('path');

module.exports = appInfo => {
  return {
    logrotator: {
      filesRotateBySize: [
        path.join(appInfo.root, 'logs', appInfo.name, 'egg-web.log'),
      ],
      maxFileSize: 2 * 1024 * 1024 * 1024,
    },
  };
};

3、按小时进行切割

// config/config.${env}.js
const path = require('path');

module.exports = appInfo => {
  return {
    logrotator: {
      filesRotateByHour: [
        path.join(appInfo.root, 'logs', appInfo.name, 'common-error.log'),
      ],
    },
  };
};

频繁的磁盘IO会影响性能,所以一般采用的文件日志写入的策略是:日志同步写入内存,异步每隔一段时间刷盘。

(3)HttpClient

1、通过app使用HTTPClient

框架在应用初始化的时候,会自动将HTTPClient初始化到app.httpclient,同时增加了app.curl(url,options)方法,

// app.js
module.exports = app => {
  app.beforeStart(async () => {
    // 示例:启动的时候去读取 https://registry.npm.taobao.org/egg/latest 的版本信息
    const result = await app.curl('https://registry.npm.taobao.org/egg/latest', {
      dataType: 'json',
    });
    app.logger.info('Egg latest version: %s', result.data.version);
  });
};

2、通过ctx使用HTTPClient

框架提供了ctx.curl(url,options)ctx.httpClient保持跟在app下使用的体验一致,这样既可方便的使用ctx.curl()方法完成一次http请求。

(4)session

当用户请求没有导致Session被修改时,框架不会延长session的有效期,为了不让用户退出登录态,框架提供了一个renew配置项用于实现此功能,会在发现用户session的有效期仅剩下最大有效期的一半的时候,重置session的有效期。

// config/config.default.js
module.exports = {
  session: {
    renew: true,
  },
};

针对特定用户的session有效时间设置,可以通过ctx.session.maxAge=来实现

(5)多进程和进程间通信

js代码是单线程的,即Node.js进程只能运行在一个cpu上,Node.js官方提供的解决方案是cluster模块,

单个 Node.js 实例在单线程环境下运行。为了更好地利用多核环境,用户有时希望启动一批 Node.js 进程用于加载。
集群化模块使得你很方便地创建子进程,以便于在服务端口之间共享。

1、Cluster

在服务器上同时启动多个进程,每个进程里跑的都是同一份源码,这些进程可以同时监听一个端口。

其中,负责启动其他进程的叫做Master 进程,只用来负责启动其他进程
被启动的进程叫做Worker进程,接收请求,对外提供服务。Worker进程一般根据服务器的cpu来定。

2、多进程模型

进程守护:

当一个worker遇到未捕获的异常的时候,此时它处于一个不确定状态,应该让改进程优雅退出:

  • 1)关闭异常 Worker 进程所有的 TCP Server(将已有的连接快速断开,且不再接收新的连接),断开和 Master 的 IPC 通道,不再接受新的用户请求。
  • 2)Master 立刻 fork 一个新的 Worker 进程,保证在线的『工人』总数不变。
  • 3)异常 Worker 等待一段时间,处理完已经接受的请求后退出。

OOM、系统异常:

  • 此时Master应该立即fork一个新的worker

Agent机制:

Agent worker是一个单独的进程,专门用来处理一些公共事务。

综上:
框架的启动时序如下

  • 1、Master 启动后先 fork Agent 进程
  • 2、Agent 初始化成功后,通过 IPC 通道通知 Master
  • 3、Master 再 fork 多个 App Worker
  • 4、App Worker 初始化成功,通知 Master
  • 5、所有的进程初始化成功后,Master 通知 Agent 和 Worker 应用启动成功

Agent的用法

agent.js 的代码会执行在 agent 进程上,app.js 的代码会执行在 Worker 进程上,他们通过框架封装的 messenger 对象进行进程间通讯(IPC)

// agent.js
module.exports = agent => {
  // 在这里写你的初始化逻辑

  // 也可以通过 messenger 对象发送消息给 App Worker
  // 但需要等待 App Worker 启动成功后才能发送,不然很可能丢失
  agent.messenger.on('egg-ready', () => {
    const data = { ... };
    agent.messenger.sendToApp('xxx_action', data);
  });
};

// app.js
module.exports = app => {
  app.messenger.on('xxx_action', data => {
    // ...
  });
};

进程间通信

cluster 的 IPC 通道只存在于 Master 和 Worker/Agent 之间,Worker 与 Agent 进程互相间是没有的。那么 Worker 之间想通讯通过 Master 来转发。

发送:

  • app.messenger.broadcast(action, data):发送给所有的 agent / app 进程(包括自己)
  • app.messenger.sendToApp(action, data): 发送给所有的 app 进程
    在 app 上调用该方法会发送给自己和其他的 app 进程
    在 agent 上调用该方法会发送给所有的 app 进程
  • app.messenger.sendToAgent(action, data): 发送给 agent 进程
    在 app 上调用该方法会发送给 agent 进程
    在 agent 上调用该方法会发送给 agent 自己
  • agent.messenger.sendRandom(action, data):
    app 上没有该方法(现在 Egg 的实现是等同于 sentToAgent)
    agent 会随机发送消息给一个 app 进程(由 master 来控制发送给谁)
  • app.messenger.sendTo(pid, action, data): 发送给指定进程
// app.js
module.exports = app => {
  // 注意,只有在 egg-ready 事件拿到之后才能发送消息
  app.messenger.once('egg-ready', () => {
    app.messenger.sendToAgent('agent-event', { foo: 'bar' });
    app.messenger.sendToApp('app-event', { foo: 'bar' });
  });
}

接收:

app.messenger.on(action, data => {
  // process data
});
app.messenger.once(action, data => {
  // process data
});

你可能感兴趣的:(nodejs)