技术基础 | Apache Cassandra 4.0基准测试
![](https://img2020.cnblogs.com/blog/2111170/202101/2111170-20210123234203046-1510056620.png)
Apache Cassandra 4.0已经发布了Beta版,这是第一个支持JDK 11及更高JDK版本的Cassandra版本。
时延对于Apache Cassandra™用户来说是个显而易见的关注点,所以大家对JDK 11中引入的全新低时延垃圾收集器ZGC(Z Garbage Collector)寄予厚望。
我们将看到Cassandra 4.0带来了强大的性能改进,而一些新的垃圾收集器(ZGC、尤其是Shenandoah)的上线很大程度上加强了这些提升改进的效果。
经作者测试,Cassandra 4.0在吞吐量和时延方面都有25%到30%的提升。所以在使用相同的垃圾收集器时,Cassandra 4.0可以很轻易地打败Cassandra 3.11.6。
01 测试方法
下面进行的基准测试是用tlp-cluster在AWS中生成并配置Apache Cassandra集群的,并用tlp-stress来进行负载生成和指标收集工作的。
所有在本测试中用到的工具都是开源的,并且只要拥有一个AWS账号,任何人都可以很容易地复现本次测试的过程和结果。
本测试中的集群中有三个r3.2xlarge实例作为节点,另有一个c3.2xlarge实例作为压测节点。
除了垃圾收集和堆内存设置外,我们使用了Apache Cassandra的默认设置。
集群的生成和配置是由最新版本的tlp-cluster完成的。除此之外,我们还添加了一些辅助脚本(helper script),从而可以将Reaper和Medusa的集群生成和安装过程自动化。
在根据文档安装并配置好tlp-cluster工具之后,你就可以随时创建跟我们一样的 用来做基准测试的Cassandra集群了。
# 3.11.6 CMS JDK8 build_cluster.sh -n CMS_3-11-6_jdk8 -v 3.11.6 --heap=16 --gc=CMS -s 1 -i r3.2xlarge --jdk=8 --cores=8 # 3.11.6 G1 JDK8 build_cluster.sh -n G1_3-11-6_jdk8 -v 3.11.6 --heap=31 --gc=G1 -s 1 -i r3.2xlarge --jdk=8 --cores=8 # 4.0 CMS JDK11 build_cluster.sh -n CMS_4-0_jdk11 -v 4.0~alpha4 --heap=16 --gc=CMS -s 1 -i r3.2xlarge --jdk=11 --cores=8 # 4.0 G1 JDK14 build_cluster.sh -n G1_4-0_jdk14 -v 4.0~alpha4 --heap=31 --gc=G1 -s 1 -i r3.2xlarge --jdk=14 --cores=8 # 4.0 ZGC JDK11 build_cluster.sh -n ZGC_4-0_jdk11 -v 4.0~alpha4 --heap=31 --gc=ZGC -s 1 -i r3.2xlarge --jdk=11 --cores=8 # 4.0 ZGC JDK14 build_cluster.sh -n ZGC_4-0_jdk14 -v 4.0~alpha4 --heap=31 --gc=ZGC -s 1 -i r3.2xlarge --jdk=14 --cores=8 # 4.0 Shenandoah JDK11 build_cluster.sh -n Shenandoah_4-0_jdk11 -v 4.0~alpha4 --heap=31 --gc=Shenandoah -s 1 -i r3.2xlarge --jdk=11 --cores=8
注意:为了在基准测试中控制变量,整个测试中我们会同用一组EC2实例进行测试。
适当地使用下面的脚本,就可以完成Cassandra 3.11.6到Cassandra 4.0~alpha4的升级以及不同版本JDK的置换:
#!/usr/bin/env bash OLD=$1 NEW=$2 curl -sL https://github.com/shyiko/jabba/raw/master/install.sh | bash . ~/.jabba/jabba.sh jabba uninstall $OLD jabba install $NEW jabba alias default $NEW sudo update-alternatives --install /usr/bin/java java ${JAVA_HOME%*/}/bin/java 20000 sudo update-alternatives --install /usr/bin/javac javac ${JAVA_HOME%*/}/bin/java
在调用JDK版本管理工具jabba时,可以使用以下JDK值:
-
openjdk@1.11.0-2
-
openjdk@1.14.0
-
openjdk-shenandoah@1.8.0
-
openjdk-shenandoah@1.11.0
OpenJDK 8已经用Ubuntu的apt工具安装完成。
下面是在基准测试中,在不同的JDK版本下,java -version的输出结果:
-
jdk8
openjdk version "1.8.0_252" OpenJDK Runtime Environment (build 1.8.0_252-8u252-b09-1~18.04-b09) OpenJDK 64-Bit Server VM (build 25.252-b09, mixed mode)
-
jdk8 with Shenandoah
openjdk version "11.0.2" 2019-01-15 OpenJDK Runtime Environment 18.9 (build 11.0.2+9) OpenJDK 64-Bit Server VM 18.9 (build 11.0.2+9, mixed mode)
-
jdk11 with Shenandoah
openjdk version "11.0.8-testing" 2020-07-14 OpenJDK Runtime Environment (build 11.0.8-testing+0-builds.shipilev.net-openjdk-shenandoah-jdk11-b277-20200624) OpenJDK 64-Bit Server VM (build 11.0.8-testing+0-builds.shipilev.net-openjdk-shenandoah-jdk11-b277-20200624, mixed mode)
- jdk14
openjdk version "14.0.1" 2020-04-14 OpenJDK Runtime Environment (build 14.0.1+7) OpenJDK 64-Bit Server VM (build 14.0.1+7, mixed mode, sharing)
02 CMS
CMS (Concurrent Mark Sweep)收集器是目前Apache Cassandra默认的垃圾收集器。由于它在JDK 14中被移除了,所以所有的测试都是基于JDK 8或JDK 11进行的。
下面的设置被用于CMS的基准测试:
-XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=1 -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly -XX:CMSWaitDuration=10000 -XX:+CMSParallelInitialMarkEnabled -XX:+CMSEdenChunksRecordAlways -XX:+CMSClassUnloadingEnabled -XX:ParallelGCThreads=8 -XX:ConcGCThreads=8 -Xms16G -Xmx16G -Xmn8G
请注意,-XX:+UseParNewGC参数已经被从JDK 11中移除,在那之后它就变成了一个隐式参数(implicit parameter)。使用这个参数会阻止JVM的启动。
我们将CMS的最大堆内存(max heap)限制在16GB,否则它可能会引发major collection的长时间暂停。
03 G1
相比CMS收集器,G1GC(即Garbage-First Garbage Collector,垃圾优先型垃圾收集器)要容易配置一些,因为它可以动态调整年轻代的大小。
不过G1GC更适用于大型的堆内存(>=24GB)——这也就是为什么它没有成为Cassandra的默认垃圾收集器。另外,虽然它的吞吐量比CMS更好,但是它的时延比调试过的CMS要长。
下面的设置被用于G1的基准测试:
-XX:+UseG1GC -XX:G1RSetUpdatingPauseTimePercent=5 -XX:MaxGCPauseMillis=300 -XX:InitiatingHeapOccupancyPercent=70 -XX:ParallelGCThreads=8 -XX:ConcGCThreads=8 -Xms31G -Xmx31G
为了对Cassandra 4.0进行基准测试,我们在运行G1测试时使用了JDK14。
我们使用31GB的堆内存大小,从而可以受益于压缩指针(compressed oops),并可以以最小的堆内存大小拥有最多的可寻址对象。
04 ZGC
ZGC(Z Garbage Collector)是JDK中最新的垃圾收集器,它主要的关注点是将让全世界暂停(stop-the-world)的时延缩小至10ms以内。ZGC还应该可以保证堆内存大小对暂停时间没有影响,这使得它的堆内存大小可以扩充至16TB。
如果这些令人期待的效果都能被满足,ZGC就使得使用堆外存储变得没有必要,而且还能简化Apache Cassandra的一些开发任务。
下面的设置被用于ZGC的基准测试:
-XX:+UnlockExperimentalVMOptions -XX:+UseZGC -XX:ConcGCThreads=8 -XX:ParallelGCThreads=8 -XX:+UseTransparentHugePages -verbose:gc -Xms31G -Xmx31G
我们需要用-XX:+UseTransparentHugePages作为一种灵活变通的方式来避免在Linux里启用大内存页(page)。
尽管官方的ZGC文档称这可能会造成时延激增,从测试的结果来看似乎并没出现这种情况。我们也许可以使用大内存页来进行多次吞吐量测试,从而判断这种方法会对基准测试的结果有什么影响。
请注意,ZGC不能使用压缩指针,但是也不受“32GB阈值”的限制。我们在对ZGC的测试中,使用和在G1测试中一样的31GB的堆内存。这样,两种情况下系统空闲的内存大小就会是一样的。
05 Shenandoah
Shenandoah是一个由红帽(RedHat)开发的低时延垃圾收集器。在JDK 8和11中,它作为一个向后移植(backport)的版本存在。从Java 13开始,它就被包含在OpenJDK的主线版本里了。
与ZGC一样,在大多数情况下Shenandoah是个并发垃圾收集器。它的目标是让暂停时间不会随着堆内存的增大而线性增加。
下面的设置被用于Shenandoah的基准测试:
-XX:+UnlockExperimentalVMOptions -XX:+UseShenandoahGC -XX:ConcGCThreads=8 -XX:ParallelGCThreads=8 -XX:+UseTransparentHugePages -Xms31G -Xmx31G
Shenandoah应该可以使用压缩指针,因此可以受益于使用比32GB稍小一些的堆。
06 Cassandra 4.0的JVM配置
Cassandra 4.0版本分别为Java 8和Java 11推出了不同的jvm.options文件。它们是:
-
conf/jvm-server.options
-
conf/jvm8-server.options
-
conf/jvm11-server.options
如果将Cassandra从3.11版本升级到4.0版本,原先已有的jvm.options文件依然可以沿用,只要它被重命名为jvm-server.options,并且将jvm8-server.options和jvm11-server.options文件移除就好。不过,这并不是值得推荐的方式.
值得推荐的方式是将原来的jvm.options文件里的设置,重新应用到新的jvm-server.options和jvm8-server.options文件上。这些特定的Java选项文件(option file)大多是和垃圾收集的参数相关的。
一旦jvm-server.options和jvm8-server.options文件更新完毕并且就位,配置jvm11-server.options文件以及从JDK 8转换到JDK 11就容易多了。
07 工作负载
本次基准测试中有8个线程,并将读写比例限制为80%写/20%读。tlp-stress大量使用异步的查询语句,这使得它只要有限的几个压测线程一不小心就可能会让Cassandra节点过载。在本次的负载测试中,每一个线程会一次并发地发送50个查询语句。
本次测试中的键空间(keyspace)的复制因子(replication factor)为3,并且所有的语句都以一致性级别LOCAL_ONE来执行。
对于所有垃圾收集器和Cassandra版本的测试,操作的数目都以每秒25k、40k、45k、50k的增长幅度进行,因而我们能够评估它们在不同压力水平下的性能表现。
下面的tlp-stress语句被使用在本测试中:
tlp-stress run BasicTimeSeries -d 30m -p 100M -c 50 --pg sequence -t 8 -r 0.2 --rate <desired rate> --populate 200000
所有的工作负载都运行30分钟,将5至16GB的数据装载到每个节点并考虑合理的压实负载。
请注意,这个测试的目的并非测评Cassandra的最优性能,因为对于不同的工作负载,可以通过很多方式进行调试出最优性能。
这个测试的目的也并非调试这些垃圾收集器,虽然它们其实已经暴露了很多可以针对特定工作负载提高性能的参数和选项。
这些基准测试想要达到的目的是:在使用大部分默认设置并且Cassandra产生同样的负载的情况下,提供一个针对不同垃圾收集器的公平比较。
08 基准测试结果
3.11.6 25k-40k ops/s
![](https://pic1.zhimg.com/80/v2-a4e32b831ae713e4303d4c7b33e71c9d_720w.png)
4.0 25k-40k ops/s
![](https://pic1.zhimg.com/80/v2-7892fbbdabda0a34e4fea7ad415273f1_720w.png)
4.0 45k-50k ops/s
![](https://pic2.zhimg.com/80/v2-bbe9709f3696ea1b6323024e079b735c_720w.png)
![](https://pic4.zhimg.com/80/v2-d82518847b414e99779546d6a85fc220_720w.png)
就吞吐量而言,Cassandra 3.11.6最高可达41k ops/s,而Cassandra 4.0则高达51k ops/s。两种版本都用了CMS作为垃圾收集器,而升级后的Cassandra 4.0的比Cassandra 3.11.6的提升达到了25%。
4.0版本中的大量性能提升都可以用来解释这个结果,尤其是关于压实(compaction)操作引发的堆内存压力问题(可查看CASSANDRA-14654作为例子)。
在Cassandra 3.11.6集群中的JDK 8的Shenandoah在40k ops/s的负载测试中,不仅没能实现最高的吞吐量,还出现了查询失败的情况。
而借助于Cassandra 4.0集群和JDK 11,Shenandoah的表现就好很多——它在这种情况下的最大吞吐量49.6k ops/s,几乎可以赶得上4.0在CMS下的吞吐量了。
使用JDK 8和Cassandra 3.11.6,G1和Shenandoah总体上的吞吐量都只能最高达到36k ops/s。
使用JDK 14和JDK 11,前者似乎让G1的表现也有些微提高——从47k/s提高到50k/s。
无论是使用JDK 14还是JDK 11,ZGC的吞吐量都无法与其它的垃圾回收器匹敌,最高只能到41k ops/s。
![](https://pic4.zhimg.com/80/v2-f754378a76cc8b5c34c91635e238017e_720w.png)
![](https://pic1.zhimg.com/80/v2-1c728e87e4284689cc109d4787a1a047_720w.png)
![](https://pic2.zhimg.com/80/v2-7d60c79c68eddf77a64557d706b2a723_720w.png)
![](https://pic4.zhimg.com/80/v2-c48aa4b5204e0c4c222a94fcc919cb0b_720w.png)
![](https://pic4.zhimg.com/80/v2-3d93542d48cbfc0d30a99e6aef3de226_720w.png)
![](https://pic1.zhimg.com/80/v2-4a60abff87b59f58389b62894833c1c5_720w.png)
在Cassandra 3.11.6集群中,JDK 8的Shenandoah在中等程度的负载下,展现出了非常令人印象深刻的低时延。不过,它的性能会随着负载压力的提升而严重下降。
在使用CMS的情况下,Cassandra 4.0的平均p99(99百分位)值介于11ms到31ms之间,而吞吐量则高达50k ops/s。在中等程度的负载下,读操作的P99平均值在Cassandra 3.11.6中为17ms,而在Cassandra 4.0中则下降到了11.5ms。即相比之下,在时延方面Cassandra 4.0有30%的提升。
Cassandra 4.0在吞吐量和时延方面都有25%到30%的提升,所以在使用相同的垃圾收集器时,Cassandra 4.0可以很轻易地打败Cassandra 3.11.6。
值得一提的是,在Cassandra 3.11.6集群中,Shenandoah在中等程度的负载下的延迟非常低,不过它在压力下的表现使我们担心其处理突增负载的能力。
虽然ZGC在中等程度的负载下展现出了非常令人印象深刻的低时延,尤其是在使用JDK 14时,但是它的最高吞吐量并不能与Shenandoah相匹敌。
几乎在所有的负载测试中,Shenandoah的读操作时延和写操作时延的平均p99值都是最低的。Shenandoah这样低的时延再加上它在Cassandra 4.0中能达到的吞吐量,使它成为了在向Cassandra 4.0升级时,一个值得考虑的垃圾收集器。
在中等程度的负载下,读操作时延的平均p99值只有2.64ms本身已经是相当令人印象深刻的。在此基础上,如果你知道这些的数据是由客户端记录的,你就不得不对Shenandoah刮目相看了。
在大多数情况下,G1的最大p99值符合它所配置的最大暂停时间,即300ms。如果想要降低目标的暂停时间,则可能会在高负载的情况下出现不想看到的效果,甚至可能会触发更长时间的暂停。
在中等程度的负载下,Shenandoah的平均p99时延可以降低77%,即最高时延只有2.64ms。这对于时延敏感的用例来说会是一项重大的提升——相比使用Cassandra 3.11.6中的CMS,读操作的p99时延大幅降低了85%!
值得一提的是,JDK 14中的ZGC在中等程度的负载下有着良好的性能,但是遗憾的是它不能在更大的吞吐速率下保持同样的表现。我们乐观地认为ZGC会在未来的几个月内被改进,最终可能会足以与Shenandoah一较高下。
09 反思
G1通过移除对不同代的大小的调试需求得以提升Cassandra的易用性,但是 是以牺牲一定的性能为代价的。
新发行的Apache Cassandra 4.0带来了令人印象极为深刻的性能增强,它将会允许使用像是Shenandoah和ZGC这样的新一代垃圾收集器。这些收集器简单易用,无需太多细微精妙的调试,并且在时延问题上更为高效。
如果使用Cassandra 3.11.6,我们很难向你推荐Shenandoah,因为Cassandra节点在高负载情况下的表现并不如意。但是从JDK 11和Cassandra 4.0开始,Shenandoah在延迟方面有着惊人的改进,同时还能支持几乎是Cassandra数据库所能提供的最大吞吐量。
由于这次基准测试集中关注于特定的工作负载,你的测试结果可能会依情况而有所不同。但是对于时延敏感的用例来说,这次测试的结果让我们对Apache Cassandra的未来感到相当的乐观,因为它将为我们带来超过Cassandra 3.11.6的大幅改进。
下载最新的Apache 4并且自行尝试一番吧。如果你有任何反馈意见,记得通过社区邮件或是ASF Slack联系我们。点击这里查看我们的联系方式。