HBase代码优化
Compression压缩
- 数据量大,边压边写也会提升性能的,毕竟IO是大数据的最严重的的瓶颈,哪怕使用了SSD也是一样。众多的压缩方式中,推荐使用SNAPPY。从压缩率和压缩速度来看,性价比最高
HColumnDescriptor hcd = new HColumnDescriptor(familyName);
hcd.setCompressionType(Algorithm.SNAPPY);
HBase数据表优化
1.预分区
- 默认情况下,在创建HBase表的时候会自动创建一个region分区,写入数据时所有数据都向这一个region写数据,直到这个region足够大了才进行切分。而预分区可以通过先创建很多空region的方式加快批量写入的速度,这样当数据写入HBase时会按照region分区情况,在集群内做数据的负载均衡
2.RowKey优化
- rowkey是按照字典存储,因为设置rowkey时,要充分利用排序特点,将经常一起读取的数据存储到一块,将最近可能会被访问的数据放到一块。
- rowkey若是递增生成的,建议不要使用正序直接写入,可以使用字符串反转方式写入,使得rowkey大致均衡分布,这样设计的好处是能将RegionServer的负载均衡,否则容易产生所有新数据都集中在一个RegionServer上堆积的现象。
- rowkey设计原则
1.长度
1)rowkey最大长度64kb,越短越好,尽量不要超过16个字节
2)rowkey过长,内存利用率会降低,系统不能缓存太多数据
3)机器都是64位,内存以8个字节对齐,所以控制在8个字节的整数可以获得最佳性能
2.分散:建议rowkey的设置散列字段,程序固定生成
3.唯一性:rowkey要求独一无二
3.减少Column Family数量
- 把所有列的数据放在一个文件里
- 传统行存储的做法
- 如果只希望访问个别的几列数据时,需要遍历每一行,效率低
- 把所有列的数据分开放到不同文件里
- 列存储
- 文件数量很多,影响文件系统效率
- 需要以上两个方面取折中:HBase将CF中的列放到一起,不同CF的数据分开存(一些经常访问的CQ可以放在一个CF里)
- 不要在一张表中定义太多的CF。目前HBase并不能很好的处理超过2-3个CF的表,因为当某个CF的数据flush的时候,其他CF也会关联被触发flush。如果CF设计比较多,一旦出现连锁反应,会导致系统产生很大的IO,影响性能。
- flush和region合并的时候,触发的基本单位都是region,如果memStore里面通常存储少量的数据的时候,没有必要flush
4.设置最大版本数
- 创建表的时候,可以通过
HColumnDescriptor.setMaxVersions(int maxVersions)
设置表中数据的最大版本,如果只需要保存最新版本的数据,那么可以设置setMaxVersions(1)
5.缓存策略(setCaching)
- 创建表的时候,可以通过
HColumnDEscriptor.setInMemory(true)
将表放到RegionServer的缓存中,保证在读取的时候被cache命中
6.设置存储生命周期
- 创建表的时候,可以通过
HColumnDescriptor.setTimeToLive(int timeToLive)
设置表中数据的存储生命周期,过期数据将自动被删除
写表操作
1.多个HTable并发写
- 创建多个HTable客户端用于写操作,提高写数据的吞吐量
2.HTable参数设置
- Auto Flash
该方法可以将HTable写客户端自动flush关闭,这样可以批量写入数据到HBase,而不是有一条put就执行一次更新,只有当put填满客户端写缓存的时候,才会向HBase服务端发起写请求。HTable.setAutoFlushTo(false)
每个HBase客户端默认的写缓冲区大小是2M,可以通过以下方法配置写缓冲区的大小。 - Write Buffer
通过调用
方法可以设置HTable客户端的写buffer大小,如果新设置的buffer小于当前写buffer中的数据时,buffer将会被flush到服务端。其中,writeBufferSize的单位是byte字节数,可以根据实际写入数据量的多少来设置该值HTable.setWriteBufferSize(long writeBufferSize)
- WAL Flag
在HBase中,客户端向集群中的RegionServer提交数据时(Put/Delete操作),首先会写到WAL(Write Ahead Log)日志,即HLog,一个RegionServer上的所有Region共享一个HLog,只有当WAL日志写成功后,再接着写memStore,然后客户端被通知提交数据成功。如果写WAL日志失败,客户端被告知提交失败,这样做的好处是可以做到RegionServer宕机后的数据恢复
对于不太重要的数据,可以在Put/Delete操作时,通过调用Put.setWriteToWAL(false) Delete.setWriteToWAL(false)
放弃写WAL日志,以提高数据写入的性能
3.多线程并发写
在客户端开启多个HTable写线程,每个写线程复制一个HTable对象的flush操作,这样结合定时flush和写buffer(writeBufferSize),可以既保证在数据量小的时候,数据可以在较短时间内被flush(如1秒内),同时又保证在数据量大的时候,写buffer一满就及时进行flush。
4.批量写
通过调用
HTable.put(Put)
可以将一个指定的rowkey记录写入HBase,同样HBase提供了另一个方法,通过调用
HTable.put(List)
可以将指定的rowkey列表,批量写入多行记录,这样做的好处是批量执行,只需要一次网络I/O开销,这对于数据实时性要求高的场景下带来明显的性能提升
读表操作
多个HTable并发度
创建多个HTable客户端用于读操作,提高读数据的吞吐量
HTable参数设置
- Scanner Caching
通过使用
可以设置HBase scanner一次从服务器抓取的数据条数;默认情况下一次一条,通过将此值设置成一个合理的值,可以减少scan过程中next()的时间开销,代价是scanner需要通过客户端的内存来维持这些被cache的行记录HTable.setScannerCaching(int scannerCaching)
- Scan Attribute Selection
scan时指定需要的Column family,可以减少网络传输数据量,否则默认scan操作会返回整行所有CF的数据 - Close ResultScanner
通过scan取完数据后,记得要关闭ResultScanner,否则RegionServer可能会出现问题(对应的server资源无法释放)
批量读
通过调用
HTable.get(Get)
可以根据一个指定的row key获取一行记录,同样HBase提供了另一个方法:通过调用
HTable.get(List)
方法可以根据一个指定的rowkey列表,批量获取多行记录,这样做的好处是批量执行,只需要一次网络I/O开销,这对于对数据实时性要求高可能带来明显的性能提升
多线程并发度
在客户端开启多个HTable读线程,每个读线程负责通过HTable对象进行get操作
缓存查询结果
对于频繁查询HBase的应用场景,可以考虑在应用程序中缓存,当有新的查询请求时,首先在缓存中查找,如果存在则直接返回,不再查询HBase;否则对HBase发起读请求查询,然后在应用程序中将查询结果缓存起来。至于缓存的替换策略,可以考虑LRU等常用的策略
BlockCache
HBase的RegionServer的内存分为两部分,一部分作为memStore,主要用来写;另一部分作为BlockCache,主要用于读。
写请求会先写入memStore。RegionServer会给每个region提供一个memStore,当memStore满64M以后,会启动flush刷新到磁盘。当memStore的总大小超过限制时
(heapsize * hbase.regionserver.global.memstore.upperlimit * 0.9),会强行启动flush进程,从最大的memStore开始flush直到低于限制。
读请求先到BlockCache中查,查不到就去memStore中查,再查不到就会到磁盘上读,并把读的结果放入BlockCache。由于BlockCache采用的是LRU策略,由此BlockCache达到上限(heapsize * hfile.block.cache.size * 0.85)后,会启动淘汰机制,淘汰掉最老的一批数据。
一个RegionServer上有一个BlockCache和N个memStore,它们的大小之和不能大于等于heapsize * 0.8,否则HBase不能启动。默认BlockCache为0.2,而memStore为0.4。对于注重读响应时间的系统,可以将BlockCache设大些,以加大缓存的命中率