HBase Table 的每个列族都可以设置 VERSION,TTL、BLOOMFLTER 等很多属性;

create 'test:user', {NAME => 'b', VERSIONS => '3', TTL => '2147483647', 'BLOOMFILTER' => 'ROW'}

如果我们不设置,HBase 会填充默认值:

在这里插入图片描述

NAME => 'b', 						# 列族名
BLOOMFILTER => 'ROW', 			    # 布隆过滤器
VERSIONS => '3',  					# 版本数量
IN_MEMORY => 'false',  				# 激进缓存
KEEP_DELETED_CELLS => 'FALSE', 	    # 保留删除的单元格
DATA_BLOCK_ENCODING => 'NONE',  	# 数据块编码
TTL => 'FOREVER',  					# 存活时间
COMPRESSION => 'GZ',  				# 压缩
MIN_VERSIONS => '0',  				# 最小版本数
BLOCKCACHE => 'true', 				# 块缓存
BLOCKSIZE => '65536',  				# 数据块大小
REPLICATION_SCOPE => '0'  			# 复制范围

1.VERSIONS:版本数量

每个列族可以单独设置行版本数,默认是3。这个设置很重要,因为 HBase 是不会去覆盖一个值的,它只会在后面追加写,用时间戳(版本号)来区分,过早的版本会在执行 Major Compaction 时删除。这个版本的值可以根据具体的场景来增加或减少。

PS:0.96版本默认是3个, 0.98版本之后是1, 要根据业务来划分,版本是历史记录,版本增多意味空间消耗。

下面我们来看个 VERSIONS 的例子。

1.还是上面的 user 表(VERSIONS=3),我们在 user 表中添加三条数据

put 'test:user','1', 'b:name', 'zhangsan'
put 'test:user','1', 'b:name', 'wangwu'
put 'test:user','1', 'b:name', 'zhaoliu'

2.获取这三个版本的数据(注:这里要用 get,因为 scan 是获取多行,所以只会拿到最新版本的数据)

get 'test:user', '1', {COLUMN => 'b:name', VERSIONS => 3} # 注:建表时设置的 3,即使你这写个 4,也只能返回三行数据

在这里插入图片描述

可以看到,这 b:name 的这三个数据都出来了。

3.我们再新增/更新一条数据

put 'test:user', '1', 'b:name', 'Jack' # 这里既可以理解成新增,也可以理解成更新

在这里插入图片描述

可以看到,zhangsan 没了(被打上了删除标记)

4.删除掉 b:name=zhaoliu(时间戳是 1611678810121 )

delete 'test:user', '1', 'b:name', 1611678810121 # 注:如果不指定时间戳,删除的就是 Jack

在这里插入图片描述

可以看到,zhangsan 又回来了。

同样的,Hbase Delete 操作也不是真正删除了记录,而是放置了一个墓碑标记,过早的版本会在执行 Major Compaction 时真正删除。

PS:更改版本号:alter ‘test:user’, NAME => ‘b’, VERSIONS => 2

2.TTL:存活时间

TTL 全称是 Time To Live,ColumnFamilies 可以设置 TTL(单位是s),HBase 会自动检查 TTL 值是否达到上限,如果 TTL 达到上限后自动删除行。当然真正删除是在 Major Compaction 过程中执行的。

PS:数据过期时间,一般都设置成 2147483647(s),表示永久

3.MIN_VERSIONS:最小版本数

每个列族可以设置最小版本数,最小版本数缺省值是0,表示禁用该特性。只有 HBase 中的表设置了 TTL 的时候,MIN_VERSIONS 才会起作用。

  • MIN_VERSION = 0 时:Cell中的数据超过 TTL 时间时,全部清空,不保留最低版本。
  • MIN_VERSION > 0 时:Cell 至少有 MIN_VERSIONS 个最新版本会保留下来。这样确保在你的查询以及数据早于 TTL 时有结果返回。

4.BLOOMFILTER:布隆过滤器

布隆过滤器(Bloom Filter)核心实现是一个超大的位数组和几个哈希函数。如下图所示,有一个大小为 12 的数组,然后有3个哈希函数:

它的基本思想是:当一个元素被加入集合时,通过 K 个散列函数将这个元素映射成一个位数组中的 K 个点,把它们置为1;检索时,只要看看这些点是不是都是1 就(大约)知道集合中有没有它了——如果这些点有任何一个0,则被检元素一定不在,如果都是1,则被检元素很可能在。

PS:关于如何实现一个布隆过滤器可以参考我的这篇文章

HBase 中的 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。

PS:需要根据业务来确定具体使用哪种

  • 如果业务大多数随机查询时仅仅使用 row 作为查询条件,BloomFilter 设置为 row;
  • 如果大多数随机查询使用 row+cf 作为查询条件,BloomFilter 需要设置为 rowcol;
  • 如果不确定查询类型,建议设置为 row

5.BLOCKSIZE:数据块大小

HBase 默认的块大小是 64kb,不同于HDFS 默认 64MB 的块大小。原因是 HBase 需要支持随机访问,一旦找到了行键所在的块,接下来就会定位对应的单元格。使用 64kb 的块扫描的速度显然优于 64MB 大小的块。

PS:对于不同的业务数据,块大小的合理设置对读写性能有很大的影响:

  • 如果业务请求以 Get 请求为主,可以考虑将块大小设置较小;
  • 如果以 Scan 请求为主,可以将块大小调大;
  • 默认的 64K 块大小是在 Scan 和 Get 之间取得的一个平衡。

6.BLOCKCACHE:块缓存

