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 8已經用Ubuntu的apt工具安裝完成。
下面是在基準測試中,在不一樣的JDK版本下,java -version的輸出結果:
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)
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)
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)
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進行的。
-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要長。
-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的一些開發任務。
-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是個併發垃圾收集器。它的目標是讓暫停時間不會隨着堆內存的增大而線性增長。
-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文件。它們是:
若是將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 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 基準測試結果
就吞吐量而言,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。
在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聯繫咱們。點擊這裏查看咱們的聯繫方式。