天天看點

消滅毛刺!HBase2.0全鍊路offheap效果拔群阿裡雲HBase2.0版本正式上線全鍊路offheap原理對比測試總結雲端使用

阿裡雲HBase2.0版本正式上線

阿裡雲HBase2.0版本是基于社群2018年釋出的HBase2.0.0版本開發的全新版本。在社群HBase2.0.0版本基礎上,做了大量的改進和優化,吸收了衆多阿裡内部成功經驗,比社群HBase版本具有更好的穩定性和性能,同時具備了HBase2.0提供的全新能力。HBase2.0提供的新功能介紹可以參照這篇

文章

。如果想要申請使用全新的HBase2.0版本,可以在此連結

申請試用

。在HBase2.0提供的衆多功能中,最引人注目的就是全鍊路的offheap能力了。根據HBase社群官方文檔的說法,全鍊路的offheap功能能夠顯著減少JVM heap裡的資料生成和拷貝,減少垃圾的産生,減少GC的停頓時間。

線上業務在使用hbase讀寫資料時,我們可能會發現,HBase的平均延遲會很低,可能會低于1ms,但P999延遲(99.9%請求傳回的最大時間)可能會高達數百ms。這就是所謂的"毛刺",這些毛刺可能會造成我們的線上業務出現部分請求逾時,造成服務品質的下降。而對于HBase來說,GC的停頓,很多時候是造成這樣的毛刺的“罪非禍首”。那HBase2.0中的全鍊路offheap對減少GC停頓,降低P999延遲,真的有那麼神奇的功效嗎?

全鍊路offheap原理

在HBase的讀和寫鍊路中,均會産生大量的記憶體垃圾和碎片。比如說寫請求時需要從Connection的ByteBuffer中拷貝資料到KeyValue結構中,在把這些KeyValue結構寫入memstore時,又需要将其拷貝到MSLAB中,WAL Edit的建構,Memstore的flush等等,都會産生大量的臨時對象,和生命周期結束的對象。随着寫壓力的上升,GC的壓力也會越大。讀鍊路也同樣存在這樣的問題,cache的置換,block資料的decoding,寫網絡中的拷貝等等過程,都會無形中加重GC的負擔。而HBase2.0中引入的全鍊路offheap功能,正是為了解決這些GC問題。大家知道Java的記憶體分為onheap和offheap,而GC隻會整理onheap的堆。全鍊路Offheap,就意味着HBase在讀寫過程中,KeyValue的整個生命周期都會在offheap中進行,HBase自行管理offheap的記憶體,減少GC壓力和GC停頓。

寫鍊路的offheap包括以下幾個優化:

  1. 在RPC層直接把網絡流上的KeyValue讀入offheap的bytebuffer中
  2. 使用offheap的MSLAB pool
  3. 使用支援offheap的Protobuf版本(3.0+)

讀鍊路的offheap主要包括以下幾個優化:

  1. 對BucketCache引用計數,避免讀取時的拷貝
  2. 使用ByteBuffer做為服務端KeyValue的實作,進而使KeyValue可以存儲在offheap的記憶體中
  3. 對BucketCache進行了一系列性能優化

對比測試

全鍊路offheap效果怎麼樣,是騾子是馬,都要拿出來試試了。測試的準備工作和相關參數如下:

HBase版本

本次測試選用的1.x版本是雲HBase1.1版本截止目前為止最新的AliHB-1.4.9版本,2.x版本是雲HBase2.0版本截止目前為止最新的AliHB-2.0.1。這裡所有的版本号均為阿裡内部HBase分支——AliHB的版本号,與社群的版本号無任何關聯。

機型

所有的測試都是針對一台8核16G的ECS機器上部署的RegionServer。底層的HDFS共有兩個datanode(副本數為2),其中一個與該RegionServer部署在同一台。每個datanode節點挂載了4塊150GB的SSD雲盤

測試工具

本次測試所用的是hbase自帶的pe工具,由于原生的PE工具不支援不支援單行put和指定batch put數量,是以我對PE工具做了一定的改造,并回饋給了社群,具體内容和使用方法參見這篇

表屬性

測試表的分區為64個,compression算法為SNAPPY,Encoding設定為NONE。所有的region都隻在一台RegionServer上。

相關的HBase參數

共同參數

  • HBase的heap大小為9828MB,其中新生代區大小為1719MB
  • 使用的GC算法為CMS GC,當老年代占用大小超過75%時開始CMS GC。
  • hfile.block.cache.size 為0.4, 也就是說預設的lru cache的大小為3931.2MB
  • hbase.regionserver.global.memstore.size 為0.35, 即預設的memstore的大小為3439.8MB
  • 開啟了讀寫分離,在做寫相關的測試時,寫線程為90個,讀線程為10個。在做讀相關測試時(包括讀寫混合),寫線程為20個,讀線程為80個

