Секрет удвоения производительности сервисов Node.js (1)

Node.js внешний интерфейс внешний фреймворк
Секрет удвоения производительности сервисов Node.js (1)

предисловие

Студенты, которые использовали Node.js для разработки, должны были использовать koa из-за его простого и элегантного метода написания в сочетании с богатой экологией сообщества, а многие существующие платформы Node.js основаны на koa для вторичной инкапсуляции. Но когда дело доходит до производительности, мы должны упомянуть известный фреймворк:fastify, по названию видно, что характеристики у него быстрые, официально даныBenchmarksЕще более нативный, чем Node.jshttp.ServerДаже быстрее.

Benchmarks

Ключ к повышению производительности

давайте сначала посмотримfastifyКак запустить службу.

# 安装 fastify
npm i -S fastify@3.9.1
// 创建服务实例
const fastify = require('fastify')()

app.get('/', {
  schema: {
    response: {
      // key 为响应状态码
      '200': {
        type: 'object',
        properties: {
          hello: { type: 'string' }
        }
      }
    }
  }
}, async () => {
  return { hello: 'world' }
})

// 启动服务
;(async () => {
  try {
    const port = 3001 // 监听端口
    await app.listen(port)
    console.info(`server listening on ${port}`)
  } catch (err) {
    console.error(err)
    process.exit(1)
  }
})()

Как видно из приведенного выше кода,fastifyТело ответа запроса определяетschema,fastifyВ дополнение к определению тела ответаschema, также поддерживает следующие определения данныхschema:

  1. body: если это метод POST или PUT, проверьте тело запроса;
  2. query: проверьте параметры запроса URL-адреса;
  3. params: проверьте параметр URL;
  4. response: фильтровать и генерировать тело ответаschema.
app.post('/user/:id', {
  schema: {
    params: {
      type: 'object',
      properties: {
      	id: { type: 'number' }
      }
    },
    response: {
      // 2xx 表示 200~299 的状态都适用此 schema
      '2xx': {
        type: 'object',
        properties: {
          id: { type: 'number' },
          name: { type: 'string' }
        }
      }
    }
  }
}, async (req) => {
  const id = req.params.id
  const userInfo = await User.findById(id)
  // Content-Type 默认为 application/json
  return userInfo
})

ПозволятьfastifyСекрет улучшения производительности в том, что он возвращаетapplication/jsonПри вводе данных он не использует собственныйJSON.stringify, но повторно реализовал набор методов сериализации JSON внутри, этоschemaЭто ключ к удвоению производительности сериализации JSON.

Как сериализовать JSON

изучениеfastifyПрежде чем сериализовать данные JSON, давайте посмотримJSON.stringifyКакие утомительные шаги должны пройти, здесь мы ссылаемся на Дуглас Крукфорд (создатель формата JSON)JSON-jsреализовано вstringifyметод.

JSON-js:GitHub.com/Дуглас Кро С…

// 只展示 JSON.stringify 核心代码,其他代码有所省略
if (typeof JSON !== "object") {
  JSON = {};
}
JSON.stringify = function (value) {
  return str("", {"": value})
}
function str(key, holder) {
  var value = holder[key];
  switch(typeof value) {
    case "string":
      return quote(value);
    case "number":
      return (isFinite(value)) ? String(value) : "null";
    case "boolean":
    case "null":
      return String(value);
    case "object":
      if (!value) {
        return "null";
      }
      partial = [];
      if (Object.prototype.toString.apply(value) === "[object Array]") {
        // 处理数组
        length = value.length;
        for (i = 0; i < length; i += 1) {
          // 每个元素都需要单独处理
          partial[i] = str(i, value) || "null";
        }
        // 将 partial 转成 ”[...]“
        v = partial.length === 0
          ? "[]"
          : "[" + partial.join(",") + "]";
        return v;
      } else {
        // 处理对象
        for (k in value) {
          if (Object.prototype.hasOwnProperty.call(value, k)) {
            v = str(k, value);
            if (v) {
              partial.push(quote(k) + ":" + v);
            }
          }
        }
        // 将 partial 转成 "{...}"
        v = partial.length === 0
          ? "{}"
        	: "{" + partial.join(",") + "}";
        return v;
      }
  }
}

Как видно из приведенного выше кода, при сериализации JSON-объектов необходимо пройтись по всем массивам и объектам, поочередно определить тип и добавить все ключи"", и операция кодирования некоторых специальных символов сюда не включена. Однако, если естьschemaПосле этого эти ситуации станут намного проще.fastifyОфициальная сериализация JSON была разделена на склад:fast-json-stringify, а позже представилajvЧтобы проверить, здесь, чтобы упростить понимание кода, выберите более раннюю версию: 0.1.0, логика относительно проста и понятна.

fast-json-stringify@0.1.0:GitHub.com/fa body development/happening…

function $Null (i) {
  return 'null'
}

function $Number (i) {
  var num = Number(i)
  if (isNaN(num)) {
    return 'null'
  } else {
    return String(num)
  }
}

