Node.js性能优化

2019-01-03  本文已影响0人  imakan

本文内容源自知乎,地址:https://zhuanlan.zhihu.com/p/50055740

仅仅是简单的升级Node.js版本就可以轻松的获得性能提升,因为几乎任何新版本的Nodejs都会比老版本性能更好、
nodejs每个版本的性能提升主要来自于两个方面:

如何选择Nodejs的版本

nodejs的版本策略:

举个例子,现在nodejs的版本是v11,LTS版本是V10和v8,更老的V6处于Maintenace LTS,从明年4月份起(18+12个月了)就不再维护,去年发布的v9版本今年6月就不在维护(奇数版本8个月)

使用fast-json-stringify加速JSON序列化

JavaScript中,生成json字符串是非常方便的

const json = JSON.stringify(obj)

JSON.stringify也存在性能优化的空间,那就是使用JSON Schema来加速序列化。
JSON序列化时,我们需要识别大量的字段类型,比如对于string类型,我们就需要两边加上"",对于数组类型,我们需要遍历数组,把每个对象序列化后,用,隔开,然后在两边加上[] ,诸如此类。如果我们已经提前通过Schema知道每个字段的类型,那么就不需要遍历,识别字段类型,而可以直接用序列化对应的字段,这就大大减少了计算开销,这就是fast-json-stringfy的原理,在项目的跑分中,在某些情况下甚至可以比JSON.stringify快接近10倍

jsonStringify

一个简单的示例:

const fastJson = require('fast-json-stringify')
const stringify = fastJson({
  title:'Example Schema',
  type:'object', 
  properties:{
    name:{type:'string'},
    age:{type:'integer'},
    books:{
      type:'array',
      item:{
        type:'array',
        uniqueItems:true
      }
    }
  }
})

console.log(stringify({
    name: 'Starkwang',
    age: 23,
    books: ['C++ Primer', '響け!ユーフォニアム~']
}))
//=> {"name":"Starkwang","age":23,"books":["C++ Primer","響け!ユーフォニアム~"]}

node.js的中间件业务中,通常会有很多数据使用json进行传输,并且这些json的结构非常相似(如果你使用了TypeScript,更是这样),这中场景就非常适用JSON Schema来优化。

提升Promise的性能

性能损耗主要来自于Promise对象自身的实现,V8原生实现的Promisebluebird这样的第三方实现的Promise库要慢很多。而async/await语法并不会带来太多的性能损失。
所以对于大量异步逻辑,轻量计算的中间件项目而言,可以在代码中把全局的Promise换为bluebird的实现

global.Promise = require('bluebird')

正确地编写异步代码

使用async/await之后,项目的一步代码会非常好看

const foo = await doSomethingAsync();
const bar = await doSomethingElseAsync();

但因此,有时我们也会忘记使用Promise给我们带来的其他能力,比如Promise.all()的并行能力

// bad
async function getUserInfo(id) {
    const profile = await getUserProfile(id);
    const repo = await getUserRepo(id)
    return { profile, repo }
}

// good
async function getUserInfo(id) {
    const [profile, repo] = await Promise.all([
        getUserProfile(id),
        getUserRepo(id)
    ])
    return { profile, repo }
}

还有比如Promise.any()(此方法不在ES6 Promise标准中,也可以使用标准的Promise.race()代替),我们可以用他轻松实现更加可靠快速的调用

async function getServiceIP(name) {
    // 从 DNS 和 ZooKeeper 获取服务 IP,哪个先成功返回用哪个
    // 与 Promise.race 不同的是,这里只有当两个调用都 reject 时,才会抛出错误
    return await Promise.any([
        getIPFromDNS(name),
        getIPFromZooKeeper(name)
    ])
}

优化V8 GC

我们在日常开发代码的时候,比较容易踩到下面的几个坑:

使用大对象作为缓存,导致老生代(Old space)的垃圾回收变慢

示例:

const cache = {}
async function getUserInfo(id) {
  if(!cache[id]){
    cache[id] = await getUserInfoFromDatabase(id)
  }
  return cache[id]
}

这里我们使用了一个变量cache作为缓存,加速用户信息的查询,进行了很多次查询后,cache对象会进入老生代,并且会变得无比庞大,而老生代是使用三色标记+DFS的方式进行GC的,一个大对象会直接导致GC花费的时间增长(而且也有内存泄露的风险)

解决方法就是:

新生代空间不足,导致频繁GC

这个坑比较隐蔽
node默认给新生代分配的内存是64MB(64位的机器),但因为新生代GC使用的是Scavenge算法,所以实际使用的内存只有一半,即32MB。
当业务代码频繁地产生大量的小对象时,这个空间很容易被占满,从而触发GC。虽然新生代的GC比老生代要快得多,但是频繁的GC依然很大地影响性能,极端的情况下, GC甚至可以占用全部计算时间的30%左右。

解决方法就是,在启动node.js时,修改新生代的内存上限,减少GC的次数:

node --max-semi-space-size=128 app.js

也不是内存越大越好,因为随着内存的增加,GC的次数减少,但是每次GC所需的时间也会增加,所以并不是越大越好。但是一般根据经验而言,分配64MB或者128MB是比较合理的

正确的使用Stream

StreamNode.js最基本的概念,Node.js内部的大部分与IO相关的模块,比如HTTPnetfsrepl,都是建立在各种Stream之上的。

下面这个经典的例子,对于大文件,我们不需要把他完全读入内存,而是使用Stream流式地把他发送出去

const http = require('http');
const fs = require('fs');

// bad
http.createServer(function (req, res) {
    fs.readFile(__dirname + '/data.txt', function (err, data) {
        res.end(data);
    });
});

// good
http.createServer(function (req, res) {
    const stream = fs.createReadStream(__dirname + '/data.txt');
    stream.pipe(res);
});

使用pipeline管理stream

示例:

 const {pipeline} = require('stream');
const fs = require('fs');
const zlib = require('zlib');
pipeline(
    fs.createReadStream('archive.tar'),
    zlib.createGzip(),
    fs.createWriteStream('archive.tar.gz'),
    (err) => {
        if (err) {
            console.error('Pipeline failed', err);
        } else {
            console.log('Pipeline succeeded');
        }
    }
)

使用node-clinic快速定位性能问题

node-clinicNearForm 开源的一款 Node.js 性能诊断工具,可以非常快速地定位性能问题。

npm i -g clinic
npm i -g autocannon

使用的时候,先开启服务进程:

clinic doctor -- node server.js

·

上一篇 下一篇

猜你喜欢

热点阅读