JVM GC 性能测试(二):递增流量

JVM GC 性能测试(二):递增流量

JVM GC 性能测试系列:


在上一篇文章 JVM GC 性能测试(一):相同流量 中,D瓜哥使用一个总量请求对所有分组的所有机器进行性能测试。但是,经过测试发现了一个问题,同时产生了另外一个问题,有两个问题没有得到很好的解答:

  1. 由于服务响应时长直接关系到服务调用次数,当某一台机器出现问题时,整体调用次数就会急剧下降,调用次数加不上去。一个机器出问题,所有机器的访问量就上不去了。这是测试中发现的一个问题。当然,这属于测试工具的问题,别不是 GC 的问题。但是,也影响到我们的压测,也需要解决。

  2. 上次测试,这是针对某一个指定服务调用量进行性能测试,那么,无法确定每个 GC 能支撑的极限调用峰值。另外,在极限峰值和超极限峰值的情况下,各个 GC 的表现如何?这个也有待验证。

针对上述两个问题,设计了本次测试。测试方法如下:

  • 各个分组使用一套相同的流量策略:

    • 各个分组几乎同时开始执行测试任务;

    • 调用量从低到高,以此同时使用相关的调用量进行测试;

    • 除最开始预热阶段的调用量外,后续每个调用量都持续进行十分钟的测试。

  • 针对每个 GC 分组单独设定一套调用发量程序,这个保证各个 GC 分组直接不相互影响。

  • 最后,再分析调用量相同时段的各个 GC 表现,就可以看到各个 GC 的极限峰值。

为了保留更多细节,本文所有截图都是在 34 吋带鱼屏下,使用全屏模式展示并截图的。如果看不清楚,可以右击在新页面打开图片来查看。

具体流量及时间段:

  • 750, 23:14:30 ~ 23:19:30

  • 800, 23:19:30 ~ 23:29:30

  • 850, 23:29:30 ~ 23:39:30

  • 900, 23:39:30 ~ 23:49:30

  • 950, 23:49:30 ~ 23:59:30

  • 1000,23:59:30 ~ 00:09:30

  • 1050,00:09:30 ~ 00:19:30

  • 1100,00:19:30 ~ 00:29:30

这里的流量是单台服务器的测试接口接受到的调用量。

一言以蔽之

  • J21-G1 的极限峰值最高,能支撑到 1100+ QPS;其次是 J21-Gen-ZGC,大约可以支持 1000 QPS(挑食,某些机器会出现 CPU 使用率过高的问题),比 J21-G1 降低了 10%。

  • 在 900 QPS 以下,从 TP999 的数据来看,J21-Gen-ZGC 耗时更短,响应性更好;超过 1000 QPS 时,则 J21-G1 更稳定,耗时最短。

鉴于 OpenJDK 21 G1 GC 出人意外的惊艳表现,D瓜哥准备整理一下 G1 GC 的主要优化,敬请关注: Java G1 垃圾收集器主要优化

1. 服务调用监控数据

监控服务调用的相关数据,这是对于用户来说,感知最强烈的相关数据,也是直接关系到服务质量的数据。

1.1. 服务调用次数

  • 从调用次数上来看,J8-G1 最早开始失守,结合下面 服务调用耗时 来看,应该是 某台机器除了问题,否则 J8-G1 不会在 TP999 表现最差的情况下,TP99 的表现反而变现不错。

  • 紧随其后,以此是 J21-ZGC、J21-Gen-ZGC、J17-ZGC 的表现开始拉胯。

  • 在极限峰值下,J21-Gen-ZGC 的表现又反超了 J17-ZGC;由此可以看出,在极限情况下,J21-Gen-ZGC 的表现更值得信赖。

  • J21-G1 变现一直非常稳定,甚至没有达到它的峰值。

服务调用次数(秒级)
图 1. 服务调用次数(秒级)
服务调用次数(分钟级)
图 2. 服务调用次数(分钟级)

1.2. 服务调用耗时

  • J8-G1 在 TP999 表现最差,但是在 TP99 的表现反而稳居第二。应该是某个机器有问题,整体表现是可以的。

  • 从 TP999 来看,表现最好的是 J21-G1,其次是 J21-Gen-ZGC。

1.2.1. TP999

服务调用 TP999(分钟)
图 3. 服务调用 TP999(分钟)
服务调用 TP999(秒)
图 4. 服务调用 TP999(秒)

1.2.2. TP99

服务调用 TP99(分钟)
图 5. 服务调用 TP99(分钟)
服务调用 TP99(秒)
图 6. 服务调用 TP99(秒)

1.2.3. TP90

服务调用 TP90(分钟)
图 7. 服务调用 TP90(分钟)
服务调用 TP90(秒)
图 8. 服务调用 TP90(秒)

1.2.4. 平均耗时

服务调用耗时(秒级平均)
图 9. 服务调用耗时(秒级平均)

