在Hbase2x 增删改查 scala版中,有介绍HBase1.2.x增删改查的api文档,但仅仅了解还是不够,在不同的读写业务场景中,必须做出适当优化,才能满足业务需求。本文首先讲解HBase缓存机制,并针对服务端(server)和客户端(client)进行调优说明。
一、HBase缓存机制
HBase由master和regionserver组成,master用来管理regionserver并进行负载均衡,regionserver用来管理当前节点的region并响应客户端读写请求。
1. 写缓存
regionserver包括N个memstore和一个blockcache,其中memstore用来写缓存,blockcache用来度缓存。regionserver给每个region都分配一个memstore,数据写入会先预先Log(WAL机制开启时),然后写入memstore,当达到memstore设定的阈值(hbase.hregion.memstore.flush.size),会触发flush操作溢写到storefile;或达到全局性溢写触发阈值(heapsize * hbase.regionserver.global.memstore.upperLimit),会强行启动flush进程,从最大memstore开始flush至storefile。当一个region内storefile超过设定阈值(hbase.hstore.compaction.min),则启动compact进程,把小的storefile合并为大的storefile。当region越来越大,达到阈值(hbase.hregion.max.filesize),则自动split。
2. 读缓存
读请求先到memstore中查数据,查不到就到blockcache中查,再查不到就会到磁盘上读,并把读的结果放入blockcache。由于blockcache是一个LRU,因此blockcache达到上限(heapsize * hfile.block.cache.size)后,会启动淘汰机制,淘汰掉最老的一批数据。
二、服务端调优
1. 参数配置
1.1 关闭自动split和compact
hbase.hregion.max.filesize 配置region大小,默认10G。region的大小与集群支持的总数据量有关系,如果总数据量小,则单个region太大,不利于并行的数据处理,如果集群需支持的总数据量比较大,region太小,则会导致region的个数过多,导致region的管理等成本过高。一般提供在线服务的hbase集群均会弃用hbase的自动split,转而自己管理split。
hbase.hstore.blockingStoreFiles HStore的storeFile的文件数大于配置值,则在flush memstore前先进行split或者compact,除非超过hbase.hstore.blockingWaitTime配置的时间,默认为7,可调大。
1.2 处理IO线程数
hbase.regionserver.handler.count 处理RPC的线程数量,默认值:10,根据并发可以设置成80-100-120
1.3 读缓存设置
hfile.block.cache.size 默认值0.25,regionserver的block cache的内存大小限制,在偏向读的业务中,可以适当调大该值,需要注意的是hbase.regionserver.global.memstore.upperLimit的值和hfile.block.cache.size的值之和必须小于0.8。
2. 建表配置
2.1 合理设置预分区及Rowkey
2.1.1 预分区设置
当一个region的数据达到一定量时,hbase会自动开始split,split的过程中会短暂下线region,可能导致请求超时。我们可以预先创建好一定数量的region 来避免hbase自动split情况的发生。
预先创建好多个分区后,应当将rowkey离散化,让数据尽可能均衡的分部在各个region上。可使用hbase内置的算法创建,在hbases shell 下执行:
create 'test_lee',{NAME=>'f',BLOCKSIZE=>'16384',BLOCKCACHE=>false}, {NUMREGIONS=>10,SPLITALGO=>'HexStringSplit'},OWNER=>'lee'
2.1.2 rowkey长度
建议是越短越好,不要超过16个字节。在HBase中,一个具体的值由存储该值的行键、对应的列(列族:列)以及该值的时间戳决定。HBase中索引是为了加速随即访问的速度,索引的创建是基于“行键+列族:列+时间戳+值”的,如果行键和列族的大小过大,甚至超过值本身的大小,纳闷将会增加索引的大小。并且在HBase中数据记录往往非常之多,重复的行键、列将不但使索引的大小过大,也将加重系统的负担。
2.1.3 rowkey离散化
rowkeyHash= org.apache.hadoop.hbase.util.MD5Hash.getMD5AsHex(Bytes.toBytes("rowkey"));
或者
rowkeyHash1= rowKeyHash.substring(0,8)+rowkey;
2.2 设置最大版本数、存储生命期和压缩格式
2.2.1 设置最大版本数
不推荐设置比较大的最大版本数,会导致storefile大,占用比较多的资源。
create 'test_lee',{NAME=>'f',BLOCKSIZE=>'16384',BLOCKCACHE=>false,VERSIONS=>5}, {NUMREGIONS=>10,SPLITALGO=>'HexStringSplit'},OWNER=>'lee'
2.2.2 设置生命周期
通过设置生命周期,过期数据将被自动清除。
{NAME=>'f',BLOCKSIZE=>'16384',BLOCKCACHE=>false,VERSIONS=>5,TTL=>86400}, {NUMREGIONS=>10,SPLITALGO=>'HexStringSplit'},OWNER=>'lee'
2.2.3 设置压缩格式
开启lzo或者snappy压缩,压缩会消耗一定的CPU,但是,磁盘IO和网络IO将获得极大的改善,大致可以压缩4~5倍。
{NAME=>'f',BLOCKSIZE=>'16384',BLOCKCACHE=>false,VERSIONS=>5,TTL=>86400,COMPRESSION=>'SNAPPY'}, {NUMREGIONS=>10,SPLITALGO=>'HexStringSplit'},OWNER=>'lee'
如果使用hbase api,则如下
def createTable(tableName: String, family: Array[String],liveTime:Int) {
val admin = connection.getAdmin
val name: TableName = TableName.valueOf(tableName)
val desc: HTableDescriptor = new HTableDescriptor(name)
for (f <- family) {
// 设置列压缩格式
val hColumnDescriptor=new HColumnDescriptor(f).setCompressionType(Algorithm.SNAPPY)
// 设置blockcache大小
hColumnDescriptor.setBlocksize(16384)
// 设置列簇的生命周期
hColumnDescriptor.setTimeToLive(liveTime)
// 设置最大版本数
hColumnDescriptor.setMaxVersions(5)
desc.addFamily(hColumnDescriptor)
}
if (!admin.tableExists(name)) {
admin.createTable(desc)
}
}
三、客户端调优
1. 自动flush设置
默认是开启的,可以关闭自动flush,批量提交。
hbase 1.x以前版本 api:
HTable.setAutoFlushTo(false)
hbase 1.x以后版本 api:
// 设置客户端写buffer大小,达到阈值则flush
val params = new BufferedMutatorParams(name).writeBufferSize(4*1024*1024)
2. WAL设置
当数据被写入时会默认先写入Write-ahead Log(WAL)。WAL中包含了所有已经写入Memstore但还未Flush到HFile的更改(edits)。在Memstore中数据还没有持久化,当RegionSever宕掉的时候,可以使用WAL恢复数据。
可以在服务端修改hbase-site.xml配置hbase.regionserver.hlog.enabled设为false即可关闭(全局表),但不建议。一般在客户端修改设置。
hbase 1.x以前版本 api:
HTable.setAutoFlushTo(false)
hbase 1.x以后版本 api:
put.setDurability(Durability.SKIP_WAL)
3. Compression压缩
客户端和服务端一样可以修改,hbase 1.x和hbase 2.x没有区别,如下。
table.addFamily(new HColumnDescriptor(CF_DEFAULT).setCompressionType(Algorithm.SNAPPY))
4. 批量读写
通过批量读写,hbase 1.x以前版本 api如下:
HTable.put(List<Put>)
HTable.get(List<Get>)
hbase 1.x以后版本 api如下:
Mutator.mutate(gets)
Mutator.mutate(puts)
5. 缓存查询
scan.setCacheBlocks(true)
scan.setCaching(500)
参考资料
https://blog.csdn.net/baymax_007/article/details/81606683