接下来记录一下 HBase 存储原理相关的知识, 理解尚浅, 后续再补充.
索引
hbase 中没有索引, 但是 MySQL 有, 区别在于 MySQL 是行级存储, hbase 是列级存储, 索引对行级存储有意义, 对于列级存储意义不大.
如下图所示, 不管是 MySQL 还是 hbase, 最终数据都会落地成文件, 当给行级存储建立索引, 如果想查找 id 为 1 的数据, 直接通过建立的索引, 可以快速定位到数据所在的位置, 这样显然比文件中遍历更加高效, 如果是列级存储, 建立了索引也只对单列数据有效, 如想查询 {id=1,name=messi,age=32} 的数据, 单列的索引显得没意义, 因此 hbase 中就暂时没有索引的说法.
HRegionserver
HRegionserver 是管理 HRegion 的, HRegion 中的数据最终会以 HFile 的形式落地到 hdfs, 因此它和 dataNode 部署在同一个节点上(数据本地化策略, 这样可以减少网络 IO 消耗), 以下是它的主要组成结构图.
1 WAL: 写日志相关
2 BlockCache: 读缓存相关
3 HRegion: 写缓存, 存储相关
后面将一一说明这些组成部分, 最后 storeFile 会以 HFile 的形式落地到 hdfs, 因此 hbase 也具有了副本策略.
HRegion
hbase 中一个表会在行键的方向上, 会被切分为一个或多个 HRegion, 其中 HRegion 是分布式存储的最小单元, 以下是对 HRegion 的说明记录.
(1)HRegion 里有至少一个 HStore, 一个 HStore 对应一个列族, 因为表中列族至少有一个, 因此 HRegion 中就至少有一个 HStore.
(2)memStore 是写缓存, 默认 128MB, 写入 hbase 的数据不是直接写入到 hdfs, 而是会经过写缓存, 写缓存里会根据字典序对行键排序, 它有一定的规则来冲刷, 冲刷后的文件就是 storeFile.
(3)storeFile 的数目是 0 到多个, 如果 memStore 还没有写出, 那就没有 storeFile, 写出就有, 它是存储的最小单元, 存储到 hdfs 后会变成 HFile 的形式, 其中 storeFile 是 HFile 的轻量版封装.
(4)刚开始 table 不大时, 一个 HRegion 就可以应付, 当数据量大于阈值 10G 时(默认 10G, 可以通过 hbase.hregion.max.filesize 来配置, 范围 1~20G),HRegion 会分裂成两个同样大小的 HRegion, 并且其中一个 HRegion 的管理权会发生转移. 如果 RowKey 为 s3-s35 的数据量大于 10G 时, 会分裂成两个新的 HRegion, 可以看出行键都是按照字典序排列, 因此多个 HRegion 之间没有数据的交叉.
(5)HRegion 交由 HRegionServer 来管理, 后者跟 DataNode 在同一个节点上, 符合数据本地化策略.
WAL
WAL 即 write ahead log, 是维系在磁盘上的文件, 所有的写操作(如 put,delete), 首先会写入这个文件, 当写成功后才会写入 memStore. 当发生 HRegionserver 的宕机, 依然可以从这个文件中进行数据的恢复.
文件是以 HLog 作为实现类, 它是 hadoop sequence file,key 为 HLogKey 对象, 其中包含数据的归属信息, 如表名, HRegion, 写入时间戳, sequence number 等, value 为 hbase 中的 KeyValue 对象(待补充).
在 0.94 版本之前, WAL 往里写入数据是阻塞的, 会影响性能, 为了优化, 在 1.0 版本后使用了多管道并行技术.
BlockCache
一个 HRegionserver 中只有一个 blockcache, 维系在内存中, 默认 128M. 限于它的空间有限, 会采取'引用局部性原理', 来缓存下次访问能大概率命中的数据.
(1)时间局部性: 数据被访问后, 下次这条数据将大概率被访问到.
(2)空间局部性: 数据被访问后, 下次这条数据附近的数据也将大概率被访问到. 空间局部性缓存的数据最小单位是 datablock,datablock 中一条数据被读取, 则整个 datablock 都会被缓存到 blockcache.
由于它的大小有限, 数据跟 Redis 类似, 会采用淘汰策略来清除, 默认采用 on-heap LRU BlockCache 策略, 即删除最近最少使用, 以及最长时间没有被使用的数据.
Zookeeper
zookeeper 负责管理 hbase 上的节点, hbase 启动后会注册一个持久节点 hbase, 下面有很多的子节点. 其中 master 和 backup-masters 子节点分别保存 active HMaster 和 standby HMaster, 这两个子结点都是临时节点, 其中一个挂掉另外一个就能监听到, 这利用了 zookeeper 的 watcher.
(1)如果 active HMaster 宕机, standby HMaster 会顶替为 active 来提供服务.
(2)active HMaster 会监控 backup-masters 里的 standby HMaster, 选择还存活的备用节点进行数据的同步, 为下次故障转移做准备.
- # 查看 hbase 持久节点下的节点
- [zk: localhost:2181(CONNECTED) 18] ls /hbase
- [replication, meta-region-server, rs, splitWAL, backup-masters, table-lock, region-in-transition, online-snapshot, master, running, recovering-regions, draining, namespace, hbaseid, table]
- # 查看 master 节点内容, hadoop01 为 active HMaster
- [zk: localhost:2181(CONNECTED) 20] get /hbase/master
- ?master:60000K'1????PBUF
- # hadoop01 为 active
- hadoop01???????-
- cZxid = 0x480000000a
- ctime = Tue Jan 14 13:57:58 CST 2020
- mZxid = 0x480000000a
- mtime = Tue Jan 14 13:57:58 CST 2020
- pZxid = 0x480000000a
- cversion = 0
- dataVersion = 0
- aclVersion = 0
- # 显示不为 0X0, 说明是临时节点, 后面的 16 进制数字代表 session id
- ephemeralOwner = 0x16fa2a06d490001
- dataLength = 54
- numChildren = 0
- # hadoop02 为 backup HMaster
- [zk: localhost:2181(CONNECTED) 21] ls /hbase/backup-masters
- # 下面一整串, 就是备用节点的名字
- [hadoop02,60000,1579027138325]
- HMaster
前面 hbase 启动后会有一个 HMaster 以及多个 HRegionserver, 这个 HMaster 就是管理 HRegionserver 的主节点, 它可以启动多个. 而 HRegionserver 是管理多个 HRegion 的从节点.
(1)HMaster 可以启动多个, 先启动的就是主节点, 后启动都是从节点, 主节点注册在 zookeeper 的 / hbase/master 节点, 从节点注册在 / hbase/backup-masters 节点下. 通过如下 hbase-daemon.sh start master 命令就可以启动一个 HMaster, 虽然不限制启动的个数, 但是考虑到备份数据占用网络 IO 影响效率, 不宜超过 3 个.
- [[email protected] /home/software/hbase-0.98.17-hadoop2/bin]# sh hbase-daemon.sh start master
- starting master, logging to /home/software/hbase-0.98.17-hadoop2/bin/../logs/hbase-root-master-hadoop02.out
(2)HMaster 负责表的 DDL 操作, 如 create,drop,enable 和 disable 等, 如下图所示.
(3)为了确认 HMaster 还存活, 它会定期 (默认 180s) 向 zookeeper 发送心跳, 实际应用中为了更快的检测故障并转移, 会将时间设置得更短, 一般为 10-30 秒.
Hbase 读写整体概括
hbase 的读写分为 hbase0.96 版本之前和之后, 之前要复杂一些, 因为里面涉及到一步鸡肋的 - ROOT - 文件的读取, 0.96 版本之前主要步骤如下.
1 client 对 hbase 发起请求, 如 put/get 请求, 首先会访问 zookeeper 节点 root-region-server, 获取到 - ROOT - 文件的位置.
2 获取到 - ROOT - 文件位置后, 它实际存储在某个 HRegionserver 的节点上, 如图用第一个表示, 找到后会读取 - ROOT - 数据内容, 获取到. meta. 文件的位置.
3 获取到. meta. 文件的位置后, 它可能存储在另外一个 HRegionserver 节点, 如图用第二个表示, 找到后读取其中内容, 获取到数据对应的 HRegion, 以及 HRegionserver 位置.
4 直接访问最终访问地 HRegionserver 后, 跟它进行通信完成读写.
这种步骤错误的估计了 meta 文件的大小, hbase 中元数据包括 namespace, 表名, 列族, 表 - HRegion 关系数据, HRegion-HRegionserver 关系数据等. 一个表的元数据, 一般几 KB, 因此一般的项目, 元数据也就十几 MB 的大小, 为了这样大小的元数据专门设置一个 - ROOT - 目录来管理显得没有意义, 因此在 hbase0.96 后的版本对它进行了舍弃, 以上步骤将没有第一步, 将更新为以下如图所示步骤.
1 client 对 hbase 发起请求, 如 put/get 请求, 首先会访问 zookeeper 节点 meta-region-server, 获取到. meta. 文件的位置, 客户端获取这个位置信息后会进行缓存.
2 获取到. meta. 文件位置后, 它实际存储在某个 HRegionserver 的节点上, 如图用第一个表示, 找到后读取其中内容, 获取到数据对应的 HRegion, 以及 HRegionserver 位置. 客户端也会缓存. meta. 文件中的内容, 如果读取的次数越多这个文件将越大, 下次发起请求将可以直接读取缓存数据, 提高效率. 但是在 HRegion 发生分裂或者 client 宕机后, 需要重新读取建立缓存.
3 直接访问最终访问地 HRegionserver 后, 跟它进行通信完成读写.
HBase 写流程
接上面读写整体概括, 当写操作达到最终目的地 HRegionserver 后, 执行以下流程.
(1)先将操作写入到 WAL 中.
(2)将数据追加到 menStore 中, menStore 中数据会进行排序.
现在有个问题, 如果在进行写操作的时候, 想更新一条数据的值, 恰好更新的数据也在 memStore 中, 是否应该去更新它呢? 答案是否定的, hbase 中这里采用顺序写, 即更新内容以追加的形式进入 memStore, 而不是移动指针到以前数据的位置进行更改. 为了性能考虑, hbase 采用顺序写, 而不是随机写(可以移动指针修改).
(3)menStore 数据达到一定条件会冲刷, memStore 冲刷的条件有三, 一般在稳定运行的项目上容易实现条件三, 因为数据量上去了, 自然 memStore 就多了, 每个默认 128M, 多了就物理内存会吃不消会冲刷:
a. 写缓存满, 冲刷;
b.WAL 达到 1G, 冲刷, 并且新建 WAL;
c. 当 HRegionserver 下所有的 memStore 之和占用的空间达到物理内存的 35%, 冲刷最大的那个 memStore.
(4)冲刷成 HFile 后, 单个 HFile 是有序的, 因为在冲刷之前以前需排序过了, 所以 HFile 上将把持起始行键和结束行键.
HFile
关于 HFile, 目前前后经历了 3 个版本, 以第一个版本来说明它的格式内容, 后面两个版本就是添加了其他的内容, 差别不是很大.
HFile 格式如下图所示.
(1)datablock 存储数据
a. 每个 datablock 都是有序的, 也会有起始行键和结束行键, 便于数据查找.
b. 默认大小 64KB, 可以调整, 如果调小便于 get 查找, 即字段查, 调大便于 scan 查找, 即顺序读.
c. 一个 datablock 由魔数 Magic 和多个 KeyValue 组成. 魔数本质上就是一个随机数, 除了表明这是一个 datablock 外, 还可以用于验证 datablock 文件完整性, 或者是否修改, 如果有变动则魔数会变, 因此它可以用于验证.
d.KeyValue 本质是一个 byte 数组, 如图所示 key 包含信息很多, 而 value 是二进制数据.
e. 在 HFile V2 版本中, 最后还引入了 BloomFilter 布隆过滤器.
(2)metablock 存储元数据, 这个部分一般是可选, 出现在. meta. 文件中.
(3)file info, 即 HFile 的描述信息, 如 AVG_KEY_LEN, AVG_VALUE_LEN, LAST_KEY,COMPARATOR, MAX_SEQ_ID_KEY 等.
(4)data index, 记录 datablock 的在 HFile 中的起始位置.
(5)meta index, 记录 metablock 的在 HFile 中的起始位置.
(6)trail, 里面记录了 data index 和 meta index 的起始位置, 也记录了 file info 的起始位置.
布隆过滤器
布隆过滤器, 是为了查找 HFile 更快速而在 V2 版本中添加的. 因为如果读取一个数据, 只是使用行键来查询, 过滤掉剩下的 HFile 可能依然很多, 定位还不够快, V2 版本中添加的布隆过滤器, 可以将不存在这个行键的 HFile 再过滤, 这样剩下的 HFile 就更加值得遍历查询, 提高了读取的速度.
布隆过滤器会维持一个 byte 数组, 它的特点是能确定一个数据确定没有, 但是不能确定一定有. 如下图所示, 一个数据会通过三个不同的哈希函数来映射到 byte 数组上的位置, 如果有一个不为 1, 则能肯定这个数据没有, 如 p1. 如果全部为 1, 不一定确定有, 如 p1 映射的结果有一个本来为 0, 但是这个数据会被 s2 覆盖, 这样 p1 映射结果有不为 1 的但是 byte 数组对应位置全为 1, 造成有 p1 数据的假象, 但实际却没有.
如果数据量多了, 会造成判断的失误, 因此这种过滤器适宜用在不要求 100% 准确率, 但是需要这种功能的场景.
HBase 读流程
接上面读写整体概括, 当读操作达到最终目的地 HRegionserver 后, 执行以下流程.
(1)先读取读缓存.
(2)读缓存没有, 就去 memStore 查找
(3)memStore 中没有就去 HFile 中查找, 需要进行过滤, 提高读取速度.
Compaction 机制
前面的 memStore 很容易冲刷出 HFile, 这样很容易产生很多小文件, 显然对 hdfs 存储来说是不利的, hbase 考虑到了这一点, 因此就有了 compaction 机制, 分为 minor compact 和 major compact.
hbase 默认采用 minor compact 对 HFile 进行合并, 因为效率高.
major compact 是 memStore 中所有 HFile 一起合并, 这种合并执行效率低, 一般需要好几个小时, 一般安排在凌晨或周末进行.
以上是对 hbase 原理的总结, 后续再完善.
参考博文:
(1)《HBase 权威指南中文版》
(2) https://www.jianshu.com/p/53864dc3f7b4
来源: http://www.bubuko.com/infodetail-3382588.html