前言
用过 Node.js 开发过的同学必定都上手过 koa,因为他简略优雅的写法,再加上丰盛的社区生态,而且现存的许多 Node.js 框架都是基于 koa 进行二次封装的。然而说到性能,就不得不提到一个出名框架: fastify
,听名字就晓得它的个性就是快,官网给出的Benchmarks甚至比 Node.js 原生的 http.Server
还要快。
性能晋升的要害
咱们先看看 fastify
是如何启动一个服务的。
# 装置 fastifynpm 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
:
body
:当为 POST 或 PUT 办法时,校验申请主体;query
:校验 url 的 查问参数;params
:校验 url 参数;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
须要通过如许繁琐的步骤,这里咱们参考 Douglas Crockford (JSON 格局的创建者)开源的 JSON-js
中实现的 stringify
办法。
JSON-js:https://github.com/douglascrockford/JSON-js/blob/master/json2.js
// 只展现 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 对象序列化时,须要遍历所有的数组与对象,逐个进行类型的判断,并对所有的 key 加上 ""
,而且这里还不包含一些特殊字符的 encode 操作。然而,如果有了 schema
之后,这些状况会变得简略很多。fastify
官网将 JSON 的序列化独自成了一个仓库:fast-json-stringify
,前期还引入了 ajv
来进行校验,这里为了更容易看懂代码,抉择看比拟晚期的版本:0.1.0,逻辑比较简单,便于了解。
fast-json-stringify@0.1.0: https://github.com/fastify/fast-json-stringify/blob/v0.1.0/index.js
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
库)。本文只是介绍了其中的一种体现最重要显著优化思路,心愿大家浏览之后能有所播种。