JVM GC 性能测试(二):递增流量
![JVM GC 性能测试(二):递增流量](/images/java/jvm.jpg)
JVM GC 性能测试系列:
在上一篇文章 JVM GC 性能测试(一):相同流量 中,D瓜哥使用一个总量请求对所有分组的所有机器进行性能测试。但是,经过测试发现了一个问题,同时产生了另外一个问题,有两个问题没有得到很好的解答:
由于服务响应时长直接关系到服务调用次数,当某一台机器出现问题时,整体调用次数就会急剧下降,调用次数加不上去。一个机器出问题,所有机器的访问量就上不去了。这是测试中发现的一个问题。当然,这属于测试工具的问题,别不是 GC 的问题。但是,也影响到我们的压测,也需要解决。
上次测试,这是针对某一个指定服务调用量进行性能测试,那么,无法确定每个 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 更稳定,耗时最短。
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 变现一直非常稳定,甚至没有达到它的峰值。
![服务调用次数(秒级)](/images/gc-performance-2/api-qps-second.jpg)
![服务调用次数(分钟级)](/images/gc-performance-2/api-qps-minute.jpg)
1.2. 服务调用耗时
J8-G1 在 TP999 表现最差,但是在 TP99 的表现反而稳居第二。应该是某个机器有问题,整体表现是可以的。
从 TP999 来看,表现最好的是 J21-G1,其次是 J21-Gen-ZGC。
1.2.1. TP999
![服务调用 TP999(分钟)](/images/gc-performance-2/api-qps-tp999-minute.jpg)
![服务调用 TP999(秒)](/images/gc-performance-2/api-qps-tp999-second.jpg)
1.2.2. TP99
![服务调用 TP99(分钟)](/images/gc-performance-2/api-qps-tp99-minute.jpg)
![服务调用 TP99(秒)](/images/gc-performance-2/api-qps-tp99-second.jpg)
1.2.3. TP90
![服务调用 TP90(分钟)](/images/gc-performance-2/api-qps-tp90-minute.jpg)
![服务调用 TP90(秒)](/images/gc-performance-2/api-qps-tp90-second.jpg)
1.2.4. 平均耗时
![服务调用耗时(秒级平均)](/images/gc-performance-2/api-qps-avg-second.jpg)
1.2.5. 最大耗时
![服务调用耗时(秒级最大)](/images/gc-performance-2/api-qps-max-second.jpg)
![服务调用耗时(分钟级最大)](/images/gc-performance-2/api-qps-max-minute.jpg)
1.3. 每台机器的调用次数及耗时
点击查看机器分组详情
从截图上来看,“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](/images/gc-performance-2/api-tp999-per-host-j21-gen-zgc-vs-j21-g1.jpg)
![每台机器服务调用 TP999 及调用次数:J21-ZGC 、 J17-ZGC 和 J8-G1](/images/gc-performance-2/api-tp999-per-host-j21-zgc-vs-j17-zgc-vs-j8-g1.jpg)
1.3.2. TP99 及调用次数
![每台机器服务调用 TP99 及调用次数:J21-Gen-ZGC 和 J21-G1](/images/gc-performance-2/api-tp99-per-host-j21-gen-zgc-vs-j21-g1.jpg)
![每台机器服务调用 TP99 及调用次数:J21-ZGC 、 J17-ZGC 和 J8-G1](/images/gc-performance-2/api-tp99-per-host-j21-zgc-vs-j17-zgc-vs-j8-g1.jpg)
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 使用率(平均)](/images/gc-performance-2/jvm-cpu-avg.jpg)
![CPU 使用率(最大)](/images/gc-performance-2/jvm-cpu-max.jpg)
![CPU 使用率(最小)](/images/gc-performance-2/jvm-cpu-min.jpg)
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 次数(平均)](/images/gc-performance-2/jvm-young-gc-avg.jpg)
![JVM Young GC 次数(最大)](/images/gc-performance-2/jvm-young-gc-max.jpg)
![JVM Young GC 次数(最小)](/images/gc-performance-2/jvm-young-gc-min.jpg)
2.2.2. Young GC 耗时
![JVM Young GC 耗时(平均)](/images/gc-performance-2/jvm-young-gc-time-avg.jpg)
![JVM Young GC 耗时(最大)](/images/gc-performance-2/jvm-young-gc-time-max.jpg)
![JVM Young GC 耗时(最小)](/images/gc-performance-2/jvm-young-gc-time-min.jpg)
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 次数(平均)](/images/gc-performance-2/jvm-full-gc-avg.jpg)
![JVM Full GC 次数(最大)](/images/gc-performance-2/jvm-full-gc-max.jpg)
![JVM Full GC 次数(最小)](/images/gc-performance-2/jvm-full-gc-min.jpg)
2.3.2. Full GC 耗时
![JVM Full GC 耗时(平均)](/images/gc-performance-2/jvm-full-gc-time-avg.jpg)
![JVM Full GC 耗时(最大)](/images/gc-performance-2/jvm-full-gc-time-max.jpg)
![JVM Full GC 耗时(最小)](/images/gc-performance-2/jvm-full-gc-time-min.jpg)
2.4. Heap
![JVM 堆内存(平均)](/images/gc-performance-2/jvm-heap-avg.jpg)
![JVM 堆内存(最大)](/images/gc-performance-2/jvm-heap-max.jpg)
![JVM 堆内存(最小)](/images/gc-performance-2/jvm-heap-min.jpg)
2.5. 非堆
![JVM 非堆内存(平均)](/images/gc-performance-2/jvm-non-heap.jpg)
2.6. 线程数
![JVM 线程数(平均)](/images/gc-performance-2/jvm-thead-avg.jpg)
![JVM 线程数(最大)](/images/gc-performance-2/jvm-thead-max.jpg)
![JVM 线程数(最小)](/images/gc-performance-2/jvm-thead-min.jpg)
3. 系统监控
3.1. CPU 使用率
![系统监控 CPU 使用率(分钟级平均)](/images/gc-performance-2/os-cpu-avg-minute.jpg)
![系统监控 CPU 使用率(分钟级最大)](/images/gc-performance-2/os-cpu-max-minute.jpg)
![系统监控 CPU 使用率(分钟级最小)](/images/gc-performance-2/os-cpu-min-minute.jpg)
3.2. 内存使用率
![内存使用率(分钟级平均)](/images/gc-performance-2/os-cache-avg-minute.jpg)
![内存使用率(分钟级最大)](/images/gc-performance-2/os-cache-max-minute.jpg)
![内存使用率(分钟级最小)](/images/gc-performance-2/os-cache-min-minute.jpg)
3.3. 磁盘读写速度
![磁盘读写速度(分钟级平均)](/images/gc-performance-2/os-disk-avg-minute.jpg)
![磁盘读写速度(分钟级最大)](/images/gc-performance-2/os-disk-max-minute.jpg)
3.4. 网络流入流出速率
![网络流入流出速率(分钟级平均)](/images/gc-performance-2/os-net-avg-minute.jpg)
![网络流入流出速率(分钟级最大)](/images/gc-performance-2/os-net-max-minute.jpg)
![网络流入流出速率(分钟级最小)](/images/gc-performance-2/os-net-min-minute.jpg)
3.5. 每个机器 CPU 使用率
![每个机器 CPU 使用率](/images/gc-performance-2/cpu-per-host.jpg)
3.6. 每个机器系统负载
![每个机器系统负载](/images/gc-performance-2/load-per-host.jpg)
揭秘
在上一篇文章 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 性能测试(三):真实流量。