同时打开的文件和进程数量限制 (ulimit)
Linux 系统默认的 ulimit -n
结果为 1024
,这个数量对 HBase 来说有点低,如果 HBase 打开的文件句柄数量超过这个限制,会报以下形式的错误:
官方建议这个数值最少 10000
,不过最好是 2 幂或可以跟 2 的幂有简单的换算关系,比如 10240
。
HBase 打开的文件句柄数量,和 StoreFile
文件的数量直接相关,而 StoreFile
的数量受 ColumnFamily
的总数和 Region
的数量影响。每个 ColumnFamily
至少要用到一个 StoreFile
,而一个被加载的 Region
可能要用到 6
个甚至更多 StoreFile
文件。一个 RegionServer
上会打开的文件句柄数量,有个大概的计算公式:
ulimit -u
可以查看系统允许打开的进程数量,如果 HBase 启动进程过多,会抛出 OutOfMemoryError
异常。
升级
升级过程不能跨大版本,必须从高到低依次升级。
索引数据
HBase 的 get
功能,其实基于 scan
来实现,scan
也可以支持一些查询条件,比如下面的 Java 代码会索引出所有 rowkey
以 0xjiayu
开头的行:
数据记录的 version
HBase 中,一个 {row, column, version}
的组合精准定义一条记录(cell
),row
和 column
字段的值都是未解析的字节串,但 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 相关
HBase 中存储数据,RowKey 是按字典序存储的,如果相似(名称、意义或功能相似)的 RowKey 设计的值有相同的字符前缀,那么它们会被会相邻存储在 HBase 中,此时RowKey 设计不当会导致 访问热区 问题。避免这个问题通常有三种策略:
- RowKey 加盐( Salting )—— 利于写入,不利于批量检索
- 单路哈希 —— 利于批量检索
- 翻转 RowKey
RowKey、ColumnFamily 和 Column Qualifier 的值都要设的尽量简洁,以减少 HBase 的存储消耗(它们会在同一组数据中反复出现,比较占用存储空间)
HBase 底层是 列式存储,所以在操作数据的时候,列优先、行次之,所以先看 ColumnFamily 再看 RowKey —— 相同的 RowKey 可以对应不同的 ColumnFamily,不同的 ColumnFamily 可以包含同一个 RowKey。
对于同一个 ColumnFamily 来说,某一特定的 RowKey 是不可变的。改变 RowKey 的唯一方式是先删除再重新插入。
索引软删除的数据
上面说过,HBase 中的数据都是 软删除,delete
操作只是给特定的数据做一个删除标记,在后续的 Major Compaction
中才会被 硬删除。处于 软删除 状态的数据,在常规的索引方式下不会被索引到。但 HBase 提供了另外一种机制,可以通过 Raw Scan
的方式索引到 软删除 状态中的数据,并且 Major Compaction
也不会把相应的数据进行 硬删除(即经过Major Compaction
任务之后,软删除 状态的数据依然可以被索引到)。
可以对 ColumnFamily 指定是否要启用这种数据保存机制,启用方式有两种,一种是 HBase 的 Shell 命令:
HBase Java API 中的启用方式为:
通过实例演示 KEEP_DELETED_CELLS
机制的效果,先看没启用这种机制时的操作:
启用 KEEP_DELETED_CELLS
机制后的演示:
可以发现,启用 KEEP_DELETED_CELLS
机制后,即使数据被 delete
而且手动执行 major Compaction
,数据仍然能够被索引到。
那么问题就来了,启用 KEEP_DELETED_CELLS
机制后,被标记删除的数据,到底什么时候会被 硬删除 ?按照官方说明,只有发生以下情况(之一)数据才会被 硬删除:
- 数据生存期(TTL)到达,该 Row 的每个 Version 都会被彻底删除;
- 该 Row 的 Version 数量超过 版本数量上限 (maximum number of versions)