HBase2.xoffheap相關參數

在測寫場景時,使用了HBase2.x的預設參數,即隻開啟了RPC鍊路上的offheap,并沒有開始memstore的offheap。因為根據測試,我們發現開啟memstore的offheap并沒有帶來多大改善,究其原因,還是因為Memstore的offheap隻是把KeyValue資料offheap,而Memstore本身使用的Java原生的ConcurrentSkipListMap,其索引結構會在JVM的heap中産生大量的記憶體碎片,是以隻把KeyValue offheap的效果并不是很明顯。畢竟,在HBase-1.x開始,就有了MSLAB來管理Memstore中的KeyValue對象,記憶體結構已經比較緊湊。

在測讀場景時:

  • hbase-env.sh中設定HBASE_OFFHEAPSIZE=5G (RPC和HDFS 用戶端需要部分DirectMemory)
  • hbase.bucketcache.ioengine 調成offheap
  • hbase.bucketcache.size 調成 3911,即使用3911MB的DirectMemory來做L2 的cache來 cache data block(之前的測試發現L1中meta block index block的大小大約為20MB,是以在原來onheap的cache基礎上減去了20MB)
  • 由于cahce的一部分放入offheap,heapsize減至6290MB
  • block cache的比例不變,用來做L1 cache來cache META block(可能遠遠大約meta block的需求,但測試中隻需保證meta block 100%命中即可,大了不會影響測試)

注意,本次測試旨在測試HBase2.x與HBase1.x版本在相同壓力下延遲和GC的表現情況,并非測試HBase的最大吞吐能力,是以測試所用的用戶端線程數也隻限制在了60~64個,遠沒有達到雲HBase的最大吞吐能力

單行寫場景

單行寫測試時使用PE工具開啟64個寫線程,每個寫線程随機往HBase表中寫入150000行,共960w行。每行的value size為200bytes。所用的PE指令為

hbase pe --nomapred --oneCon=true --valueSize=200 --compress=SNAPPY --rows=150000 --autoFlush=true --presplit=64 randomWrite 64           
版本 TPS AVG RT 95% RT 99% RT 99.9% RT MAX RT
AliHB-1.4.9 38737 1.1ms 2ms 45ms 140ms
AliHB-2.0.1 40371 0.7ms 1ms 5ms
AVG younggc Time younggc GC頻率
90ms 0.6次/s
110ms 0.28次/s

可以看到,使用了HBase-2.x的寫鍊路offheap後,單行寫的P999延遲從45ms降低到了5ms,效果非常明顯。同時吞吐有5%的提升,帶來這種效果的原因就是寫鍊路的offheap使HBase在heap的young區減少了臨時對象的産生,younggc發生的頻率從0.6次每秒降低到了0.28次每秒。這樣受到younggc影響的請求量也會大大減少。是以P999延遲急劇下降.

批量寫

在批量寫測試中,一次batch的個數是100。使用的指令為:

hbase pe --nomapred --oneCon=true --valueSize=200 --compress=SNAPPY --rows=200000 --autoFlush=true --presplit=64 --multiPut=100 randomWrite 64           

測試的場景和參數配置與單行寫保持一緻

81477 72ms 220ms 350ms 420ms
​97985 ​67ms 75ms ​280ms 300ms
120ms
180ms

可以看到,使用了HBase-2.x的寫鍊路offheap後,從平均延遲到最大延遲,都有不同程度的下降,GC的頻率也降到1.x版本的一半以下。是以吞吐也上漲了20%。

100%Cache命中單行Get

在此場景中,先使用以下指令先往表中灌了120w行資料

hbase pe --nomapred --oneCon=true --valueSize=200 --compress=SNAPPY --rows=200000 --autoFlush=true --presplit=64 --multiPut=100 sequentialWrite  60           

再保證所有資料刷盤,major compact成一個檔案後,先做cache的預熱,然後使用如下指令進行單行讀取:

hbase pe --nomapred --oneCon=true --rows=200000 randomRead   60           

測試結果如下:

QPS
AliHB-1.4.9​ 53895 ​0.04ms ​1ms 30ms
AliHB-2.0.1​ 49518 0.05ms ​​0ms ​14ms

注:百分比的延遲統計最低分辨率是1ms,是以低于1ms時會顯示為0

​25ms 0.4次/s
8ms 0.35次/s

可以看到,在100%記憶體命中場景下,HBase2.x的吞吐性能有了8%的下滑。這是預料之中的,這在HBase的官方文檔中也有解釋:讀取offheap的記憶體會比讀onheap的記憶體性能會稍稍下滑。另外,由于在100%記憶體命中的場景下,onheap的cache也不會發生置換,是以産生的gc開銷會比較小,是以在這個場景中,HBase1.x版本的P999延遲也已經比較低。但是,在這個GC不會很嚴重的場景裡(沒有寫,沒有開Block-encoding,cache裡内容不用decode可以直接使用),HBase2.x版本仍然可以把最大延遲降到1.x版本的一半,非常難能可貴。

