性能文章>如何利用Fastify 实现更快的 JSON 序列化>

如何利用Fastify 实现更快的 JSON 序列化转载

157623

导语

对于 web 框架而言,更快的 HTTP 请求响应速度意味着更优异的性能。而 HTTP 协议是一个文本协议,传输的格式都是字符串,而我们在代码中常常操作的是 JSON 格式的数据。因此,需要在返回响应数据前将 JSON 数据序列化为字符串。JavaScript 原生提供了 JSON.stringify 这个方法,来将 JSON 转成字符串。先来介绍这个方法。

 

正文

缓慢的 JSON.stringify

由于 JavaScript 是一个动态语言,它的类型是在运行时才能确定,因此 JSON.stringify 的执行过程中要进行大量的类型判断,对不同类型的键值做不同的处理。由于不能做静态分析,我们很难做进一步优化。而且还需要一层一层的递归,循环引用的话还有爆栈的风险。在以性能著称的 Node.js 框架 Fastify 中,通过使用 fast-json-stringify 这个库,来替代 JSON.stringify,实现 JSON 序列化性能翻倍。那么,fast-json-stringify 是怎么做到的呢?

766FB416-3CF3-41AC-A458-DCA5EB011CC5.png


fast-json-stringify 揭秘

fast-json-stringify 基于 JSON Schema Draft 7 来定义(JSON)对象的数据格式。比如对象:

{
    foo: 1, 
    bar: "abc"
}


它的 JSON Schema 可以是这样:

{
  type: "object",
  properties: {
    foo: {type: "integer"},
    bar: {type: "string"}
  },
  required: ["foo"]
}


除了这种简单的类型定义,JSON Schema 还支持一些条件运算,比如字段类型可能是字符串或者数字,可以用 oneOf 关键字:

 

"oneOf": [
  {
    "type": "string"
  },
  {
    "type": "number"
  }
]


关于 JSON Schema 的完整定义,可以参考 Ajv 的文档,Ajv 是一个流行的 JSON Schema验证工具,性能表现也非常出众。来看一段使用 fast-json-stringify 的简单代码:

require('http').createServer(handle).listen(3000)
var flatstr = require('flatstr')

var stringify = require('fast-json-stringify')({
  type: 'object',
  properties: { hello: { type: 'string' } }
})

function handle (req, res) {
  res.setHeader('Content-Type', 'application/json')
  res.end(flatstr(stringify({ hello: 'world' })))
}


这段代码里,fast-json-stringify 接受一个 JSON Schema 对象作为参数,生成了一个 stringify 函数。通常,Response 的数据结构是固定的,所以可以将其定义为一个 Schema,就相当于提前告诉 stringify 函数,需序列化的对象的数据结构,这样它就可以不必再在运行时去做类型判断。下面来看看 stringify 函数是如何生成的。

生成 stringify 函数

首先,需要对 JSON Schema 进行校验。底层校验逻辑是基于 Ajv 实现的,这里暂不赘述。然后需要预先注入一些工具方法,用于将一些常见类型转成字符串。

const asFunctions = `
  function $asAny (i) {
    return JSON.stringify(i)
  }

  function $asNull () {
    return 'null'
  }

  function $asInteger (i) {
    if (isLong && isLong(i)) {
      return i.toString()
    } else if (typeof i === 'bigint') {
      return i.toString()
    } else if (Number.isInteger(i)) {
      return $asNumber(i)
    } else {
      return $asNumber(parseInteger(i))
    }
  }

  function $asNumber (i) {
    const num = Number(i)
    if (isNaN(num)) {
      return 'null'
    } else {
      return '' + num
    }
  }

  function $asBoolean (bool) {
    return bool && 'true' || 'false'
  }

  // 省略了一些其他类型......
`


可以看到,如果你使用的是 any 类型,它内部依然还是用的 JSON.stringify。我们经常建议 ts 开发者避免使用 any 类型是有道理的,因为如果是基于 ts interface 生成 JSON Schema 的话,使用 any 也会影响到 JSON 序列化的性能。

接下来,遍历 schema,对不同类型调用对应的工具函数来生成代码。

let code = `
    'use strict'
    ${asFunctions}
`
let main
switch (schema.type) {
    // 省略了对象和数组
    case 'integer':
        main = '$asInteger'
        break
    case 'number':
        main = '$asNumber'
        break
    case 'boolean':
        main = '$asBoolean'
        break
    case 'null':
        main = '$asNull'
        break
    case undefined:
        main = '$asAny'
        break
    default:
        throw new Error(`${schema.type} unsupported`)
}

