0%

HBase 读取性能优化

HBase 读取性能优化


前言

本文主要借阅于《HBase原理与实践》这本书,可以自行查阅 13.4 和 13.5 章

读请求延迟较大通常存在三种场景,分别为:

  1. 仅有某业务延迟较大,集群其他业务都正常

  2. 整个集群所有业务都反映延迟较大

  3. 某个业务起来之后集群其他部分业务延迟较大

这三种场景是表象,通常某业务反应延迟异常,首先需要明确具体是哪种场景,然后针对性解决问题。

主要分为四个方面: 客户端优化、服务器端优化、列族设计优化以及 HDFS 相关优化

客户端优化

客户端作为业务读写的入口,姿势使用不正确通常会导致本业务读延迟较高实际上存在一些使用姿势的推荐用法

scan 缓存是否设置合理?

优化原理: HBase业务通常一次 scan 就会返回大量数据,因此客户端发起一次 scan 请求,实际并不会一次就将所有数据加载到本地,而是分成多次 RPC 请求进行加载,这样设计一方面因为大量数据请求可能会导致网络带宽严重消耗进而影响其他业务,另一方面因为数据量太大可能导致本地客户端发生OOM。在这样的设计体系下,用户会首先加载一部分数据到本地,然后遍历处理,再加载下一部分数据到本地处理,如此往复,直至所有数据都加载完成。数据加载到本地就存放在scan缓存中,默认为100条数据。

通常情况下,默认的scan缓存设置是可以正常工作的。但是对于一些大scan(一次scan可能需要查询几万甚至几十万行数据),每次请求100条数据意味着一次scan需要几百甚至几千次RPC请求,这种交互的代价无疑是很大的。因此可以考虑将scan缓存设置增大,比如设为500或者1000条可能更加合适。《HBase原理与实践》作者之前做过一次试验,在一次scan 10w+条数据量的条件下,将scan缓存从100增加到1000条,可以有效降低scan请求的总体延迟,延迟降低了25%左右。

优化建议: 大scan场景下将scan缓存从100增大到500或者1000,用以减少RPC次数。

get 是否使用批量请求?

优化原理: HBase分别提供了单条get以及批量get的API接口,使用批量get接口可以减少客户端到RegionServer之间的RPC连接数,提高读取吞吐量。另外需要注意的是,批量get请求要么成功返回所有请求数据,要么抛出异常。

优化建议: 使用批量get进行读取请求。需要注意的是,对读取延迟非常敏感的业务,批量请求时每次批量数不能太大,最好进行测试。

请求是否可以显式指定列簇或者列?

优化原理: HBase是典型的列簇数据库,意味着同一列簇的数据存储在一起,不同列簇的数据分开存储在不同的目录下。一个表有多个列簇,如果只是根据rowkey而不指定列簇进行检索,不同列簇的数据需要独立进行检索,性能必然会比指定列簇的查询差很多,很多情况下甚至会有2~3倍的性能损失。

优化建议:尽量指定列簇或者列进行精确查找。

离线批量读取请求是否设置禁止缓存?

优化原理: 通常在离线批量读取数据时会进行一次性全表扫描,一方面数据量很大,另一方面请求只会执行一次。这种场景下如果使用scan默认设置,就会将数据从HDFS加载出来放到缓存。可想而知,大量数据进入缓存必将其他实时业务热点数据挤出,其他业务不得不从HDFS加载,进而造成明显的读延迟毛刺。

优化建议: 离线批量读取请求设置禁用缓存,scan.setCacheBlocks (false)。

服务端优化

一般服务端端问题一旦导致业务读请求延迟较大的话,通常是集群级别的,即整个集群的业务都会反映读延迟较大。

读请求是否均衡?

优化原理: 假如业务所有读请求都落在集群某一台RegionServer上的某几个Region上,很显然,这一方面不能发挥整个集群的并发处理能力,另一方面势必造成此台 RegionServer 资源严重消耗(比如IO耗尽、handler耗尽等),导致落在该台 RegionServer 上的其他业务受到波及。也就是说读请求不均衡不仅会造成本身业务性能很差,还会严重影响其他业务。

观察确认: 观察所有RegionServer的读请求QPS曲线,确认是否存在读请求不均衡现象。

优化建议: Rowkey必须进行散列化处理(比如MD5散列),同时建表必须进行预分区处理。

BlockCache设置是否合理?

优化原理: BlockCache作为读缓存,对于读性能至关重要。默认情况下BlockCache和MemStore的配置相对比较均衡(各占40%),可以根据集群业务进行修正,比如读多写少业务可以将BlockCache占比调大。另一方面,BlockCache的策略选择也很重要,不同策略对读性能来说影响并不是很大,但是对GC的影响却相当显著,尤其在BucketCache的offheap模式下GC表现非常优秀。

观察确认: 观察所有 RegionServer 的缓存未命中率、配置文件相关配置项以及GC日志,确认 BlockCache 是否可以优化。

优化建议: 如果JVM内存配置量小于20G,BlockCache策略选择LRUBlockCache;否则选择BucketCache策略的 offheap 模式。

HFile文件是否太多?

优化原理: HBase在读取数据时通常先到MemStore和BlockCache中检索(读取最近写入数据和热点数据),如果查找不到则到文件中检索。HBase的类LSM树结构导致每个store包含多个HFile文件,文件越多,检索所需的IO次数越多,读取延迟也就越高。文件数量通常取决于Compaction的执行策略,一般和两个配置参数有关:hbase.hstore. compactionThreshold和hbase.hstore.compaction.max.size,前者表示一个store中的文件数超过阈值就应该进行合并,后者表示参与合并的文件大小最大是多少,超过此大小的文件不能参与合并。这两个参数需要谨慎设置,如果前者设置太大,后者设置太小,就会导致Compaction合并文件的实际效果不明显,很多文件得不到合并,进而导致HFile文件数变多。

