JVM GC 性能对比方法

JVM GC 性能对比方法

JVM GC 性能测试系列:


现在部门内部绝大部分应用都还在使用 OpenJDK 8,计划推进部门升级 JDK 到 OpenJDK21。本着实事求是,用数据说话的原则,准备对如下 GC 做性能测试:

  1. OpenJDK 8 G1 GC(以下称 J8-G1。具体版本号:1.8.0_321-b07。)

  2. OpenJDK 17 ZGC(以下称 J17-ZGC。具体版本号:17.0.9+9。)

  3. OpenJDK 21 G1(以下称 J21-G1。具体版本号:21.0.2+13-LTS。)

  4. OpenJDK 21 ZGC(以下称 J21-ZGC。具体版本号:21.0.2+13-LTS。)

  5. OpenJDK 21 Gen ZGC(以下称 J21-Gen-ZGC。具体版本号:21.0.2+13-LTS。)

所有 OpenJDK 版本都是选用相同大版本号里的最高的版本。所有的机器都是 4C8G 的配置,JVM 堆栈内存设置为 4608M 。

为了减少不必要的干扰,JVM 相关参数也尽可能做到了一致或者接近。(等测试完,D瓜哥会把相关参数也分享出来。)

测试对象

由于D瓜哥所处的部门是一个直接面向用户的线上业务部门,所以,大部分系统是直接面对用户,接受用户访问的在线业务系统。所以,为了服务线上业务系统的需求,测试对象的选择就限定在了类似的场景中。测试对象是线上接受用户访问的一个服务。结构如下:

压测接口依赖关系图
图 1. 压测接口依赖关系图

该接口有外部依赖服务,也有数据库查询,是一个微服务架构下典型的在线服务接口。

测试方法

原本计划是想直接通过上线,将线上不同分组的机器使用不同的 GC 来做测试,但是,这样面临好几个问题:

  1. 由于正是环境上线需要审批,每次上线是针对一个构建包做上线,由于基于三个不同版本的 OpenJDK,所以,至少需要上线三次。

  2. 线上环境,不同分组的机器数量是不一样的,所以,不方便对比。比如,对比不同分组的响应请求数量。

  3. 性能测试势必会影响到线上的业务处理。如果引发客诉,鱼有没有迟到不确定,但是绝对惹一身腥。

但是后来在跟同事交流中,收获了一个更为稳妥的性能测试办法:直接在预发上搞,不需要上线审批,方便省事。

基于这个大的方向,最后确定的测试方法及具体操作如下:

  • 新建五个预发分组,直接在预发环境上做测试。

  • 每个分组申请五台和线上配置一样的机器。由于在使用 OpenJDK 21 的过程中发现,OpenJDK 21 的 ZGC 和 Gen ZGC 都挑食,有些机器表现很稳定,但是有些机器 CPU 波动就很大,为了避免出现类似问题,多申请几台机器,相互印证,让测试结果更加可靠。总体数量上来讲,还是要尽可能比线上服务机器数量少,尽量不要超过三分之一,最好是不超过五分之一。

  • 为了减少其他访问请求带来的干扰,将其他服务下线,只保留性能测试使用的接口(D瓜哥这里直接修改了配置文件)。确保应用只处理性能测试的相关请求。

  • 为了减少对相关依赖方的影响,尽可能选择访问量最大的接口,这些接口抗造,有大流量进来,下游接口也能扛得住,减少可能存在的不必要的报警和麻烦。

  • 将线上服务和性能测试服务进行分离,避免影响线上服务,影响用户体验。这里有三个可选方案:

    1. 理想状况下,将所有的外部依赖都做隔离。但是,这个代价比较大,不容易实现。比如,没办法完全重建一套一样的数据库系统,尤其是数据量大的时候。另外,有些接口不再我们可以掌控的范围内,就无法做到有效的隔离。

    2. 将自己可以掌控的外部依赖做有效的隔离,其余不可掌控的接口就不做隔离。这个方案相比上一个方案更容易实现,但是也有不妥之处:单独隔离自己掌控的外部依赖,这些依赖是否可以提供足够的服务能力?会不会成为瓶颈?

    3. 最后一个方案:只隔离性能测试的接口,其余外部依赖都使用线上正式环境的服务。这个方案相对来说比较容易实现。由于上一条也提了,“尽可能选择访问量最大的接口”,这样的话,外部依赖提供的服务大概率可以足够支撑性能测试。性能测试时,只需要做好相关监控,时刻关注告警信息,在过载时,及时终止性能测试任务。这也是D瓜哥最终采用的方案。

  • 准备足够大量的请求参数,防止请求参数太少,导致热点数据,进而触发可能存在的告警。

  • 将 JMeter 的共享模式设置为所有线程,这样的话,每次发送请求的参数都会不一样。

  • 同时对所有分组进行加压,这样可以充分利用相关监控,方便后续做分析对比。

  • 测试时间,尽量选择在线上服务的波谷时间进行,这样可以充分利用依赖服务的多余支撑能力,尽可能高地支持性能测试。

注意事项:

  1. 数据库查询尽量检查,数据量尽量控制在比较接近的数量;减少由于数量差异导致的查询耗时波动。

将上述工作准备完毕后,就可以跑性能测试脚本,开始正式性能测试啦。

收尾工作

在完成性能测试后,有几项必要的工作需要及时完成:

  1. 及时关闭性能测试任务。同时,检查服务是否还存在访问量。双向检查,确保任务确实终止,防止持续加压,也线上业务带来不必要的影响。

  2. 关闭性能测试任务后,如果服务不再需要,就可以考虑直接停机。经过 D瓜哥的观察发现,即使没有接受服务请求, OpenJDK 21 还是会持续产生 JVM 日志,而且量还不小。如果后续还要分析 JVM 日志,那么会产生不必要的多余日志。

  3. 最后,也是最重要的工作,就是根据采集到的相关监控数据,及时对性能测试进行分析和总结,得出最终的结论。