1.2.5. 最大耗时

服务调用耗时(秒级最大)
图 10. 服务调用耗时(秒级最大)
服务调用耗时(分钟级最大)
图 11. 服务调用耗时(分钟级最大)

1.3. 每台机器的调用次数及耗时

点击查看机器分组详情

由于截图时间跨度太长,即使使用分钟级的数据,也不能在一张图上展示所有机器的访问请求。所以,将其分为两组来展示并截图:

  1. J21-Gen-ZGC 和 J21-G1

  2. J21-ZGC 、 J17-ZGC 和 J8-G1

各组的 IP 列表
  • J21-Gen-ZGC:

    • 11.243.85.100

    • 11.243.84.159

    • 11.243.84.164

    • 11.243.84.173

    • 11.248.8.90

  • J21-G1:

    • 11.243.87.118

    • 11.243.87.95

    • 11.248.1.166

    • 11.248.8.91

    • 11.243.87.117

  • J21-ZGC:

    • 11.243.84.112

    • 11.248.8.88

    • 11.243.84.154

    • 11.248.8.89

    • 11.248.1.120

  • J17-ZGC

    • 11.248.1.164

    • 11.243.65.198

    • 11.248.1.165

    • 11.243.85.109

    • 11.243.86.251

  • J8-G1:

    • 11.248.1.168

    • 11.248.1.169

    • 11.248.8.92

    • 11.248.1.167

    • 11.248.8.93

从截图上来看,“J21-Gen-ZGC 和 J21-G1” 这组机器稳定性明显比 “J21-ZGC 、 J17-ZGC 和 J8-G1” 这组要好:

  • 剧烈抖动出现的更晚;

  • 在最高峰值,前者依然有机器支持高流量访问,而后者都已经全部沦陷,只有少量机器支持起降配的访问量。

  • 看 TP99 图表,去掉了个别剧烈抖动的点,前者从 00:10 开始(也就是 QPS 已经到 1050 后),才有一半机器调用耗时过高;而后者,早早就有大量机器开始剧烈抖动。

1.3.1. TP999 及调用次数

每台机器服务调用 TP999 及调用次数:J21-Gen-ZGC 和 J21-G1
图 12. 每台机器服务调用 TP999 及调用次数:J21-Gen-ZGC 和 J21-G1
每台机器服务调用 TP999 及调用次数:J21-ZGC 、 J17-ZGC 和 J8-G1
图 13. 每台机器服务调用 TP999 及调用次数:J21-ZGC 、 J17-ZGC 和 J8-G1

1.3.2. TP99 及调用次数

每台机器服务调用 TP99 及调用次数:J21-Gen-ZGC 和 J21-G1
图 14. 每台机器服务调用 TP99 及调用次数:J21-Gen-ZGC 和 J21-G1
每台机器服务调用 TP99 及调用次数:J21-ZGC 、 J17-ZGC 和 J8-G1
图 15. 每台机器服务调用 TP99 及调用次数:J21-ZGC 、 J17-ZGC 和 J8-G1

2. JVM 监控

2.1. CPU

单独从 CPU 使用率角度来看:

  • J21-ZGC 和 J17-ZGC 早早就把 CPU 干到了 90%+,而这个时候 QPS 只有 750。

  • J21-G1 一直非常稳定,跟随 QPS 的提升,CPU 使用率也稳步上升;而 J21-Gen-ZGC 则更早的把 CPU 使用率打到接近 100%(时间是:00:00,QPS:1000)。J21-G1 比 J21-Gen-ZGC 的稳定性好很多。

CPU 使用率(平均)
图 16. CPU 使用率(平均)
CPU 使用率(最大)
图 17. CPU 使用率(最大)
CPU 使用率(最小)
图 18. CPU 使用率(最小)

2.2. Young GC

关于 Young GC 的说明,D瓜哥在 JVM GC 性能测试(一):相同流量:Young GC 中,已经做了说明,这里就不再赘述。
  • J21-G1 的 Young GC 次数也是随 QPS 的提升,逐步上升;

  • J21-Gen-ZGC 在前期,Young GC 次数也是随 QPS 的提升,逐步上升;临界点在“时间是:00:00,QPS:1000”,在此之后,可能是回收速度有点力不从心,开始频繁地进行 Young GC,耗时也有大幅度增加。

2.2.1. Young GC 次数

JVM Young GC 次数(平均)
图 19. JVM Young GC 次数(平均)
JVM Young GC 次数(最大)
图 20. JVM Young GC 次数(最大)
JVM Young GC 次数(最小)
图 21. JVM Young GC 次数(最小)

2.2.2. Young GC 耗时

JVM Young GC 耗时(平均)
图 22. JVM Young GC 耗时(平均)
JVM Young GC 耗时(最大)
图 23. JVM Young GC 耗时(最大)
JVM Young GC 耗时(最小)
图 24. JVM Young GC 耗时(最小)

