HBase性能优化-Rowkey&列族设计
RowKey设计
长度原则
RowKey是一个二进制码流,可以是任意字符串,最大长度为64kb,实际应用中一般为10-100byte,以byte[]形式保存,一般设计成定长。建议越短越好,不要超过16个字节,原因如下:
- 数据的持久化文件HFile中时按照Key-Value存储的,如果RowKey过长,例如超过100byte,那么1000w行的记录,仅RowKey就需占用近1GB的空间。这样会极大影响HFile的存储效率。
- MemStore会缓存部分数据到内存中,若RowKey字段过长,内存的有效利用率就会降低,就不能缓存更多的数据,从而降低检索效率。
- 目前操作系统都是64位系统,内存8字节对齐,控制在16字节,8字节的整数倍利用了操作系统的最佳特性。
唯一原则
必须在设计上保证RowKey的唯一性。由于在HBase中数据存储是Key-Value形式,若向HBase中同一张表插入相同RowKey的数据,则原先存在的数据会被新的数据覆盖。
散列原则
设计的RowKey应均匀的分布在各个HBase节点上, 避免数据热点现象。
反转
如果经初步设计出的RowKey在数据分布上不均匀,但RowKey尾部的数据却呈现出了良好的随机性,此时,可以考虑将RowKey的信息翻转,或者直接将尾部的bytes提前到RowKey的开头。Reversing可以有效的使RowKey随机分布,但是牺牲了RowKey的有序性。
缺点:
利于Get操作,但不利于Scan操作,因为数据在原RowKey上的自然顺序已经被打乱。
加盐
加盐的原理是在原RowKey的前面添加固定长度的随机数,也就是给RowKey分配一个随机前缀使它和之间的RowKey的开头不同。随机数能保障数据在所有Regions间的负载均衡。
缺点:
因为添加的是随机数,基于原RowKey查询时无法知道随机数是什么,那样在查询的时候就需要去各个可能的Regions中查找,加盐对于读取是利空的。并且加盐这种方式增加了读写时的吞吐量。
哈希
基于 RowKey 的完整或部分数据进行 Hash,而后将Hashing后的值完整替换或部分替换原RowKey的前缀部分。这里说的 hash 包含 MD5、sha1、sha256 或 sha512 等算法。
缺点:
与 Reversing 类似,Hashing 也不利于 Scan,因为打乱了原RowKey的自然顺序。
列族设计
列族数尽量少,不要超过3个
-
列族数对Flush的影响
在 HBase 中,调用 API 往对应的表插入数据是会写到 MemStore 的,而 MemStore 是一种内存结构,每个列族对应一个 MemStore(和零个或多个 HFile)。如果我们的表有两个列族,那么相应的 Region 中存在两个 MemStore。
越多的列族,将会导致内存中存在越多的Memstore;而存储在Memstore中的数据在满足一定的条件将会进行Flush操作;每次Flush的时候,每个Memstore将在磁盘生成一个Hfile文件。
越多的列族最终持久化到磁盘的Hfile越多。当前Flush操作是Region级别的,也就是说,Region中某个Memstore被Flush,同一个Region的其他Memstore也会进行Flush操作。当表有很多列族,且列族之间数据不均匀,比如一个列族有100W行,一个列族只有10行,这样会导致持久化到磁盘的文件数很多,同时有很多小文件,而且每次Flush操作也涉及到一定的IO操作。
-
列族数对Split的影响
我们知道,当 HBase 表中某个 Region 过大(比如大于
hbase.hregion.max.filesize
配置的大小。当然,Region 分裂并不是说整个 Region 大小加起来大于hbase.hregion.max.filesize
就拆分,而是说 Region 中某个最大的 Store/HFile/storeFile 大于hbase.hregion.max.filesize
才会触发 Region 拆分的),会被拆分成两个。如果我们有很多个列族,而这些列族之间的数据量相差悬殊,比如有些列族有 100W 行,而有些列族只有10行,这样在 Region Split 的时候会导致原本数据量很小的 HFile 文件进一步被拆分,从而产生更多的小文件。注意,Region Split 是针对所有的列族进行的,这样做的目的是同一行的数据即使在 Split 后也是存在同一个 Region 的。 -
列族数对Compaction的影响
与 Flush 操作一样,目前 HBase 的 Compaction 操作也是 Region 级别的,过多的列族也会产生不必要的 IO。
-
列族数对HDFS的影响
HDFS 其实对一个目录下的文件数有限制的(
dfs.namenode.fs-limits.max-directory-items
)。如果我们有 N 个列族,M 个 Region,那么我们持久化到 HDFS 至少会产生 N*M 个文件;而每个列族对应底层的 HFile 文件往往不止一个,我们假设为 K 个,那么最终表在 HDFS 目录下的文件数将是 N*M*K,这可能会操作 HDFS 的限制。 -
列族数对RegionServer内存的影响
一个列族在 RegionServer 中对应于一个 MemStore。而 HBase 从 0.90.1 版本开始引入了 MSLAB(Memstore-Local Allocation Buffers,参考HBASE-3455),这个功能默认是开启的(通过
hbase.hregion.memstore.mslab.enabled
),这使得每个 MemStore 在内存占用了 2MB (通过hbase.hregion.memstore.mslab.chunksize
配置)的 buffer。如果我们有很多的列族,而且一般一个 RegionServer 上会存在很多个 Region,这么算起来光 MemStore 的缓存就会占用很多的内存。要注意的是,如果没有往 MemStore 里面写数据,那么 MemStore 的 MSLAB 是不占用空间的。
列族数太多的影响主要有两点:
-
多列簇导致持有数据量最少的列簇扫描性能下降,主要是因为数据太分散。
-
系统产生更多的 IO,耗费资源。
BlockSize 数据块大小
lockSize默认值 64K或 65536字节数据块大小,即每次读请求读取的最小数据大小将数据块调大可以提高扫描的性能,将数据块调小可以增加随机读的速度。
如果业务请求以Get请求为主,可以考虑将块大小设置较小;如果以Scan请求为主,可以将块大小调大。
BlockCache 数据块缓存
LRUBlockCache是HBase目前默认的BlockCache机制。
HBase在此基础上采用了分层设计,将整个BlockCache分成了三个部分,BlockCache包含三个级别的优先级队列:
Single: 如果一个Block被第一次访问,则放在这一级的队列中
Multi: 如果一个Block被多次访问,则从Single队列移动Multi队列
In Memory: 优先级最高,常驻cache,在创建列族时定义,不会像其他两种cache会因访问频率而发生改变,这就决定了它的独立性,另外两种block访问次数再多也不会被放到in-memory的区段里去,in-memory的block不管是第几次访问,总是被放置到in-memory的区段中,但是In Memory除了保证优先级外,不会提供其他的保证。
BlockCache数据块缓存默认是true。
把数据放进缓存,不一定能够提升性能。
如果在实际情况中,某些表或表中的列族不经常访问,或者只是进行有序扫表的话,数据块缓存可以关闭。
BloomFilter 布隆过滤器
BloomFilter是由Bloom在1970年提出的一种多哈希函数映射的快速查找算法,通常应用在一些需要快速判断某个元素是否属于该集合,但是并不严格要求100%正确的场合。
BloomFilter的数据存在StoreFile的meta中,一旦写入无法更新,因为StoreFile是不可变的。
BloomFilter是一个列族级别的配置属性,如果在表中设置了BloomFilter,那么HBase会在生成StoreFile时包含一份BloomFilter结构的数据,称其为MetaBlock;MetaBlock与DataBlock(真实的KeyValue数据)一起由LRU BlockCache维护。所以,开启BloomFilter会有一定的存储及内存cache开销。
BloomFilter包含三种参数:NONE(默认)、ROW、ROWCOL
-
ROW:表示行级布隆过滤器,根据KeyValue中的行来过滤StoreFile
-
ROWCOL:表示列级布隆过滤器,根据KeyValue中的行+列来过滤StoreFile
所以ROWCOL的空间开销,要高于ROW
region下的StoreFile数目越多,bloomfilter的效果越好
region下的StoreFile数目越少,HBase读性能越好
Compression 压缩
Hfile可以被压缩并存放到HDFS上,这样有助于节省磁盘IO,但是读写数据时压缩和解压缩会提高CPU的利用率。
压缩特性就是使用CPU资源换取磁盘空间资源,对读写性能并不会有太大影响。
默认值是NONE,即不开启压缩。
推荐打开表的压缩,除非压缩不适合业务场景,例如对图片,音频,视频进行压缩。
HBase目前提供了三种常用的压缩方式:GZip、LZO、Snappy。
Snappy的压缩率最低,但是编解码速率最高,对CPU的消耗也最小,所以目前一般建议使用Snappy。
Version 版本
用于设置列族所能记录的最多的版本数量,默认值是3,可以通过alter进行修改。
对于更新频繁的应用,建议设置为1,可以快速淘汰无用的数据,节省存储空间同时还能提升查询效率。
生存时间 TTL
用于设置单元格的生存周期,如果单元格过期,则会将其删除,单位是秒,默认值:FOR****EVER (永不过期)。
Data Block Encoding 数据块编码/解码
HBase提供了四种编码/解码类型:Prefix、Diff、Fast Diff、Prefix Tree。
可以根据具体使用情况选择合适的编/解码。
-
Prefix
适用于key有相同前缀,仅后几位有所不同。
如果使用Prefix编码,会产生一个额外的列,用于存储当前key与前一个key之间的相同前缀的长度
-
Diff
扩展了Prefix编码,与按顺序将key看作一个整体的字节序列不同,它将每个key字段被分割,以便更有效地压缩key的每个部分
如果使用Diff编码,会产生两个额外的列timestamp和type,用于存储时间戳和类型。Diff编码在默认情况下是禁用的,因为写入和扫描比较慢,但是缓存了更多的数据。
-
Fast Diff
Fast Diff的工作原理与Diff类似,但是使用了更快的实现
它还添加了另一个字段,该字段存储一个位来跟踪数据本身是否与前一行相同,如果是,则不再存储数据
如果设计的表,rowkey很长,并且有很多列,推荐使用Fast Diff格式