缓存是内存存储,HBase 使用块缓存将最近使用的块加载到内存中。块缓存会根据最近最久未使用(LRU)的规则删除数据块。如果使用场景是经常顺序访问 Scan 或者很少被访问,可以关闭列族的缓存。列族缓存默认是打开的(true)。

7.IN_MEMORY:激进缓存

HBase 可以选择一个列族赋予更高的优先级缓存,激进缓存(表示优先级更高),IN_MEMORY 默认是false。如果设置为 true,HBase 会尝试将整个列族保存在内存中,只有在需要保存是才会持久化写入磁盘。但是在运行时 HBase 会尝试将整张表加载到内存里。

PS:这个参数通常适合较小的列族。

8.COMPRESSION:压缩

数据压缩是 HBase 提供的一个特性,HBase 在写入数据块到 HDFS 之前会首先对数据块进行压缩,再落盘,从而可以减少磁盘空间使用量。同样的,在读数据的时候首先从 HDFS 中加载出 block 块之后进行解压缩,然后再缓存到BlockCache,最后返回给用户。

压缩可以节省空间,但读写数据会增加CPU负载,默认为 NONE,不使用用压缩,HBase 目前提供了三种常用的压缩方式: GZip, LZO, Snappy:

  1. GZIP 的压缩率最高,但是 CPU 密集型的,对 CPU 的消耗比其他算法要多,压缩和解压速度也慢;
  2. LZO 的压缩率居中,比 GZIP 要低一些,但是压缩和解压速度明显要比GZIP 快很多,其中解压速度快的更多;
  3. Snappy 的压缩率最低,而压缩和解压速度要稍微比 LZO 要快一些。

PS:综合来看,Snappy 的压缩率最低,但是编解码速率最高,对 CPU 的消耗也最小,目前一般建议使用 Snappy。

9.DATA_BLOCK_ENCODING:数据块编码

除了数据压缩之外,HBase 还提供了数据编码功能。

和压缩一样,数据在落盘之前首先会对 KV 数据进行编码;但又和压缩不同,数据块在缓存前并没有执行解码。因此即使后续命中缓存的查询是编码的数据块,需要解码后才能获取到具体的 KV 数据。

和不编码情况相比,编码后相同数据 block 块占用内存更少,即内存利用率更高,但是读取的时候需要解码,又不利于读性能,在内存不足的情况下,可以压榨 CPU 字段,换区更多的缓存数据。

HBase目前提供了四种常用的编码方式: Prefix_Tree、 Diff 、 Fast_Diff 、Prefix。

10.KEEP_DELETED_CELLS:保留删除的单元格

HBase 的 delete 命令,并不是真的删除数据,而是设置一个标记(delete marker)。用户在检索数据的时候,会过滤掉这些标示的数据。该属性可以设置为 FALSE(默认)、TRUE、TTL

  • FALSE:不保留删除的单元格。
  • TRUE:删除的单元格会保留,超期(TTL)或者数据版本数超过 VERSIONS 设置的值才会被删除;如果没有指定 TTL 或没有超出 VERSIONS 值,则会永久保留它们。
  • TTL:超期(TTL)才会删除,当 TTL 与 MIN_VERSIONS 结合使用时,会删除过期后的数据,但是同时会保留最少数量的版本。

11.REPLICATION_SCOPE:复制范围

HBase 提供了跨级群同步的功能,本地集群的数据更新可以及时同步到其他集群。复制范围(replication scope)的参数默认为0,表示复制功能处于关闭状态。

列族设置建议

1.列族数量

不要在一张表中定义太多的列族。

目前 HBase 并不能很好的处理 2~3 以上的列族,flushcompaction 操作是针对一个 Region 的。

当一个列族操作大量数据的时候会引发一个 flush,它邻近的列族也会因关联效应被触发 flush,尽管它没有操作多少数据。compaction 操作是根据一个列族下的全部文件的数量触发的,而不是根据文件大小触发的。

当很多的列族在 flush 和 compaction 时,会造成很多没用的 IO 负载。

尽量在模式中只针对一个列族进行操作。将使用率相近的列归为一个列族,这样每次访问就只用访问一个列族,既能提升查询效率,也能保持尽可能少的访问不同的磁盘文件。

2.列族的基数

如果一个表存在多个列族,要注意列族之间基数(如行数)相差不要太大。例如列族 A 有100 万行,列族 B 有 10 亿行,按照 RowKey 切分后,列族 A 可能被分散到很多很多 Region(及RegionServer),这导致扫描列族 A 十分低效。

3.列族名、列名长度

列族名和列名越短越好,冗长的名字虽然可读性好,但是更短的名字在 HBase 中更好。

一个具体的值由存储该值的行键、对应的列(列族:列)以及该值的时间戳决定。HBase 中索引是为了加速随机访问的速度,索引的创建是基于“行键+列族:列+时间戳+值”的,如果行键和列族的大小过大,甚至超过值本身的大小,那么将会增加索引的大小。并且在 HBase 中数据记录往往非常之多,重复的行键、列将不但使索引的大小过大,也将加重系统的负担。

总结

根据 HBase 列族属性配置,结合使用场景,HBase 列族可以进行如下优化:

  1. 列族不宜过多,将相关性很强的 key-value 都放在同一个列族下;
  2. 尽量最小化行键和列族的大小;
  3. 提前预估数据量,再根据 RowKey 规则,提前规划好 Region 分区,在创建表的时候进行预分区;
  4. 在业务上没有特别要求的情况下,只使用一个版本,即最大版本和最小版本一样,均为1;
  5. 根据业务需求合理设置好失效时间(存储的时间越短越好);
  6. 根据查询条件,设置合理的BloomFilter配置
Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