信息化设计公司排名,seo技术是干什么的,网站流量的转换率一般是多少,怎么做刷东西的网站1、使用最新版本的 Node.js 仅仅是简单的升级 Node.js 版本就可以轻松地获得性能提升#xff0c;因为几乎任何新版本的 Node.js 都会比老版本性能更好#xff0c;为什么#xff1f; Node.js 每个版本的性能提升主要来自于两个方面#xff1a; V8 的版本更新#xff1b;Nod… 1、使用最新版本的 Node.js 仅仅是简单的升级 Node.js 版本就可以轻松地获得性能提升因为几乎任何新版本的 Node.js 都会比老版本性能更好为什么 Node.js 每个版本的性能提升主要来自于两个方面 V8 的版本更新Node.js 内部代码的更新优化。例如最新的 V8 7.1 中就优化了某些情形下闭包的逃逸分析让 Array 的一些方法得到了性能提升 Node.js 的内部代码随着版本的升级也会有明显的优化比如下面这个图就是 require 的性能随着 Node.js 版本升级的变化 每个提交到 Node.js 的 PR 都会在 review 的时候考虑会不会对当前性能造成衰退。同时也有专门的 benchmarking 团队来监控性能变化你可以在这里看到 Node.js 的每个版本的性能变化 https://benchmarking.nodejs.org/ 所以你可以完全对新版本 Node.js 的性能放心如果发现了任何在新版本下的性能衰退欢迎提交一个 issue。 如何选择 Node.js 的版本 这里就要科普一下 Node.js 的版本策略 Node.js 的版本主要分为 Current 和 LTSCurrent 就是当前最新的、依然处于开发中的 Node.js 版本LTS 就是稳定的、会长期维护的版本Node.js 每六个月每年的四月和十月会发布一次大版本升级大版本会带来一些不兼容的升级每年四月发布的版本版本号为偶数如 v10是 LTS 版本即长期支持的版本社区会从发布当年的十月开始继续维护 18 12 个月Active LTS Maintaince LTS每年十月发布的版本版本号为奇数例如现在的 v11只有 8 个月的维护期。举个例子现在2018年11月Node.js Current 的版本是 v11LTS 版本是 v10 和 v8。更老的 v6 处于 Maintenace LTS从明年四月起就不再维护了。去年十月发布的 v9 版本在今年六月结束了维护。 对于生产环境而言Node.js 官方推荐使用最新的 LTS 版本现在是 v10.13.0。 2、使用 fast-json-stringify 加速 JSON 序列化 在 JavaScript 中生成 JSON 字符串是非常方便的 preconst json JSON.stringify(obj)/pre 但很少人会想到这里竟然也存在性能优化的空间那就是使用 JSON Schema 来加速序列化。 在 JSON 序列化时我们需要识别大量的字段类型比如对于 string 类型我们就需要在两边加上 对于数组类型我们需要遍历数组把每个对象序列化后用 , 隔开然后在两边加上 [ 和 ] 诸如此类等等。 但 如果已经提前通过 Schema 知道每个字段的类型那么就不需要遍历、识别字段类型 而可以直接用序列化对应的字段这就大大减少了计算开销这就是 fast-json-stringfy 的原理。 根据项目中的跑分在某些情况下甚至可以比 JSON.stringify 快接近 10 倍 一个简单的示例 preconst fastJson require(fast-json-stringify)
const stringify fastJson({title: Example Schema,type: object,properties: {name: { type: string },age: { type: integer },books: {type: array,items: {type: string,uniqueItems: true}}}
})console.log(stringify({name: Starkwang,age: 23,books: [C Primier, 響けユーフォニアム]
}))
// {name:Starkwang,age:23,books:[C Primier,響けユーフォニアム]}/pre 在 Node.js 的中间件业务中通常会有很多数据使用 JSON 进行并且这些 JSON 的结构是非常相似的如果你使用了 TypeScript更是这样这种场景就非常适合使用 JSON Schema 来优化。 3、提升 Promise 的性能 Promise 是解决回调嵌套地狱的灵丹妙药特别是当自从 async/await 全面普及之后它们的组合无疑成为了 JavaScript 异步编程的终极解决方案现在大量的项目都已经开始使用这种模式。 但是优雅的语法后面也隐藏着性能损耗我们可以使用 github 上一个已有的跑分项目 进行测试以下是测试结果 prefile time(ms) memory(MB)
callbacks-baseline.js 380 70.83
promises-bluebird.js 554 97.23
promises-bluebird-generator.js 585 97.05
async-bluebird.js 593 105.43
promises-es2015-util.promisify.js 1203 219.04
promises-es2015-native.js 1257 227.03
async-es2017-native.js 1312 231.08
async-es2017-util.promisify.js 1550 228.74Platform info:
Darwin 18.0.0 x64
Node.JS 11.1.0
V8 7.0.276.32-node.7
Intel(R) Core(TM) i5-5257U CPU 2.70GHz × 4/pre 我们可以从结果中看到原生 async/await Promise 的性能比 callback 要差很多并且内存占用也高得多。对于大量异步逻辑的中间件项目而言这里的性能开销还是不能忽视的。 通过对比可以发现性能损耗主要来自于 Promise 对象自身的实现V8 原生实现的 Promise 比 bluebird 这样第三方实现的 Promise 库要慢很多。而 async/await 语法并不会带来太多的性能损失。 所以对于大量异步逻辑、轻量计算的中间件项目而言可以在代码中把全局的 Promise 换为 bluebird 的实现 preglobal.Promise require(bluebird);/pre 4、正确地编写异步代码 使用 async/await 之后项目的异步代码会非常好看 preconst foo await doSomethingAsync();
const bar await doSomethingElseAsync();/pre 但因此有时我们也会忘记使用 Promise 给我们带来的其它能力比如 Promise.all() 的并行能力 pre// 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 }
}/pre 还有比如 Promise.any() 此方法不在ES6 Promise标准中也可以使用标准的 Promise.race() 代替我们可以用它轻松实现更加可靠快速的调用 preasync function getServiceIP(name) {// 从 DNS 和 ZooKeeper 获取服务 IP哪个先成功返回用哪个// 与 Promise.race 不同的是这里只有当两个调用都 reject 时才会抛出错误return await Promise.any([getIPFromDNS(name),getIPFromZooKeeper(name)])
}/pre 5、优化 V8 GC 关于 V8 的垃圾回收机制已经有很多类似的文章了这里就不再重复介绍。推荐两篇文章 解读 V8 GC Log一: Node.js 应用背景与 GC 基础知识解读 V8 GC Log二: 堆内外内存的划分与 GC 算法我们在日常开发代码的时候比较容易踩到下面几个坑 坑一使用大对象作为缓存导致老生代Old Space的垃圾回收变慢 示例 preconst cache {}
async function getUserInfo(id) {if (!cache[id]) {cache[id] await getUserInfoFromDatabase(id)}return cache[id]
}/pre 这里我们使用了一个变量 cache 作为缓存加速用户信息的查询进行了很多次查询后 cache 对象会进入老生代并且会变得无比庞大而老生代是使用三色标记 DFS 的方式进行 GC 的一个大对象会直接导致 GC 花费的时间增长而且也有内存泄漏的风险。 解决方法就是 使用 Redis 这样的外部缓存实际上像 Redis 这样的内存型数据库非常适合这种场景限制本地缓存对象的大小比如使用 FIFO、TTL 之类的机制来清理对象中的缓存。坑二新生代空间不足导致频繁 GC 这个坑会比较隐蔽。 Node.js 默认给新生代分配的内存是 64MB64位的机器后同但因为新生代 GC 使用的是 Scavenge 算法所以实际能使用的内存只有一半即 32MB。 当业务代码频繁地产生大量的小对象时这个空间很容易就会被占满从而触发 GC。虽然新生代的 GC 比老生代要快得多但频繁的 GC 依然会很大地影响性能。极端的情况下GC 甚至可以占用全部计算时间的 30% 左右。 解决方法就是在启动 Node.js 时修改新生代的内存上限减少 GC 的次数 prenode --max-semi-space-size128 app.js/pre 当然有人肯定会问新生代的内存是不是越大越好呢 随着内存的增大GC 的次数减少但每次 GC 所需要的时间也会增加所以并不是越大越好具体数值需要对业务进行压测 profile 才能确定分配多少新生代内存最好。 但一般根据经验而言 分配 64MB 或者 128MB 是比较合理的 。 6、正确地使用 Stream Stream 是 Node.js 最基本的概念之一Node.js 内部的大部分与 IO 相关的模块比如 http、net、fs、repl都是建立在各种 Stream 之上的。 下面这个经典的例子应该大部分人都知道对于大文件我们不需要把它完全读入内存而是使用 Stream 流式地把它发送出去 preconst 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);
});/pre 在业务代码中合理地使用 Stream 能很大程度地提升性能当然是但实际的业务中我们很可能会忽略这一点比如采用 React 服务器端渲染的项目我们就可以用 renderToNodeStream preconst ReactDOMServer require(react-dom/server)
const http require(http)
const fs require(fs)
const app require(./app)// bad
const server http.createServer((req, res) {const body ReactDOMServer.renderToString(app)res.end(body)
});// good
const server http.createServer(function (req, res) {const stream ReactDOMServer.renderToNodeStream(app)stream.pipe(res)
})server.listen(8000)/pre 使用 pipeline 管理 stream 在过去的 Node.js 中处理 stream 是非常麻烦的举个例子 presource.pipe(a).pipe(b).pipe(c).pipe(dest)/pre 一旦其中 source、a、b、c、dest 中有任何一个 stream 出错或者关闭会导致整个管道停止此时我们需要手工销毁所有的 stream在代码层面这是非常麻烦的。 所以社区出现了 pump 这样的库来自动控制 stream 的销毁。而 Node.js v10.0 加入了一个新的特性 stream.pipeline 可以替代 pump 帮助我们更好的管理 stream。 一个官方的例子 preconst { 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);}}
);/pre 实现自己的高性能 Stream 在业务中你可能也会自己实现一个 Stream可读、可写、或者双向流可以参考文档 * implementing Readable streams
* implementing Writable streams Stream 虽然很神奇但自己实现 Stream 也可能会存在隐藏的性能问题比如 preclass MyReadable extends Readable {_read(size) {while (null ! (chunk getNextChunk())) {this.push(chunk);}}
}/pre 当我们调用 new MyReadable().pipe(xxx) 时会把 getNextChunk() 所得到的 chunk 都 push 出去直到读取结束。但如果此时管道的下一步处理速度较慢就会导致数据堆积在内存中导致内存占用变大GC 速度降低。 而正确的做法应该是根据 this.push() 返回值选择正确的行为当返回值为 false 时说明此时堆积的 chunk 已经满了应该停止读入。 preclass MyReadable extends Readable {_read(size) {while (null ! (chunk getNextChunk())) {if (!this.push(chunk)) {return false }}}
}/pre 这个问题在 Node.js 官方的一篇文章中有详细的介绍 Backpressuring in Streams 7、C 扩展一定比 JavaScript 快吗 Node.js 非常适合 IO 密集型的应用而对于计算密集的业务很多人都会想到用编写 C Addon 的方式来优化性能。但实际上 C 扩展并不是灵丹妙药V8 的性能也没有想象的那么差。 比如我在今年九月份的时候把 Node.js 的 net.isIPv6() 从 C 迁移到了 JS 的实现让大多数的测试用例都获得了 10%- 250% 不等的性能提升 具体PR可以看这里 。 JavaScript 在 V8 上跑得比 C 扩展还快这种情况多半发生在与字符串、正则表达式相关的场景因为 V8 内部使用的正则表达式引擎是 irregexp 这个正则表达式引擎比 boost 中自带的引擎 boost::regex 要快得多。 还有一处值得注意的就是Node.js 的 C 扩展在进行类型转换的时候可能会消耗非常多的性能如果不注意 C 代码的细节性能会很大地下降。 这里有一篇文章对比了相同算法下 C 和 JS 的性能需翻墙 How to get a performance boost using Node.js native addons 。其中值得注意的结论就是C 代码在对参数中的字符串进行转换后 String::Utf8Value 转为 std::string 性能甚至不如 JS 实现的一半。只有在使用 NAN 提供的类型封装后才获得了比 JS 更高的性能。 换句话说C 是否比 JavaScript 更加高效需要具体问题具体分析某些情况下C 扩展不一定就会比原生 JavaScript 更高效。如果你对自己的 C 水平不是那么有信心其实还是建议用 JavaScript 来实现因为 V8 的性能比你想象的要好得多。 8、使用 node-clinic 快速定位性能问题 说了这么多有没有什么可以开箱即用五分钟见效的呢当然有。 node-clinic 是 NearForm 开源的一款 Node.js 性能诊断工具可以非常快速地定位性能问题。 prenpm i -g clinic
npm i -g autocannon/pre 使用的时候先开启服务进程 preclinic doctor -- node server.js/pre 然后我们可以用任何压测工具跑一次压测比如使用同一个作者的 autocannon 当然你也可以使用 ab、curl 这样的工具来进行压测。 preautocannon http://localhost:3000/pre 压测完毕后我们 ctrl c 关闭 clinic 开启的进程就会自动生成报告。比如下面就是我们一个中间件服务的性能报告 我们可以从 CPU 的使用曲线看出这个中间件服务的性能瓶颈不在自身内部的计算而在于 I/O 速度太慢。clinic 也在上面告诉我们检测到了潜在的 I/O 问题。 下面我们使用 clinic bubbleprof 来检测 I/O 问题 preclinic bubbleprof -- node server.js/pre 再次进行压测后我们得到了新的报告 这个报告中我们可以看到 http.Server 在整个程序运行期间96% 的时间都处于 pending 状态点开后我们会发现调用栈中存在大量的 empty frame也就是说由于网络 I/O 的限制CPU 存在大量的空转这在中间件业务中非常常见也为我们指明了优化方向不在服务内部而在服务器的网关和依赖的服务相应速度上。 想知道如何读懂 clinic bubbleprof 生成的报告可以看这里 https://clinicjs.org/bubblepr… 同样clinic 也可以检测到服务内部的计算性能问题下面我们做一些“破坏”让这个服务的性能瓶颈出现在 CPU 计算上。 我们在某个中间件中加入了空转一亿次这样非常消耗 CPU 的“破坏性”代码 prefunction sleep() {let n 0while (n 10e7) {empty()}
}
function empty() { }module.exports (ctx, next) {sleep()// ......return next()
}/pre 然后使用 clinic doctor 重复上面的步骤生成性能报告 这就是一个非常典型的 同步计算阻塞了异步队列 的“病例”即主线程上进行了大量的计算导致 JavaScript 的异步回调没法及时触发Event Loop 的延迟极高。 对于这样的应用我们可以继续使用 clinic flame 来确定到底是哪里出现了密集计算 preclinic flame -- node app.js/pre 压测后我们得到了火焰图这里把空转次数减少到了100万次让火焰图看起来不至于那么极端 从这张图里我们可以明显看到顶部的那个大白条它代表了 sleep 函数空转所消耗的 CPU 时间。根据这样的火焰图我们可以非常轻松地看出 CPU 资源的消耗情况从而定位代码中哪里有密集的计算找到性能瓶颈。 为了学习工作与休闲娱乐互不冲突现新建圈【码农茶水铺】用于程序员生活爱好交友求职招聘吐槽等话题交流希望各位大神工作之余到茶水铺来喝茶聊天。群号:582735936