function $String (i) {
  return '"' + i + '"'
}

function buildObject (schema, code, name) {
  // 序列化对象 ...
}

function buildArray (schema, code, name) {
  // 序列化数组 ...
}

function build (schema) {
  var code = `
    'use strict'

    ${$String.toString()}
    ${$Number.toString()}
    ${$Null.toString()}
  `
  var main

  code = buildObject(schema, code, '$main')

  code += `
    ;
    return $main
  `

  return (new Function(code))()
}

module.exports = build

fast-json-stringifyразоблачить одинbuildметод, который принимаетschema, который возвращает функцию ($main), для преобразованияschemaСоответствующий объект сериализуется, и конкретное использование выглядит следующим образом:

const build = require('fast-json-stringify')

const stringify = build({
  type: 'object',
  properties: {
    id: { type: 'number' },
    name: { type: 'string' }
  }
})
console.log(stringify)

const objString = stringify({
  id: 1, name: 'shenfq'
})
console.log(objString) // {"id":1,"name":"shenfq"}

проходить черезbuildПосле построения возвращаемый метод сериализации выглядит следующим образом:

function $String (i) {
  return '"' + i + '"'
}
function $Number (i) {
  var num = Number(i)
  if (isNaN(num)) {
    return 'null'
  } else {
    return String(num)
  }
}
function $Null (i) {
  return 'null'
}
// 序列化方法
function $main (obj) {
  var json = '{'

  json += '"id":'

  json += $Number(obj.id)
  json += ','
  json += '"name":'

  json += $String(obj.name)

  json += '}'
  return json
}

Как видите, естьschemaУ поддержки логическая последовательность моментов становится чрезвычайно простой, в результате строка JSON сохраняет только нужные атрибуты, простые и эффективные. Возвращаемся и смотримbuildObjectкак он генерируется$mainВнутри кода:

function buildObject (schema, code, name) {
  // 构造一个函数
  code += `
    function ${name} (obj) {
      var json = '{'
  `
  var laterCode = ''
  // 遍历 schema 的属性
  const { properties } = schema
  Object.keys(properties).forEach((key, i, a) => {
    // key 需要加上双引号
    code += `
      json += '${$String(key)}:'
    `
    // 通过 nested 转化 value
    const value = properties[key]
    const result = nested(laterCode, name, `.${key}`, value)

    code += result.code
    laterCode = result.laterCode

    if (i < a.length - 1) {
      code += 'json += \',\''
    }
  })

  code += `
      json += '}'
      return json
    }
  `

  code += laterCode

  return code
}

function nested (laterCode, name, key, schema) {
  var code = ''
  var funcName
  // 判断 value 的类型,不同类型进行不同的处理
  const type = schema.type
  switch (type) {
    case 'null':
      code += `
      json += $Null()
      `
      break
    case 'string':
      code += `
      json += $String(obj${key})
      `
      break
    case 'number':
    case 'integer':
      code += `
      json += $Number(obj${key})
      `
      break
    case 'object':
      // 如果 value 为一个对象,需要一个新的方法进行构造
      funcName = (name + key).replace(/[-.\[\]]/g, '')
      laterCode = buildObject(schema, laterCode, funcName)
      code += `
        json += ${funcName}(obj${key})
      `
      break
    case 'array':
      funcName = (name + key).replace(/[-.\[\]]/g, '')
      laterCode = buildArray(schema, laterCode, funcName)
      code += `
        json += ${funcName}(obj${key})
      `
      break
    default:
      throw new Error(`${type} unsupported`)
  }

  return {
    code,
    laterCode
  }
}

На самом деле это правильноtypeдля"object"изpropertiesсделать обход, то дляvalueРазличные типы вторичной обработки, если встречается новый объект, построит новую функцию обработки.

// 如果包含子对象
const stringify = build({
  type: 'object',
  properties: {
    id: { type: 'number' },
    info: {
      type: 'object',
      properties: {
        age: { type: 'number' },
        name: { type: 'string' },
      }
    }
  }
})

console.log(stringify.toString())
function $main (obj) {
  var json = '{'

  json += '"id":'

  json += $Number(obj.id)
  json += ','
  json += '"info":'

  json += $maininfo(obj.info)

  json += '}'
  return json
}

// 子对象会通过另一个函数处理
function $maininfo (obj) {
  var json = '{'

  json += '"age":'

  json += $Number(obj.age)
  json += ','
  json += '"name":'

  json += $String(obj.name)

  json += '}'
  return json
}

Суммировать

Конечно,fastifyПричина, по которой он претендует на быстроту, заключается в том, что внутри есть какие-то другие методы оптимизации, например, он используется в реализации библиотеки маршрутизации.Radix Tree, объект контекста можно использовать повторно (используяmiddieбиблиотека). В этой статье представлена ​​только одна из самых важных и очевидных идей по оптимизации, и я надеюсь, что после ее прочтения вы сможете что-то для себя извлечь.