code += `
;
    return ${main}
`


最后,对生成出来的 code 调用 Function 构造函数。

const dependencies = [new Ajv(options.ajv)]
const dependenciesName = ['ajv']
dependenciesName.push(code)
return (Function.apply(null, dependenciesName).apply(null, dependencies))


这里将 ajv 对象作为参数注入到函数里,是为了处理 JSON Schema 中 if、then、else、anyOf 等情况。

由于是调用的 new Function 来动态执行代码,这里其实是有一定的安全风险的。所以建议开发者一定不要使用用户生成的 schema,保证生成的 schema 是安全可控的。

最终,开发者调用 stringify 函数,将 JSON 转成字符串。执行 stringify 的过程本质上就是在做字符串拼接。

总结

Fastify 使用 fast-json-stringify 替代 JSON.stringify,实现了更快的 JSON 序列化。它的原理是通过开发者预先定义 JSON Schema,使得框架可以提前知道 JSON 数据的结构。然后根据 JSON Schema 生成一个 stringify 函数,stringify 内部做的事情其实就是字符串拼接。最后开发者调用 stringify 函数来序列化 JSON。本质上是将类型分析从运行时提前到编译时了。

分类:标签:
请先登录,查看2条精彩评论吧
快去登录吧,你将获得
  • 浏览更多精彩评论
  • 和开发者讨论交流,共同进步

为你推荐

一次 Node.js http 连接无法复用的问题排查
一次压测中阿里云 SLB 的并发连接数被打满了,导致服务之间的 HTTP 调用延迟很大。当时 SLB 的并发连接数情况如下图所示。登录容器终端查看,发现某个前端 Node.js 服务中的单个容器的 E
使用 Lighthouse 优化 Web Vitals
今天,我们将介绍 Lighthouse、PageSpeed 和 DevTools 中的新工具功能,以帮助确定您的网站如何在Web Vitals上进行改进。 作为工具的复习,Lighthouse是一个开源的自动化工具,用于提高网页的质量。您可以在Chrome DevTools调试工具套件
使用 CSS font-display 优化字体加载和替换
导语 在编写网站的时候,或多或少都会用到一些网络上的字体,CSS 3 中虽然加入了对 Web Fonts(网络字体)的支持,但是浏览器对它们的加载和默认处理方式会极大的影响网站的性能和用户体验。例如默认情况下,在 Web Fonts 加载时,使用该字体的地方会显示空白,直到字体下载完成
从实际项目入手,完成web性能监控及优化
导语 这篇文章总结一下我过往项目的web性能优化,主要是就项目中如何发现性能问题,优化如何解决,谈一下性能优化。把学习的过程跟大家分享一下,共同学习。web的性能监测工具我用的是Chrome的Performance 面板和Lighthouse。 正文 回想起
4000字全解Web 前端性能
导语也许你有听过一个问题,你这款 web 应用性能怎么样呀?你会回答什么呢?是否会优于海量 web 应用市场呢?本文就来整理下如何进行 web 性能监控?包括我们需要监控的指标、监控的分类、performance 分析以及如何监控。但是,如何进行 web 性能监控本身是一个很大的话题,文中只会侧
关于Web页面全链路性能优化指南
导语性能优化不单指优化一个页面的打开速度,在开发环境将一个项目的启动时间缩短使开发体验更好也属于性能优化,大文件上传时为其添加分片上传、断点续传也属于性能优化。在项目开发以及用户使用的过程中,能够让任何一个链路快一点,都可以被叫做性能优化。本文会对web页面的全链路进行完整的讲解并针对每一步找到
关于web端字体设置及兼容性适配的问题
前言在几年的开发生涯中碰见过几次由字体引起的各终端的页面显示差异。或许是由移动端等终端字体库引起的差异引起的,或许是由 Modern Browser 或 IE Browser 直接的解析差异引起的,或许是由字体源设计表现引起的,或许是由所设置的通用字体族字符的编码格式引起的......来一起来探个
如何利用Fastify 实现更快的 JSON 序列化
导语对于 web 框架而言,更快的 HTTP 请求响应速度意味着更优异的性能。而 HTTP 协议是一个文本协议,传输的格式都是字符串,而我们在代码中常常操作的是 JSON 格式的数据。因此,需要在返回响应数据前将 JSON 数据序列化为字符串。JavaScript 原生提供了 JSON.strin