部分cache命中單行讀

在這個場景中,先使用以下指令往表中灌了3600w行資料,這些資料會超過設定的cache大小,進而會産生一定的cache miss。

灌資料:

hbase pe --nomapred --oneCon=true --valueSize=200 --compress=SNAPPY --rows=600000 --autoFlush=true --presplit=64 --multiPut=100 sequentialWrite  60           
hbase pe --nomapred --oneCon=true --rows=600000 randomRead   60           
AliHB-1.4.9​​ 14944 ​2.5ms ​5ms 80ms 200ms ​300ms
​15372 ​1.7ms ​34ms 65ms ​130ms
CMS GC Remark AVG Time CMS GC 頻率
2.4次/s 50ms 0.25次/s
21ms 2.5次/s

在部分cache命中的場景中,由于會有一定的cahce miss,在讀的過程中,會産生cache内容的置換。如果這些記憶體的置換發生在heap裡,會顯著加重GC的負擔。是以,在這個GC壓力比較大的場景中,HBase2.x的全鍊路讀offheap産生了非常優秀的效果,無論是吞吐,平均延遲還是P999和最大延遲,都全面超越HBase1.x版本。由于cache不會在heap中産生垃圾,是以GC的頻率和耗時都顯著降低,基本消滅了CMSGC。更加難能可貴的是,使用了offheap的bucketcache由于每個bucket都是固定大小,是以在放入不定大小的data block時不可能完全放滿,進而會造成一些空間的浪費。是以雖然我把兩者的cache大小調到一樣的大小,HBase1.x的測試中,data block的命中率有58%,HBase2.x的測試中命中率隻有40%。也就是說,HBase2.x在命中率更低的情況下,取得的吞吐和延遲都更加優秀!但這從另外一個方面說明,同樣的記憶體大小,在使用offheap功能後,cache的命中率會降低,是以使用offheap時最好使用速度更高的媒體做存儲,比如本次測試中選用的SSD雲盤。保證讀取速度不會被落盤而拖慢太多。

讀寫混合測試

讀寫混合測試是大部分生産環境中面對的真實場景。大批量的寫和部分命中的讀都會産生GC壓力,兩者一起發生,GC壓力可想而知。

在這個測試中,灌資料和讀取和部分cache命中場景中使用的指令一緻。隻不過在讀取的同時,在另外一台用戶端上起了一個20個線程的批量寫測試,去寫另外一個Table

hbase pe --nomapred --oneCon=true --valueSize=200 --table=WriteTable  --compress=SNAPPY --blockEncoding=DIFF  --rows=600000000 --autoFlush=true --presplit=64 --multiPut=100 randomWrite 20           
3945 ​11ms ​8700ms ​9000ms
12028 ​2ms ​45ms ​100ms 250ms

注:表中的QPS指的是讀的吞吐

Full GC time(concurrent mode failure)
​ 80ms 0.7次/s ​/(絕大部分退化成full gc) ​0.08次/s 約7~9s
​40ms 2.1次/s ​/ ​幾乎為0

在讀寫混合測試中,在此壓力下,CMS GC的速度已經跟不上heap中産生的垃圾的速度。是以在發生CMS時,由于CMS還沒完成時old區已經滿(concurrent mode failure),是以CMS GC都退化成了Full GC,進而産生了7到9s的‘stop the world’停頓。是以,1.x中P999被這樣的Full GC影響,P999已經上升到了8700ms。而由于HBase2.x使用了讀鍊路offheap。在此場景中仍然穩如泰山,CMS GC發生的頻率幾乎為0。是以在讀寫混合場景中,HBase2.x的吞吐是HBase1.x的4倍,P999延遲仍然保持在了100ms之内!

總結

通過上面的測試,我們發現HBase2.x的全鍊路offheap功能确實能夠降低GC停頓時間,在各個場景中,都顯示出了非常顯著的效果。特别是在部分cache命中和讀寫混合這兩個通常在生産環境中遇到的場景,可謂是效果拔群。是以說HBase2.x中的全鍊路offheap是我們在生産環境中去降低毛刺,增加吞吐的利器。

雲端使用

HBase2.0版本目前已經在阿裡雲提供商業化服務,任何有需求的使用者都可以在阿裡雲端使用深入改進的、一站式的HBase服務。雲HBase版本與自建HBase相比在運維、可靠性、性能、穩定性、安全、成本等方面均有很多的改進,歡迎大家通過下面的連接配接申請使用阿裡雲HBase2.0版本,使用全鍊路offheap這個利器去給生産服務帶來更好的穩定性和服務品質。

https://www.aliyun.com/product/hbase