2.3. Full GC

  • 整个过程,J21-G1 几乎没有出现 Full GC(图表里只出现了两次),有些让人吃惊。

  • 对比之下,J8-G1 却出现了频繁的 Full GC。

  • J21-Gen-ZGC 后期由于无法支撑超极限流量,所以 Full GC 反倒没有前期多了。

2.3.1. Full GC 次数

JVM Full GC 次数(平均)
图 25. JVM Full GC 次数(平均)
JVM Full GC 次数(最大)
图 26. JVM Full GC 次数(最大)
JVM Full GC 次数(最小)
图 27. JVM Full GC 次数(最小)

2.3.2. Full GC 耗时

JVM Full GC 耗时(平均)
图 28. JVM Full GC 耗时(平均)
JVM Full GC 耗时(最大)
图 29. JVM Full GC 耗时(最大)
JVM Full GC 耗时(最小)
图 30. JVM Full GC 耗时(最小)

2.4. Heap

JVM 堆内存(平均)
图 31. JVM 堆内存(平均)
JVM 堆内存(最大)
图 32. JVM 堆内存(最大)
JVM 堆内存(最小)
图 33. JVM 堆内存(最小)

2.5. 非堆

JVM 非堆内存(平均)
图 34. JVM 非堆内存(平均)

2.6. 线程数

JVM 线程数(平均)
图 35. JVM 线程数(平均)
JVM 线程数(最大)
图 36. JVM 线程数(最大)
JVM 线程数(最小)
图 37. JVM 线程数(最小)

3. 系统监控

3.1. CPU 使用率

系统监控 CPU 使用率(分钟级平均)
图 38. 系统监控 CPU 使用率(分钟级平均)
系统监控 CPU 使用率(分钟级最大)
图 39. 系统监控 CPU 使用率(分钟级最大)
系统监控 CPU 使用率(分钟级最小)
图 40. 系统监控 CPU 使用率(分钟级最小)

3.2. 内存使用率

内存使用率(分钟级平均)
图 41. 内存使用率(分钟级平均)
内存使用率(分钟级最大)
图 42. 内存使用率(分钟级最大)
内存使用率(分钟级最小)
图 43. 内存使用率(分钟级最小)

3.3. 磁盘读写速度

磁盘读写速度(分钟级平均)
图 44. 磁盘读写速度(分钟级平均)
磁盘读写速度(分钟级最大)
图 45. 磁盘读写速度(分钟级最大)

3.4. 网络流入流出速率

网络流入流出速率(分钟级平均)
图 46. 网络流入流出速率(分钟级平均)
网络流入流出速率(分钟级最大)
图 47. 网络流入流出速率(分钟级最大)
网络流入流出速率(分钟级最小)
图 48. 网络流入流出速率(分钟级最小)

3.5. 每个机器 CPU 使用率

每个机器 CPU 使用率
图 49. 每个机器 CPU 使用率

3.6. 每个机器系统负载

每个机器系统负载
图 50. 每个机器系统负载

揭秘

在上一篇文章 JVM GC 性能测试(一):相同流量 的“后话”一节中,D瓜哥提到了对“将 JMeter 的共享模式设置为所有线程,这样的话,每次发送请求的参数都会不一样。”这句话有了新的思考和理解,在这里做个揭秘。

D瓜哥在 JVM GC 性能对比方法 中提到,对于测试接口,最好符合线上实际运行情况,那么就会出现既依赖数据库,又依赖外部接口的情况。那么外部接口的响应变化对我们的测试 GC 的表现来看,其实属于负面影响。这里,也包含数据库的情况。所以,如果“将 JMeter 的共享模式设置为所有线程”,这样每次调用都是一个新的参数(D瓜哥这里的参数样本是700w+,循环一遍要几十分钟),虽然这样的访问情况,更加符合线上真是的访问场景,但是对我们的影响也是巨大的。

如果“将 JMeter 的共享模式设置为当前线程”,这样各个线程之间调用的参数都是一样的,最早的调用相当于给后面的调用做了预热,如果外部依赖有缓存,那么后续的调用都可以直接利用外部依赖的缓存,响应会更好,对我们的测试影响反而更小,更利于对 GC 表现的测试。所以,在这次测试中,D瓜哥就是将 JMeter 的共享模式设置为当前线程。大家有不同的意见和想法,也欢迎留言交流。

下一个问题

JVM GC 性能测试(一):相同流量 中,D瓜哥经过小批量测试,确定的 QPS 是 500。但是,在这次测试中,QPS 直接从 750 起步(前面使用 200、400、600 各跑了 30s 做预热),最后的 QPS 达到了 1100。所以,两个测试得出的结论是一致的。那么,在实际使用中的表现纠结如何呢?这里要打个问号。所以,接下来,D瓜哥准备将这些机器接入到上线流量,使用真是的流量来验证各个 GC 的表现,敬请期待: JVM GC 性能测试(三):真实流量