3

HBase 一些需要注意的点

 2 years ago
source link: https://jiayu0x.com/2016/11/21/hbase-notice/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

同时打开的文件和进程数量限制 (ulimit)

参考:
http://hbase.apache.org/book.html#basic.prerequisites

Linux 系统默认的 ulimit -n 结果为 1024 ,这个数量对 HBase 来说有点低,如果 HBase 打开的文件句柄数量超过这个限制,会报以下形式的错误:

2010-04-06 03:04:37,542 INFO org.apache.hadoop.hdfs.DFSClient: Exception increateBlockOutputStream java.io.EOFException
2010-04-06 03:04:37,542 INFO org.apache.hadoop.hdfs.DFSClient: Abandoning block blk_-6935524980745310745_1391901

官方建议这个数值最少 10000,不过最好是 2 幂或可以跟 2 的幂有简单的换算关系,比如 10240

HBase 打开的文件句柄数量,和 StoreFile 文件的数量直接相关,而 StoreFile 的数量受 ColumnFamily 的总数和 Region 的数量影响。每个 ColumnFamily 至少要用到一个 StoreFile,而一个被加载的 Region 可能要用到 6 个甚至更多 StoreFile 文件。一个 RegionServer 上会打开的文件句柄数量,有个大概的计算公式:

(StoreFiles per ColumnFamily) x (regions per RegionServer)

ulimit -u 可以查看系统允许打开的进程数量,如果 HBase 启动进程过多,会抛出 OutOfMemoryError 异常。


升级过程不能跨大版本,必须从高到低依次升级。


HBase 的 get 功能,其实基于 scan 来实现,scan 也可以支持一些查询条件,比如下面的 Java 代码会索引出所有 rowkey0xjiayu 开头的行:

