随着大数据的越来越普及,HBase 也变得越来越流行。使用 HBase 并不困难,但是如何用好 HBase,这确是一个难点。为了合理地使用 HBase,尽可能发挥 HBase 的功能,我们需要根据不同的场景对 HBase 进行不同地优化以最大程度上提升系统的性能。本文重点介绍列族设计有关的优化。我们先来了解下 HBase 列族具有哪些属性配置。
列族属性配置
版本数量(VERSIONS)
每个列族可以单独设置行版本数,默认是 3。这个设置很重要,因为 HBase 是不会去覆盖一个值的,它只会在后面追加写,用时间戳(版本号)来区分,过早的版本会在执行 Major Compaction
时删除。这个版本的值可以根据具体的场景来增加或减少。
不推荐将版本最大值设置成一个很高的水平,除非老数据对你也非常重要。过多的版本,会导致存储文件变大,以至于影响查询效率。
最小版本数(MIN_VERSIONS )
每个列族可以设置最小版本数,最小版本数缺省值是 0,表示禁用该特性。最小版本数参数和存活时间是一起使用的,允许配置 “如保存最后 T 秒有价值的数据,最多 N 个版本,但最少 M 个版本”(M 是最小版本,M<N)。该参数仅在存活时间对列族启用,且必须小于行版本数。
存活时间(TTL)
HBase 支持配置版本数据的存活时间(TTL),TTL 设置了一个基于时间戳的临界值,HBase 会自动检查 TTL 值是否达到上限,如果 TTL 达到上限,则该数据会在 Major Compaction 过程中被删除。
数据块大小(BLOCKSIZE )
hbase 默认的块大小是 64kb,不同于 HDFS 默认 64MB 的块大小。原因是 hbase 需要支持随机访问,一旦找到了行键所在的块,接下来就会定位对应的单元格。使用 64kb 的块扫描的速度显然优于 64MB 大小的块。
对于不同的业务数据,块大小的合理设置对读写性能有很大的影响。如果业务请求以 Get 请求为主,可以考虑将块大小设置较小;如果以 Scan 请求为主,可以将块大小调大;默认的 64K 块大小是在 Scan 和 Get 之间取得的一个平衡。
注意:
默认块大小适用于多种数据使用模式,调整块大小是比较高级的操作。配置错误将对性能产生负面影响。因此建议在调整之后进行测试,根据测试结果决定是否可以线上使用。
块缓存(BLOCKCACHE)
默认是 true。缓存是内存存储,hbase 使用块缓存将最近使用的块加载到内存中。块缓存会根据最近最久未使用(LRU)” 的规则删除数据块。
如果你的使用场景是经常顺序访问或者很少被访问,可以关闭列族的缓存。列族缓存默认是打开的。
激进缓存的配置(IN_MEMORY)
HBase 可以选择一个列族赋予更高的优先级缓存,激进缓存(表示优先级更高),IN_MEMORY
默认是 false。如果设置为 true,hbase 会尝试将整个列族保存在内存中,只有在需要保存是才会持久化写入磁盘。但是在运行时 hbase 会尝试将整张表加载到内存里。
这个参数通常适合较小的列族。
压缩(COMPRESSION)
HBase 在写入数据块到 HDFS 之前会首先对数据进行压缩,再落盘,从而减少磁盘空间使用量。而在读数据的时候首先从 HDFS 中加载出 block 块之后进行解压缩,然后再缓存到 BlockCache,最后返回给用户。
使用压缩其实就是使用 CPU 资源换取磁盘空间资源。
HBase 支持三种压缩方式:LZO、Snappy 和 GZIP。
默认为 NONE,不适用压缩,
压缩算法 | 压缩比率 | 压缩速度 | 解压速度 |
---|---|---|---|
GZIP | 13.4% | 21 MB/s | 118 MB/s |
LZO | 20.5% | 135 MB/s | 410 MB/s |
Snappy | 22.2% | 172 MB/s | 409 MB/s |
其中:
-
GZIP 的压缩率最高,但是其实 CPU 密集型的,对 CPU 的消耗比其他算法要多,压缩和解压速度也慢;
-
LZO 的压缩率居中,比 GZIP 要低一些,但是压缩和解压速度明显要比 GZIP 快很多,其中解压速度快的更多;
-
Snappy 的压缩率最低,而压缩和解压速度要稍微比 LZO 要快一些。
综合来看,Snappy 的压缩率最低,但是编解码速率最高,对 CPU 的消耗也最小,目前一般建议使用 Snappy。
复制范围(REPLICATION_SCOPE )
HBase 提供了跨级群同步的功能,本地集群的数据更新可以及时同步到其他集群。复制范围(replication scope)的参数默认为 0,表示复制功能处于关闭状态。
预分区(SPLITS )
在默认情况下,HBase 表在刚刚被创建的时候,只有 1 个分区(Region),当一个 Region 的大小达到阈值(通过 hbase.hregion.max.filesize
参数控制),Region 会进行 split,分裂成 2 个 Region。但是在进行 split 的时候,会消耗大量的资源,频繁的 split 会对 HBase 的性能造成巨大的影响。
HBase 提供了预分区的功能,用户可以在创建表的时候对表按照一定的规则提前进行分区。这样是进行 HBase 数据读写的时候,会按照 Region 分区情况,在集群内做数据的负载均衡。
常用分区方法:
create 'table','cf', SPLITS => ['1', '2', '3', '4', '5', '6', '7', '8', '9']
create 'table','cf', { NUMREGIONS => 8 , SPLITALGO => 'UniformSplit' }
create 'table','cf', { NUMREGIONS => 10, SPLITALGO => 'HexStringSplit' }
BLOOMFILTER
BloomFilter 主要用来过滤不存在待检索 RowKey 或者 Row-Col 的 HFile 文件,避免无用的 IO 操作。它会告诉你在这个 HFile 文件中是否可能存在待检索的 KV,如果不存在,就可以不用消耗 IO 打开文件进行 seek。通过设置 BloomFilter 可以提升随机读写的性能。
BloomFilter 是一个列族级别的配置属性,如果在表中设置了 BloomFilter,那么 HBase 会在生成 StoreFile 时包含一份 BloomFilter 结构的数据,称其为 MetaBlock
和 DataBlock
(真实 KeyValue 数据) 一起由 LRUBlockCache 维护。所以开启 BloomFilter 会有一定的存储即内存 Cache 的开销。
BloomFilter 取值有两个,row
和 rowcol
,需要根据业务来确定具体使用哪种。
-
如果业务大多数随机查询时仅仅使用 row 作为查询条件,BloomFilter 一定要设置为 row;
-
如果大多数随机查询使用 row+cf 作为查询条件,BloomFilter 需要设置为 rowcol;
-
如果不确定查询类型,建议设置为 row。
列族设置
列族数量
不要在一张表中定义太多的列族。目前 HBase 并不能很好的处理 2~3 以上的列族,flush
和 compaction
操作是针对一个 Region 的。
当一个列族操作大量数据的时候会引发一个 flush,它邻近的列族也会因关联效应被触发 flush,尽管它没有操作多少数据。compaction 操作是根据一个列族下的全部文件的数量触发的,而不是根据文件大小触发的。
当很多的列族在 flush 和 compaction 时,会造成很多没用的 IO 负载。
尽量在模式中只针对一个列族进行操作。将使用率相近的列归为一个列族,这样每次访问就只用访问一个列族,既能提升查询效率,也能保持尽可能少的访问不同的磁盘文件。
列族的基数
如果一个表存在多个列族,要注意列族之间基数(如行数)相差不要太大。例如列族 A 有 100 万行,列族 B 有 10 亿行,按照 RowKey 切分后,列族 A 可能被分散到很多很多 Region(及 RegionServer),这导致扫描列族 A 十分低效。
列族名、列名长度
列族名和列名越短越好,冗长的名字虽然可读性好,但是更短的名字在 HBase 中更好。
一个具体的值由存储该值的行键、对应的列(列族:列
)以及该值的时间戳决定。HBase 中索引是为了加速随机访问的速度,索引的创建是基于 “行键+列族:列+时间戳+值
” 的,如果行键和列族的大小过大,甚至超过值本身的大小,那么将会增加索引的大小。并且在 HBase 中数据记录往往非常之多,重复的行键、列将不但使索引的大小过大,也将加重系统的负担
总结
根据 HBase 列族的这些属性配置,结合我们的使用场景,HBase 列族可以进行如下优化:
-
列族不宜过多,将相关性很强的 key-value 都放在同一个列族下,;
-
尽量最小化行键和列族的大小;
-
提前预估数据量,再根据 Rowkey 规则,提前规划好 Region 分区,在创建表的时候进行预分区;
-
在业务上没有特别要求的情况下,只使用一个版本,即最大版本和最小版本一样,均为 1;
-
根据业务需求合理设置好失效时间(存储的时间越短越好);
-
根据查询条件,设置合理的 BloomFilter 配置;
-
合理设计 RowKey,可以参考《一篇文章带你快速搞懂 HBase RowKey 设计》。