观察确认: 观察RegionServer级别以及Region级别的HFile数,确认HFile文件是否过多。

优化建议: hbase.hstore.compactionThreshold设置不能太大,默认为3个。

Compaction是否消耗系统资源过多?

优化原理: Compaction是将小文件合并为大文件,提高后续业务随机读性能,但是也会带来IO放大以及带宽消耗问题(数据远程读取以及三副本写入都会消耗系统带宽)。正常配置情况下,Minor Compaction并不会带来很大的系统资源消耗,除非因为配置不合理导致MinorCompaction太过频繁,或者Region设置太大发生Major Compaction。

观察确认: 观察系统IO资源以及带宽资源使用情况,再观察Compaction队列长度,确认是否由于Compaction导致系统资源消耗过多。

优化建议: 对于大Region读延迟敏感的业务(100G以上)通常不建议开启自动MajorCompaction,手动低峰期触发。小Region或者延迟不敏感的业务可以开启MajorCompaction,但建议限制流量。

列簇设计优化

布隆过滤器是否设置?

优化原理: 布隆过滤器主要用来过滤不存在待检索rowkey的HFile文件,避免无用的IO操作。

布隆过滤器取值有两个——row以及rowcol,需要根据业务来确定具体使用哪种。如果业务中大多数随机查询仅仅使用row作为查询条件,布隆过滤器一定要设置为row;如果大多数随机查询使用row+column作为查询条件,布隆过滤器需要设置为rowcol。如果不确定业务查询类型,则设置为row。

优化建议: 任何业务都应该设置布隆过滤器,通常设置为row,除非确认业务随机查询类型为row+column,则设置为rowcol。默认为 row

TTL 是否设置合理?

优化原理: TTL(Time to Live) 用于限定数据的超时时间,HBase cell 超过时间后会被自动删除,对某些数据不是永久保存,并大量写入的场景下非常适用,减少数据规模

优化建议: CF 默认的 TTL 值是 FOREVER,也就是永不过期,可以根据具体的业务场景设置超时时间

HDFS 相关优化

数据本地率是不是很低?

优化原理: 如果数据本地率很低,数据读取时会产生大量网络IO请求,导致读延迟较高。

观察确认: 观察所有RegionServer的数据本地率(见jmx中指标PercentFileLocal,在TableWeb UI可以看到各个Region的Locality)。

优化建议: 尽量避免Region无故迁移。对于本地率较低的节点,可以在业务低峰期执行major_compact。

执行major_compact提升数据本地率的理论依据是,major_compact本质上是将Region中的所有文件读取出来然后写到一个大文件,写大文件必然会在本地DataNode生成一个副本,这样Region的数据本地率就会提升到100%。

Short-Circuit Local Read功能是否开启?

优化原理: 当前HDFS读取数据都需要经过 DataNode,客户端会向DataNode发送读取数据的请求,DataNode接受到请求之后从硬盘中将文件读出来,再通过TCP发送给客户端。Short Circuit策略允许客户端绕过DataNode直接读取本地数据。

优化建议: 开启Short Circuit Local Read 功能,需要在hbase-site.xml或者hdfs-site.xml配置文件中增加如下配置项

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<configuration>
<property>
<name>dfs.client.read.shortcircuit</name>
<value>true</value>
</property>
<property>
<name>dfs.domain.socket.path</name>
<value>/var/lib/hadoop-hdfs/dn_socket</value>
</property>
<property>
<name>dfs.client.read.shortcircuit.buffer.size</name>
<value>131072</value>
</property>
</configuration>

需要注意的是,dfs.client.read.shortcircuit.buffer.size参数默认是1M,对于HBase系统来说有可能会造成OOM,详见HBASE-8143 HBase on Hadoop 2 with local short circuit reads(ssr) causes OOM

Hedged Read功能是否开启?

优化原理: HBase数据在HDFS中默认存储三个副本,通常情况下HBase会根据一定算法优先选择一个DataNode进行数据读取。然而在某些情况下,有可能因为磁盘问题或者网络问题等引起读取超时,根据Hedged Read策略,如果在指定时间内读取请求没有返回,HDFS客户端将会向第二个副本发送第二次数据请求,并且谁先返回就使用谁,之后返回的将会被丢弃。

优化建议: 开启Hedged Read功能,需要在hbase-site.xml配置文件中增加如下配置项

1
2
3
4
5
6
7
8
9
10
<configuration>
<property>
<name>dfs.client.hedged.read.threadpool.size</name>
<value>20</value><!-- 20 threads -->
</property>
<property>
<name>dfs.client.hedged.read.threshold.millis</name>
<value>10</value><!-- 10 milliseconds -->
</property>
</configuration>

参数dfs.client.hedged.read.threadpool.size表示用于hedged read的线程池线程数量,默认为0,表示关闭hedged read功能;参数dfs.client.hedged.read.threshold.millis表示HDFS数据读取超时时间,超过这个阈值,HDFS客户端将会再发起一次读取请求。

读性能优化归纳

提到读延迟较大无非三种常见的表象,单个业务慢、集群随机读慢以及某个业务随机读之后其他业务受到影响导致随机读延迟很大。

了解完常见的可能导致读延迟较大的一些问题之后,我们将这些问题进行如下归类,读者可以在看到现象之后在对应的问题列表中进行具体定位


参考链接