网站小图标怎么做的,合肥网站建设方案策划,龙岗建设网站制作,青之峰网站建设哪家好楔子
本章前面两节生产案例分别侧重于单一的 CPU 高和单一的内存问题#xff0c;我们也给大家详细展示了问题的定位排查过程#xff0c;那么实际上还有一类相对更复杂的场景——它本质上是 V8 引擎的 GC 引发的问题。
简单的给大家介绍下什么是 GC#xff0c;GC 实际上是语…楔子
本章前面两节生产案例分别侧重于单一的 CPU 高和单一的内存问题我们也给大家详细展示了问题的定位排查过程那么实际上还有一类相对更复杂的场景——它本质上是 V8 引擎的 GC 引发的问题。
简单的给大家介绍下什么是 GCGC 实际上是语言引擎实现的一种自动垃圾回收机制它会在设定的条件触发时比如堆内存达到一定值时查看当前堆上哪些对象已经不再使用并且将这些没有再使用到的对象所占据的空间释放出来。许多的现代高级语言都实现了这一机制来减轻程序员的心智负担。
本书首发在 Github仓库地址https://github.com/aliyun-node/Node.js-Troubleshooting-Guide云栖社区会同步更新。 GC 带来的问题
虽然上面介绍中现代语言的 GC 机制解放了程序员间接提升了开发效率但是万事万物都存在利弊底层的引擎引入 GC 后程序员无需再关注对象何时释放的问题那么相对来说程序员也就没办法实现对自己编写的程序的精准控制它带来两大问题
代码编写问题引发的内存泄漏程序执行的性能降低
内存泄漏问题我们已经在上一节的生产案例中体验了一下那么后者是怎么回事呢
其实理解起来也很简单原本一个程序全部的工作都是执行业务逻辑但是存在了 GC 机制后程序总会在一定的条件下耗费时间在扫描堆空间找出不再使用的对象上这样就变相降低了程序执行业务逻辑的时间从而造成了性能的下降而且降低的性能和耗费在 GC 上的时间换言之即 GC 的次数 * 每次 GC 耗费的时间成正比。 问题现象与原始分析
现在大家应该对 GC 有了一个比较整体的了解这里我们可以看下 GC 引发的问题在生产中的表现是什么样的。在这个案例中表象首先是 Node.js 性能平台 上监控到进程的 CPU 达到 100%但是此时服务器负载其实并不大QPS 只有 100 上下我们按照前面提到的处理 CPU 问题的步骤抓取 CPU Profile 进行分析可以看到 这次的问题显然是 Garbage Collector 耗费的 CPU 太多了也就是 GC 的问题。实际上绝大部分的 GC 机制引发的问题往往表象都是反映在 Node.js 进程的 CPU 上而本质上这类问题可以认为是引擎的 GC 引起的问题也可以理解为内存问题我们看下这类问题的产生流程
堆内存不断达到触发 GC 动作的预设条件进程不断触发 GC 操作进程 CPU 飙高
而且 GC 问题不像之前的 ejs 模板渲染引发的问题就算我们在 CPU Profile 中可以看到这部分的耗费但是想要优化解决这个问题基本是无从下手的幸运的是 Node.js 提供了其实是 V8 引擎提供的一系列的启动 Flag 能够输出进程触发 GC 动作时的相关日志以供开发者进行分析
--trace_gc: 一行日志简要描述每次 GC 时的时间、类型、堆大小变化和产生原因--trace_gc_verbose: 结合 --trace_gc 一起开启的话会展示每次 GC 后每个 V8 堆空间的详细状况--trace_gc_nvp: 每一次 GC 的一些详细键值对信息包含 GC 类型暂停时间内存变化等信息
加粗的 Flag 意味着我们需要在启动应用前加上才能在运行时生效这部分的日志实际上是一个文本格式可惜的是 Chrome devtools 原生并不支持 GC 日志的解析和结果展示因此需要大家获取到以后进行对应的按行解析处理当然我们也可以使用社区提供 v8-gc-log-parser 这个模块直接进行解析处理对这一块有兴趣的同学可以看 joyeeCheung 在 JS Interactive 的分享: Are Your V8 Garbage Collection Logs Speaking To You?这里不详细展开。 更好的 GC 日志展示
虽然 Chrome devtools 并不能直接帮助我们解析展示 GC 日志的结果但是 Node.js 性能平台 其实给大家提供了更方便的动态获取线上运行进程的 GC 状态信息以及对应的结果展示换言之开发者无需在运行你的 Node.js 应用前开启上面提到的那些 Flag 而仍然可以在想要获取到 GC 信息时通过控制台拿到 3 分钟内的 GC 数据。
对应在这个案例中我们可以进入平台的应用实例详情页面找到 GC 耗费特别大的进程然后点击 GC Trace 抓取 GC 数据 这里默认会抓取 3 分钟的对应进程的 GC 日志信息等到结束后生成的文件会显示在 文件 页面 此时点击 转储 即可上传到云端以供在线分析展示了如下图所示 最后点击这里的 分析 按钮即可看到 AliNode 定制后的 GC 信息分析结果的展现 结果展示中可以比较方便的看到问题进程的 GC 具体次数GC 类型以及每次 GC 的耗费时间等信息方便我们进一步的分析定位。比如这次问题的 GC Trace 结果分析图中我们可以看到红圈起来的几个重要信息
GC 总暂停时间高达 47.8s大头是 Scavenge3min 的 GC 追踪日志里面总共进行了 988 次的 Scavenge 回收每次 Scavenge 耗时均值在 50 ~ 60ms 之间
从这些解困中我们可以看到此次 GC 案例的问题点集中在 Scavenge 回收阶段即新生代的内存回收。那么通过翻阅 V8 的 Scavenge 回收逻辑可以知道这个阶段触发回收的条件是Semi space allocation failed。
这样就可以推测我们的应用在压测期间应该是在新生代频繁生成了大量的小对象导致默认的 Semi space 总是处于很快被填满从而触发 Flip 的状态这才会出现在 GC 追踪期间这么多的 Scavenge 回收和对应的 CPU 耗费这样这个问题就变为如何去优化新生代的 GC 来提升应用性能。 优化新生代 GC
通过平台提供的 GC 数据抓取和结果分析我们知道可以去尝试优化新生代的 GC 来达到提升应用性能的目的而新生代的空间触发 GC 的条件又是其空间被占满那么新生代的空间大小由 Flag --max-semi-space-size 控制默认为 16MB因此我们自然可以想到要可以通过调整默认的 Semi space 的值来进行优化。 这里需要注意的是控制新生代空间的 Flag 在不同的 Node.js 版本下并不是一样的大家具体可以查看当前的版本来进行确认使用。 在这个案例中显然是默认的 16M 相对当前的应用来说显得比较小导致 Scavenge 过于频繁我们首先尝试通过启动时增加 --max-semi-space-size64 这个 Flag 来将默认的新生代使用到的空间大小从 16M 的值增大为 64M并且在流量比较大而且进程 CPU 很高时抓取 CPU Profile 观察效果 调整后可以看到 Garbage collector 阶段 CPU 耗费占比下降到 7% 左右再抓取 GC Trace 并观察其展示结果确认是不是 Scavenge 阶段的耗费下降了 显然Semi space 调大为 64M 后Scavenge 次数从近 1000 次降低到 294 次但是这种状况下每次的 Scavenge 回收耗时没有明显增加还是在 50 ~ 60ms 之间波动因此 3 分钟的 GC 追踪总的停顿时间从 48s 下降到 12s相对应的业务的 QPS 提升了约 10% 左右。
那么如果我们通过 --max-semi-space-size 这个 Flag 来继续调大新生代使用到的空间是不是可以进一步优化这个应用的性能呢此时尝试 --max-semi-space-size128 来从 64M 调大到 128M在进程 CPU 很高时继续抓取 CPU Profile 来查看效果 此时 Garbage collector 耗费下降相比上面的设置为 64M 并不是很明显GC 耗费下降占比不到 1%同样我们来抓取并观察下 GC Trace 的结果来查看具体的原因 很明显造成相比设置为 64M 时 GC 占比提升不大的原因是虽然此时进一步调大了 Semi space 至 128M并且 Scavenge 回收的次数确实从 294 次下降到 145 次但是每次算法回收耗时近乎翻倍了因此总收益并不明显。
按照这个思路我们再使用 --max-semi-space-size256 来将新生代使用的空间进一步增大到 256M 再来进行最后一次的观察 这里和调整为 128M 时是类似的情况 3 分钟内 Scavenge 次数从 294 次下降到 72 次但是相对的每次算法回收耗时波动到了 150ms 左右因此整体性能并没有显著提升。
借助于性能平台的 GC 数据抓取和结果展示通过以上的几次尝试改进 Semi space 的值后我们可以看到从默认的 16M 设置到 64M 时Node 应用的整体 GC 性能是有显著提升的并且反映到压测 QPS 上大约提升了 10%但是进一步将 Semi space 增大到 128M 和 256M 时收益确并不明显而且 Semi space 本身也是作用于新生代对象快速内存分配本身不宜设置的过大因此这次优化最终选取对此项目 最优的运行时 Semi space 的值为 64M。 结尾
在本生产案例中我们首先可以看到项目使用的三方库其实也并不总是在所有场景下都不会有 Bug 的实际上这是不可能的因此在遇到三方库的问题时我们要敢于去从源码的层面来对问题进行深入的分析。
最后实际上在生产环境下通过 GC 方面的运行时调优来提升我们的项目性能是一种大家不那么常用的方式这也有很大一部分原因是应用运行时 GC 状态本身不直接暴露给开发者。通过上面这个客户案例我们可以看到借助于 Node.js 性能平台实时感知 Node 应用 GC 状态以及进行对应的优化使得不改一行代码提升项目性能变成了一件非常容易的事情。
原文链接 本文为云栖社区原创内容未经允许不得转载。