HBase读写性能优化
一个系统上线之后,开发和调优将会一直伴随在系统的整个生命周期中,HBase也不例外。下面我们要学习如何进行HBase读写性能调优,以获取最大的读写效率。
HBase写入优化
客户端优化
批量写
采用批量写,可以减少客户端到RegionServer之间的RPC的次数,提高写入性能。批量写请求要么全部成功返回,要么抛出异常。
HTable.put(List<Put>);
异步批量提交
如果业务可以接受异常情况下丢失少量数据,可以使用异步批量提交方式提交请求。
用户提交写请求之后,数据会先写入客户端缓存,并返回用户写入成功(此时数据并为提交到RegionServer),当客户端缓存达到阈值(默认2M,可通过hbase.client.write.buffer配置)时才会批量提交给RegionServer。需要注意的是,在某些情况下客户端异常的情况下缓存数据有可能丢失。
HTable.setWriteBufferSize(writeBufferSize); // 设置缓存大小
HTable.setAutoFlush(false);
多线程并发写
客户端开启多个HTable写线程,每个写线程负责一个HTable对象的flush操作,这样结合定时flush和写buffer,可以即保证在数据量小的时候,数据可以在较短时间内被flush,同时又保证在数据量大的时候,写buffer一满就即使进行flush。
使用BulkLoad写入
在HBase中数据都是以HFile形式保存在HDFS中的,当有大量数据需要写入到HBase的时候,可以采用BulkLoad方式完成。
使用MapReduce或者Spark直接生成HFile格式的数据文件,然后再通过RegionServer将HFile数据文件移动到相应的Region上去。
写数据表设计调优
COMPRESSION
配置数据的压缩算法,这里的压缩是HFile中block级别的压缩。对于可以压缩的数据,配置压缩算法可以有效减少磁盘的IO,从而达到提高性能的目的。但是并不是所有数据都可以进行有效压缩,如图片,因为图片一般是已经压缩后的数据,所以压缩效果有限。常用的压缩算法是SNAPPY,因为它有较好的压缩和解压速度和可以接受的压缩率。
IN_MEMORY
配置表的数据优先缓存在内存中,这样可以有效提升读取的性能。适合小表,而且需要频繁进行读取操作的。
预分区
在HBase中数据是分布在各个Region中的,每个Region都负责一个起始RowKey和结束Rowkey的范围,在向HBase中写数据的时候,会根据RowKey请求到对应的Region上,如果写请求都集中在某一个Region或某几个Region上的时候,性能肯定不如写请求均匀分布在各个Region上好。默认情况下,创建的HBase的只有一个Region分区,会随着数据量的变大,进行split,拆分成多个Region,最开始的性能肯定会很不好
建议在设计HBase的的时候,进行预分区,并设计一个良好的Rowkey生成规则(关于RowKey设计,可以参考《一篇文章带你快速搞懂HBase RowKey设计》),尽量将数据分散到各个Region上,那样在进行HBase的读写的时候,对性能会有很好的改善。
合理设置WAL存储级别
数据在写入HBase的时候,先写WAL,再写入缓存。通常情况下写缓存延迟很低,WAL机制一方面是为了确保数据即使写入缓存后数据丢失也可以通过WAL恢复,另一方面是为了集群之间的复制。默认WAL机制是开启的,并且使用的是同步机制写WAL。
如果业务不特别关心异常情况下部分数据的丢失,而更关心数据写入吞吐量,可考虑关闭WAL写,这样可以提升2~3倍数据写入的吞吐量。
如果业务不能接受不写WAL,但是可以接受WAL异步写入,这样可以带了1~2倍性能提升。
HBase中可以通过设置WAL的持久化等级决定是否开启WAL机制、以及HLog的落盘方式。
WAL的持久化等级分为如下四个等级:
- SKIP_WAL:只写缓存,不写HLog日志。这种方式因为只写内存,因此可以极大的提升写入性能,但是数据有丢失的风险。在实际应用过程中并不建议设置此等级,除非确认不要求数据的可靠性。
- ASYNC_WAL:异步将数据写入HLog日志中。
- SYNC_WAL:同步将数据写入日志文件中,需要注意的是数据只是被写入文件系统中,并没有真正落盘,默认。
- FSYNC_WAL:同步将数据写入日志文件并强制落盘。最严格的日志写入等级,可以保证数据不会丢失,但是性能相对比较差。
同样,除了在创建表的时候直接设置WAL存储级别,也可以通过客户端设置WAL持久化等级,代码:
put.setDurability(Durability.SYNC_WAL);
HBase读取优化
客户端优化
批量get请求
使用批量请求,可以减少RPC的次数,显著提高吞吐量。需要注意的是,批量get请求要么成功返回所有请求数据,要么抛出异常。
Result[] re= table.get(List<Get> gets);
合理设置scan缓存大小
一次scan可能会返回大量数据,但是实际客户端发起一次scan请求,并不会将所有数据一次性加载到本地,而是分成多次RPC请求进行加载,这样设计一方面是因为大量数据请求可能会导致网络带宽严重消耗进而影响其他业务,另一方面是有可能因为数据量太大导致客户端发生OOM。所以采用先加载一部分数据到本地,然后进行遍历,每次加载一部分数据,如此往复,直至所有数据加载完成。数据加载到本地就存放在scan缓存中,默认100。
增大scan的缓存,可以让客户端减少一次scan的RPC次数,从而从整体上提升数据读取的效率。
scan.setCaching(int caching); //大scan可以设置为1000
指定请求列族或者列名
HBase是列族数据库,同一列族的数据存储在一块,不同列族是分开存储的,如果一个表由多个列族,只是根据RowKey而不指定列族进行检索的话,不同列族的数据需要独立进行检索,性能必然会比指定列族的查询差的多。
此外指定请求的列的话,不需要将整个列族的所有列的数据返回,这样就减少了网路IO。
scan.addColumn();
设置只读Rowkey过滤器
在只需要Rowkey数据时,可以为Scan添加一个只读取Rowkey的filter(FirstKeyOnlyFilter或KeyOnlyFilter)。
关闭ResultScanner
在使用table.getScanner之后,记得关闭,否则它会和服务器端一直保持连接,资源无法释放,从而导致服务端的某些资源不可用。
scanner.close();
离线计算访问HBase建议禁用缓存
当离线访问HBase时,往往会对HBase表进行扫描,此时读取的数据没有必要存放在BlockCache中,否则会降低扫描的效率。
scan.setBlockCache(false);
建议在对HBase表进行扫描时禁用缓存。
对于频繁查询HBase的应用场景不需要禁用缓存,并且可以考虑在应用程序和HBase之间加一层缓存系统(如Redis),先查询缓存,缓存没有命中再去查询HBase。
读数据表设计调优
COMPRESSION
同写性能优化COMPRESSION部分。
BLOCKSIZE
配置HFile中block块的大小,不同的block大小,可以影响HBase读写数据的效率。越大的block块,配置压缩算法,压缩的效率就越好;但是由于HBase的读取数据时以block块为单位的,所以越大的block块,对于随机读的情况,性能可能会比较差,如果要提升写入的性能,一般扩大到128kb或者256kb,可以提升写数据的效率,也不会影响太大的随机读性能。
DATA_BLOCK_ENCODING
配置HFile中block块的编码方法。当一行数据中存在多个列时,一般可以配置为"FAST_DIFF",可以有效的节省数据存储的空间,从而提升性能。
BloomFilter
优化原理:BloomFilter主要用来过滤不存在待检索RowKey或者Row-Col的HFile文件,避免无用的IO操作。它会告诉你在这个HFile文件中是否可能存在待检索的KeyValue,如果不存在,就可以不用小号IO打开文件进行seek。通过设置BloomFilter可以提升读写的性能。
BloomFilter是一个列族级别的配置属性,如果列族设置了BloomFilter,那么HBase会在生成StoreFile时包含一份BloomFilter的结构的数据,称为MetaBlock(一旦写入就无法更新)。MetaBlock和DataBlock(真实的KeyValue数据)一起由LRUBlockCache维护,所以开启了BloomFilter会有一定的存储即内存cache开销。
HBase利用BloomFilter可以节省必须读磁盘过程,可以提高随机读(get)的性能,但是对于顺序读(scan)而言,设置BloomFilter是没有作用的(0.92版本以后,如果设置了BloomFilter为ROWCOL,对于执行了qualifier的scan有一定的优化)
BloomFilter取值有两个,ROW和ROWCOL,需要根据业务来确定具体使用哪种。
- 如果业务大多数随机查询仅仅使用row作为查询条件,BloomFilter一定要设置为ROW。
- 如果大多数随机查询使用row+col作为查询条件,BloomFilter需要设置为ROWCOL。
- 如果不确定业务查询类型,设置为ROW。‘
预分区
同写性能优化预分区部分。
HBase服务端调优
GC_OPTS
HBase是利用内存完成读写操作。提高HBase内存可以有效提高HBase性能。GC_OPTS主要需要调整HeapSize和NewSize的大小。调整HeapSize大小的时候,建议将Xms和Xmx设置成相同的值,这样可以避免JVM动态调整HeapSize大小的时候影响性能。调整NewSize大小的时候,建议把其设置为HeapSize大小的1/9。
当HBase集群规模越大,Region数量越多时,可以适当调大HMaster的GC_OPTS参数
RegionServer需要比HMaster更大的内存,在内存充足的情况下,HeapSize可以相对设置大一些。
HMaster的HeapSize为4G的时候,HBase集群可以支持100000个Region的规模。根据经验值,单个RegionServer的HeapSize不建议超过20GB。
- # HMaster、RegionServer GC_OPTS配置如下:
- HMaster: -Xms2G -Xmx2G -XX:NewSize=256M -XX:MaxNewSize=256M
- RegionServer: -Xms4G -Xmx4G -XX:NewSize=512M -XX:MaxNewSize=512M
RegionServer并发请求处理数量
hbase.regionserver.handler.count表示RegionServer在同一时刻能够并发处理多少请求。如果设置过高会导致激烈的线程竞争,如果设置过小,请求将会在RegionServer长时间等待,降低处理能力。应该根据资源情况,适当增加处理线程数。
建议根据CPU的使用情况,可以设置为100至300之间的值。
控制MemStore的大小
hbase.hregion.memstore.flush.size默认值128M,单位字节,一旦有MemStore超过该值将被flush,如果regionserver的jvm内存比较充足(16G以上),可以调整为256M。在内存足够put负载大情况下可以调整增大。
BlockCache优化
BlockCache作为读缓存,合理设置对于提高读性能非常重要。默认情况下,BlockCache和MemStore的配置各占40%,可以根据集群业务进行修正,比如读多写少业务可以将BlockCache占比调大。另外BlockCache的策略也很重要,不同策略对读性能来说影响并不大,但是对GC的影响 却很显著。
HBase缓存区大小,主要影响查询性能。根据查询模式以及查询记录分布情况来决定缓存区的大小。如果采用随机查询使得缓存区的命中率较低,可以适当降低缓存大小。
- hfile.block.cache.size,默认0.4,用来提高读性能
- hbase.regionserver.global.memstore.size,默认0.4,用来提高写性能
控制HFile个数
MemStore在flush之前,会进行StoreFile的文件数量校验(通过hbase.hstore.blockingStoreFiles参数配置),如果大于设定值,系统将会强制执行Compaction操作进行文件合并,在合并的过程中会阻塞MemStore的数据写入,等待其他线程将StoreFile进行合并。通常情况下发生在数据写入很快的情况下。
hbase.hstore.compactionThreshold表示启动Compaction的最低阈值,该值不能太大,否则会积累太多文件,一般建议设置为5~8左右。
hbase.hstore.blockingStoreFiles默认设置为7,可以适当调大一些。
Split优化
hbase.hregion.max.filesize表示HBase中Region的文件总大小的最大值。当Region中的文件大于该参数时,将会导致Region分裂。
- 如果该参数设置过小时,可能会导致Split操作频繁
- 如果该参数设置过大时,会导致Compaction操作需要处理的文件个数增大,影响Compaction执行效率
Compaction优化
hbase.hstore.compaction.min当一个Store中文件超过该值时,会进行Compaction,适当增大该值,可以减少文件被重复执行Compaction。但是如果过大,会导致Store中文件数过多而影响读取的性能。
hbase.hstore.compaction.max控制一次Compaction操作时的文件数据量的最大值。
hbase.hstore.compaction.max.size如果一个HFile文件的大小大于该值,那么在Minor Compaction操作中不会选择这个文件进行Compaction操作,除非进行Major Compaction操作。这个值可以防止较大的HFile参与Compaction操作。在禁止Major Compaction后,一个Store中可能存在几个HFile,而不会合并成为一个HFile,这样不会对数据读取造成太大的性能影响。
原则是:尽量要减小Compaction的次数和Compaction的执行时间
总结
在HBase使用过程中,要想获取好的读写性能,可以从以下几个方面进行优化:
- 一个优秀的HBase表列族设置方案,可以参考《带你快速上手HBase | HBase列族优化》
- 一个优秀的HBase RowKey设计方案,可以参考《一篇文章带你快速搞懂HBase RowKey设计》
- 读写时客户端相关配置
- HBase服务器优化