声享正努力加载中...

ThinkJS 3 设计与运维

关于我

  • 李成银 / welefen
  • 奇舞团
  • ThinkJS 作者
  • 擅长工程化和性能优化
  • 带一些团队

大纲

  • 2.x 版本的痛点
  • 3.0 版本的设计
  • 为方便运维的设计

面向未来开发的 Node.js 框架

  • 使用最新的 ES next 语法
  • 集成最佳实践
  • 提供常用的功能
  • 大而全
  • 社区建立不起来

面临的挑战

基于 Koa

Koa 1

Koa 2

基于 Koa 2

直接使用 async/await,理念与 ThinkJS 相同

Async functions VS Generator

  • 为解决异步而生,async/await 更加语义化。而 Generator 本身是个迭代器,只是被发现可以用来解决异步问题
  • 要求 await 后面必须是 Promise 接口,而 yield 后面没有任何限制
  • 不需要额外的执行器,Generator 需要借助 co 这样的执行器
  • 没有类似 yield 和 yield * 的问题
  • 可以和 Arrow function 一起使用,generator 不能

Koa 2 缺少的东西

  • 多进程管理
  • 功能过于简单,扩展机制
  • 配置的统一管理
  • 适配器
  • 自动编译/自动更新机制
  • 需要团队整理中间件,缺少最佳实践
  • 基于 Koa 2
    • 直接使用 async/await,不再支持 yield/*
    • 直接用 Koa 2 的 middleware
  • 精简核心,方便扩展
    • application, context, request, response
    • think, controller, logic, service
  • 适配器,配合扩展可以使用更多的类型
    • session
    • websocket
    • cache
    • model

自动编译,自动更新

自动编译

删除 transpiler 就可关闭编译

const Application = require('thinkjs')
const babel = require('think-babel')
const watcher = require('think-watcher')
const notifier = require('node-notifier')

const instance = new Application({
  ROOT_PATH: __dirname,
  watcher,
  transpiler: [babel, {
    presets: ['think-node'],
  }],
  notifier: notifier.notify.bind(notifier),
  env: 'development',
})

instance.run()

middleware 使用

middleware 配置统一管理

src/config/middleware.js

const path = require('path');

const isDev = think.env === 'development';

module.exports = [
  {
    handle: 'meta',
    options: {
      logRequest: isDev,
      sendResponseTime: true
    }
  },
  {
    handle: 'resource',
    enable: isDev,
    options: {
      root: path.join(think.ROOT_PATH, 'www'),
      publicPath: /^\/(static|favicon\.ico)/
    }
  },
  {
    handle: 'trace',
    enable: !think.isCli,
    options: {
      debug: isDev,
      error: (err) => {
        think.logger.error(err);
      }
    }
  },
  {
    handle: 'payload',
    options: {
      keepExtensions: true
    }
  },
  {
    handle: 'router',
    options: {}
  },
  'logic',
  'controller'
];

trace middleware

Extend

extend

src/config/extend.js

const view = require('think-view');
const model = require('think-model');
const fetch = require('think-fetch');
const session = require('think-session');

module.exports = [
  view, // make application support view
  model(think.app),
  fetch,
  session
];

项目里的 extend

  • src/extend/application.js
  • src/extend/context.js
  • src/extend/request.js
  • src/extend/response.js
  • src/extend/think.js
  • src/extend/controller.js
  • src/extend/logic.js
  • src/extend/service.js

扩展 controller

// src/extend/controller.js
module.exports = {
  get isMobile() {
    const userAgent = this.userAgent.toLowerCase();
    const mList = ['iphone', 'android'];
    return mList.some(item => userAgent.indexOf(item) > -1);
  }
}
// src/controller/index.js
module.exports = class extends think.Controller {
  indexAction() {
    if (this.isMobile) {
      ...
    }
  }
}

Adapter 模式

  • 解决一个功能不同实现下接口不统一的问题
  • 提供统一的接口,可以通过配置快速切换

Adapter 模式(缓存)

Adapter 配置

const mysql = require('think-model-mysql');
const postgresql = require('think-model-postgresql');
exports.model = {
  type: 'mysql',
  common: {
    logConnect: isDev,
    logSql: true
  },
  mysql: {
    handle: mysql,
    database: 'test',
    prefix: 'think_',
    host: '127.0.0.1',
    port: '',
    user: 'root',
    password: '12345678'
  },
  postgre: {
    handle: postgresql,
    ...
  }
};

Adapter 使用

module.exports = class extend think.Controller {
  indexAction() {
    // 默认使用 mysql 数据库
    const user = this.model('user');
    // 动态切换为 postgre 数据库
    const anotherUser = this.model('user', 'postgre');
  }
}

Adapter 扩展

  • src/adapter/cache/xx.js
  • src/adapter/session/xx.js

https://github.com/thinkjs/think-awesome#adapters

多进程管理

多进程管理

  • 通过 cluster.fork 方法创建子进程
    • 监听 uncaughtException 和 unhandledRejection,通知处理函数
    • 遇到错误后通知主进程 fork 新的子进程,关闭当前进程
  • 捕获重启指令(USR2)
    • 依序 fork 新的子进程,关闭之前的进程
    • 通过 env THINK_PREV_PID 获取上一个进程的 pid,以便关联监控数据

多进程通信

  • think.messenger.broadcast
    • 子进程发起,广播到每个子进程
  • think.messenger.consume
    • 消费者,只有一个子进程触发
  • think.messenger.map
    • 广播到每个子进程,并获取所有返回值

监控内存和 CPU

think.messager.on('monitorMapEvent', () => {
  const data = {
    cpu: getCpuData(),
    memory: getMemoryData(),
    pid: process.pid,
    prevPid: process.env.THINK_PREV_PID
  }
  return data;
})
think.messenger.on('monitorConsumeEvent', () => {
   setInterval(async () => {
     // data 数组长度为子进程的个数
     // [{cpu: xxx, memory: xxx, pid: xxx, prevPid: xxx}, {cpu: yyy, memory: yyy, ...}, ...]
     const data = await think.messenger.map('monitorMapEvent');
     postData('http://example.com/api/monitor', data);
   }, 60 * 1000)
});

think.messenger.consume('monitorEvent');
  • think-trace 中间件统一处理错误
  • socket 使用连接池管理,错误处理

谢谢大家

Your browser does not support the canvas element.

ThinkJS 3 设计与运维

创建于2017年10月23日

李成银的更多幻灯片

  • 聊聊 promisify

    2017年11月20日 1
  • Node.js 进阶开发

    2017年11月01日 29

分享“幻灯片”

HI, 亲爱的用户

为获得声享编辑器的最佳体验,建议使用
360安全浏览器极速模式?
或最新版Chrome浏览器

下载360浏览器 下载最新版Chrome