public static final byte[] CF = "cf".getBytes();
public static final byte[] ATTR = "attr".getBytes();
Table table = ... // instantiate a Table instance
Scan scan = new Scan();
scan.addColumn(CF, ATTR);
scan.setRowPrefixFilter(Bytes.toBytes("0xjiayu"));
ResultScanner rs = table.getScanner(scan);
for (Result r = rs.next(); r != null; r = rs.next()) {
// process result...
} finally {
rs.close(); // always close the ResultScanner!



数据记录的 version

参考:
http://hbase.apache.org/book.html#versions

HBase 中,一个 {row, column, version} 的组合精准定义一条记录(cell),rowcolumn 字段的值都是未解析的字节串,但 version 是长整型数字,一般是时间戳,由 java.util.Date.getTime()System.currentTimeMillis() 生成。

version 字段存在的意义,是相同的 {row, column},插入、修改的时间戳不同,其 {row, column, version} 组合也代表不同的数据记录,这些数据记录实际的数据相同,但版本不相同;把 version 字段用到 HBase 的数据表中,意思是让 HBase 为同一组 {row, column} 保留的版本数目。在 HBase 0.96 以前,一个新创建的数据表默认的 version 值是 3(即默认为同一组数据最多保存 3 个版本),自 HBase 0.96 开始,默认值改为 1

version 字段可以在创建数据表的时候就设定好,也可以创建数据表之后用 alter 的方式更改,具体需查阅官方手册。

HBase 中,version 按照降序存储,所以每次需要索引数据,最新版本的数据(时间戳表示的version 值最大)最先被索引。默认只索引最新版本的一条记录,如果需要索引所有版本或部分版本的数据记录,可以参考 http://hbase.apache.org/apidocs/org/apache/hadoop/hbase/client/Get.html#setMaxVersions() 。

同一条记录(cell)里的 version 容易疑惑的两个地方:

  • 如果 cell 被多次连续写、修改,那么只有最后一次的操作是可以索引的、有效的;
  • version 的值只需 非升序 即可。

对数据记录的每一次 put 操作,都会产生一个新的 version,但 put 操作的时候,可以手动指定 version 的时间戳的数值(一般可以指定一个比较小的数值),即可以手动指定版本,使最新的操作不是最新的 version

HBase 中的删除,默认都是 软删除,即只做一个删除标记,然后不能索引,只有在随后的 Major Compaction 中,才会真正地删除。另外,删除操作中,除非指定删除一个版本,不然会把小于等于当前版本的所有记录都标记为删除。


HBase 数据表相关

不支持原生的 Join 操作。

表设计的一些经验准则:

  • 一个 region 大小在 10~50GB 之间比较合适;
  • HBase 中的单条记录(cell),不宜超过 10MB,如果启用 mob 模块,不宜超过 50MB,不然可以考虑把数据放到 HDFS 中,然后在 HBase 中只存一个指针或者表明数据再 HDFS 位置的字串;
  • 单个数据表的 ColumnFamily 数量不宜超过 3 个,设计 HBase 的数据表,尽量不要模仿关系数据库表的设计;
  • 包含 1 到 2 个 ColumnFamily 的数据表,对应的 Regoin 数量在 50~100 个比较适合;
  • ColumnFamily 的值越短越好,因为实际存储中,它会做每一个值的前缀,越短越节省存储空间;
  • 如果只有一个 ColumnFamily 有频繁的写操作,那么它占用内存最多。分配资源的时候注意这一个写操作模型。

RowKey 相关

参考:
http://hbase.apache.org/book.html#rowkey.design

HBase 中存储数据,RowKey 是按字典序存储的,如果相似(名称、意义或功能相似)的 RowKey 设计的值有相同的字符前缀,那么它们会被会相邻存储在 HBase 中,此时RowKey 设计不当会导致 访问热区 问题。避免这个问题通常有三种策略:

  1. RowKey 加盐( Salting )—— 利于写入,不利于批量检索
  2. 单路哈希 —— 利于批量检索
  3. 翻转 RowKey

RowKey、ColumnFamily 和 Column Qualifier 的值都要设的尽量简洁,以减少 HBase 的存储消耗(它们会在同一组数据中反复出现,比较占用存储空间)

HBase 底层是 列式存储,所以在操作数据的时候,列优先、行次之,所以先看 ColumnFamily 再看 RowKey —— 相同的 RowKey 可以对应不同的 ColumnFamily,不同的 ColumnFamily 可以包含同一个 RowKey。

对于同一个 ColumnFamily 来说,某一特定的 RowKey 是不可变的。改变 RowKey 的唯一方式是先删除再重新插入。


索引软删除的数据

参考:
http://hbase.apache.org/book.html#cf.keep.deleted

上面说过,HBase 中的数据都是 软删除delete 操作只是给特定的数据做一个删除标记,在后续的 Major Compaction 中才会被 硬删除。处于 软删除 状态的数据,在常规的索引方式下不会被索引到。但 HBase 提供了另外一种机制,可以通过 Raw Scan 的方式索引到 软删除 状态中的数据,并且 Major Compaction 也不会把相应的数据进行 硬删除(即经过Major Compaction 任务之后,软删除 状态的数据依然可以被索引到)。

可以对 ColumnFamily 指定是否要启用这种数据保存机制,启用方式有两种,一种是 HBase 的 Shell 命令:

hbase> alter ‘t1′, NAME => ‘f1′, KEEP_DELETED_CELLS => true

HBase Java API 中的启用方式为:

HColumnDescriptor.setKeepDeletedCells(true);

通过实例演示 KEEP_DELETED_CELLS机制的效果,先看没启用这种机制时的操作:

create 'test', {NAME=>'e', VERSIONS=>2147483647}
put 'test', 'r1', 'e:c1', 'value', 10
put 'test', 'r1', 'e:c1', 'value', 12
put 'test', 'r1', 'e:c1', 'value', 14
delete 'test', 'r1', 'e:c1', 11
hbase(main):017:0> scan 'test', {RAW=>true, VERSIONS=>1000}
ROW COLUMN+CELL
r1 column=e:c1, timestamp=14, value=value
r1 column=e:c1, timestamp=12, value=value
r1 column=e:c1, timestamp=11, type=DeleteColumn
r1 column=e:c1, timestamp=10, value=value
1 row(s) in 0.0120 seconds
hbase(main):018:0> flush 'test'
0 row(s) in 0.0350 seconds
hbase(main):019:0> scan 'test', {RAW=>true, VERSIONS=>1000}
ROW COLUMN+CELL
r1 column=e:c1, timestamp=14, value=value
r1 column=e:c1, timestamp=12, value=value
r1 column=e:c1, timestamp=11, type=DeleteColumn
1 row(s) in 0.0120 seconds
hbase(main):020:0> major_compact 'test'
0 row(s) in 0.0260 seconds
hbase(main):021:0> scan 'test', {RAW=>true, VERSIONS=>1000}
ROW COLUMN+CELL
r1 column=e:c1, timestamp=14, value=value
r1 column=e:c1, timestamp=12, value=value
1 row(s) in 0.0120 seconds

启用 KEEP_DELETED_CELLS 机制后的演示:

hbase(main):005:0> create 'test', {NAME=>'e', VERSIONS=>2147483647, KEEP_DELETED_CELLS => true}
0 row(s) in 0.2160 seconds
=> Hbase::Table - test
hbase(main):006:0> put 'test', 'r1', 'e:c1', 'value', 10
0 row(s) in 0.1070 seconds
hbase(main):007:0> put 'test', 'r1', 'e:c1', 'value', 12
0 row(s) in 0.0140 seconds
hbase(main):008:0> put 'test', 'r1', 'e:c1', 'value', 14
0 row(s) in 0.0160 seconds
hbase(main):009:0> delete 'test', 'r1', 'e:c1', 11
0 row(s) in 0.0290 seconds
hbase(main):010:0> scan 'test', {RAW=>true, VERSIONS=>1000}
ROW COLUMN+CELL
r1 column=e:c1, timestamp=14, value=value
r1 column=e:c1, timestamp=12, value=value
r1 column=e:c1, timestamp=11, type=DeleteColumn
r1 column=e:c1, timestamp=10, value=value
1 row(s) in 0.0550 seconds
hbase(main):011:0> flush 'test'
0 row(s) in 0.2780 seconds
hbase(main):012:0> scan 'test', {RAW=>true, VERSIONS=>1000}
ROW OLUMN+CELL
r1 column=e:c1, timestamp=14, value=value
r1 column=e:c1, timestamp=12, value=value
r1 column=e:c1, timestamp=11, type=DeleteColumn
r1 column=e:c1, timestamp=10, value=value
1 row(s) in 0.0620 seconds
hbase(main):013:0> major_compact 'test'
0 row(s) in 0.0530 seconds
hbase(main):014:0> scan 'test', {RAW=>true, VERSIONS=>1000}
ROW COLUMN+CELL
r1 column=e:c1, timestamp=14, value=value
r1 column=e:c1, timestamp=12, value=value
r1 column=e:c1, timestamp=11, type=DeleteColumn
r1 column=e:c1, timestamp=10, value=value
1 row(s) in 0.0650 seconds

可以发现,启用 KEEP_DELETED_CELLS 机制后,即使数据被 delete 而且手动执行 major Compaction,数据仍然能够被索引到。

那么问题就来了,启用 KEEP_DELETED_CELLS 机制后,被标记删除的数据,到底什么时候会被 硬删除 ?按照官方说明,只有发生以下情况(之一)数据才会被 硬删除

  1. 数据生存期(TTL)到达,该 Row 的每个 Version 都会被彻底删除;
  2. 该 Row 的 Version 数量超过 版本数量上限 (maximum number of versions)

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK