消灭毛刺!HBase2.0全链路offheap效果拔群
Posted HBase技术社区
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了消灭毛刺!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包括以下几个优化:
在RPC层直接把网络流上的KeyValue读入offheap的bytebuffer中
使用offheap的MSLAB pool
使用支持offheap的Protobuf版本(3.0+)
读链路的offheap主要包括以下几个优化:
对BucketCache引用计数,避免读取时的拷贝
使用ByteBuffer做为服务端KeyValue的实现,从而使KeyValue可以存储在offheap的内存中
对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 | 2ms | 45ms | 140ms |
AliHB-2.0.1 | 40371 | 0.7ms | 1ms | 2ms | 5ms | 140ms |
版本 | AVG younggc Time | younggc GC频率 |
---|---|---|
AliHB-1.4.9 | 90ms | 0.6次/s |
AliHB-2.0.1 | 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
测试的场景和参数配置与单行写保持一致
版本 | TPS | AVG RT | 95% RT | 99% RT | 99.9% RT | MAX RT |
---|---|---|---|---|---|---|
AliHB-1.4.9 | 81477 | 72ms | 110ms | 220ms | 350ms | 420ms |
AliHB-2.0.1 | 97985 | 67ms | 75ms | 220ms | 280ms | 300ms |
版本 | AVG younggc Time | younggc GC频率 |
---|---|---|
AliHB-1.4.9 | 120ms | 0.6次/s |
AliHB-2.0.1 | 180ms | 0.28次/s |
可以看到,使用了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 | AVG RT | 95% RT | 99% RT | 99.9% RT | MAX RT | |
---|---|---|---|---|---|---|---|
AliHB-1.4.9 | 53895 | 0.04ms | 1ms | 1ms | 1ms | 30ms | |
AliHB-2.0.1 | 49518 | 0.05ms | 0ms | 1ms | 1ms | 14ms |
注:百分比的延迟统计最低分辨率是1ms,所以低于1ms时会显示为0
版本 | AVG younggc Time | younggc GC频率 |
---|---|---|
AliHB-1.4.9 | 25ms | 0.4次/s |
AliHB-2.0.1 | 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
再保证所有数据刷盘,major compact成一个文件后,先做cache的预热,然后使用如下命令进行单行读取:
hbase pe --nomapred --oneCon=true --rows=600000 randomRead 60
版本 | QPS | AVG RT | 95% RT | 99% RT | 99.9% RT | MAX RT |
---|---|---|---|---|---|---|
AliHB-1.4.9 | 14944 | 2.5ms | 5ms | 80ms | 200ms | 300ms |
AliHB-2.0.1 | 15372 | 1.7ms | 5ms | 34ms | 65ms | 130ms |
版本 | AVG younggc Time | younggc GC频率 | CMS GC Remark AVG Time | CMS GC 频率 |
---|---|---|---|---|
AliHB-1.4.9 | 80ms | 2.4次/s | 50ms | 0.25次/s |
AliHB-2.0.1 | 21ms | 2.5次/s | 0 | 0 |
在部分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
版本 | QPS | AVG RT | 95% RT | 99% RT | 99.9% RT | MAX RT | |
---|---|---|---|---|---|---|---|
AliHB-1.4.9 | 3945 | 11ms | 5ms | 180ms | 8700ms | 9000ms | |
AliHB-2.0.1 | 12028 | 2ms | 5ms | 45ms | 100ms | 250ms |
注:表中的QPS指的是读的吞吐
版本 | AVG younggc Time | younggc GC频率 | CMS GC Remark AVG Time | CMS GC 频率 | Full GC time(concurrent mode failure) |
---|---|---|---|---|---|
AliHB-1.4.9 | 80ms | 0.7次/s | /(绝大部分退化成full gc) | 0.08次/s | 约7~9s |
AliHB-2.0.1 | 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
以上是关于消灭毛刺!HBase2.0全链路offheap效果拔群的主要内容,如果未能解决你的问题,请参考以下文章
HBase优化 | 从HBase offheap到Netty的内存管理