当前位置: 首页 > 后端技术 > Node.js

如何进行GC调优提升Node应用性能

时间:2023-04-03 19:01:43 Node.js

之前用户项目上线压测时,CPU100%时单进程QPS波动在100左右,想进一步优化.接入Node.js性能平台后,在压测中做一个CPUprofile,观察系统CPU消耗在哪里:可以看到_tickDomainCallback和垃圾收集器占用了高达83%的CPU,和用户沟通后,发现_tickDomainCallback里面的CPU密集型逻辑是typeorm和自己的controller逻辑。Typeorm因为api的变化不容易升级,controller逻辑已经优化,没有提升的空间。因此,我们自然会在GC阶段专注于进一步提高项目性能。这里,在3分钟的CPU采样时间内,GC阶段的调用占比高达27.5%。结合当时性能平台的监控数据可以看出,大部分都处于扫码阶段。这个时候继续在线压测,同时做GCTrace,可以得到更详细的GC阶段信息:在GCTrace结果分析图中,可以看到红圈圈出的几个重要信息:总停顿时间GC高达47.8s,大头在scavenge3min的GCtracelog中。988次scavengecollections每次scavenge平均耗时50-60ms。在上面对GCTrace结果的分析中,我们可以看到GC优化点集中在scavenge回收阶段,也就是新生代的内存回收。那么通过阅读V8的scavenge回收逻辑,我们可以知道这个阶段触发回收的条件是:semispaceallocationfailed。这样可以推断用户的应用在压力测试的时候应该会频繁的在新生代中产生大量的小对象,导致默认的semispace一直处于被快速填满的状态并触发翻转,这会在GCtrace期间出现Somanyscavengecollections和相应的CPU消耗。面对这样的情况,是否可以通过调整默认半空间的值来优化呢?优化V8的代码后,我们发现默认的semispace值为16M(alinode-v3.11.3/node-v8.11.3)。与用户沟通后,我们计划将其调整为64M、128M、256M进行观察。可以在node启动应用的时候加上--max_semi_space_size的flag生效。一、设置semispace为64M。调整semispace为64M后,进行在线压测,压测时获取CPUProfile和GCTrace,如下图所示:可以看到垃圾回收阶段的CPU消耗率下降到7%左右,再观察GC跟踪结果:很明显,semispace增加到64M后,scavenge次数从近1000次减少到294次,但是这种情况下,每次scavengerecovery花费的时间确实没有明显增加,仍然在50~60ms之间波动,所以3minGCtrace的总停顿时间从48s下降到12s,相应的业务QPS提升了10%左右。二。当semispace设置为128M,semispace的值进一步增加到128M时,观察CPUProfile结果:此时垃圾收集器的消耗相比上面设置的64M不是很明显。另外观察GCtrace结果:很明显,之所以GCratio相比设置为64M时并没有增加多少,是因为此时semispace虽然进一步增加到128M,但是scavengecollections的数量有所下降从294到145,每个算法集合花费的时间几乎翻了一番,所以总收益并不明显。三、设置semispace为256M,进一步调整semispace为256M。经过测试,结果其实和128M非常相似:相比64M的情况,3分钟内scavenge的数量从294下降到72,但是相对算法的恢复时间波动在150ms左右,所以整体性能没有明显改善,如下图所示:IV.总结通过以上测试提升semispace值后,可以看到当默认的16M设置为64M时,节点应用整体的GC性能有了明显提升,体现在压测QPS提升了大约10%;但是,当semispace进一步增加到128M和256M时,好处并不明显,而且semispace本身也影响了新生代对象的快速内存分配不宜设置太大,所以本次优化最终选择最优该项目的运行时半空间值为64M。最后,通过GC的运行时调优来提升我们项目的性能,是大家不常用的一种方式。这也很大程度上是因为GC状态本身并没有在应用程序运行时直接暴露给开发人员。通过上面的真实客户案例,我们可以看到,借助Node.js性能平台,实时感知Node应用的GC状态并进行相应的优化,让项目性能提升变得非常简单,无需改动一行代码。本文作者:易君阅读原文,为云栖社区原创内容,未经允许不得转载。