深入了解HBase的存储设计及伸缩性与高可用

主要内容

  • HBase存储设计

  • HBase读写流程

  • HBase Campaction(伸缩性)

  • HBase高可用(灾备)

HBase存储设计

底层存储

HBase的底层存储是基于 Hadoop HDFS 的:

  • Hadoop DataNode 负责存储 Region Server 所管理的数据。所有的 HBase 数据都存储在 HDFS 文件中。Region Server 和 HDFS DataNode 往往是分布在一起的,这样 Region Server 就能够实现数据本地化(data locality,即将数据放在离需要者尽可能近的地方)。HBase 的数据在写的时候是本地的,但是当 region 被迁移的时候,数据就可能不再满足本地性了,直到完成 compaction,才能又恢复到本地。
  • Hadoop NameNode 维护了所有 HDFS 物理 data block 的元信息。

HBase存储

hbase2-1

第一幅图(左上角)是HBase里面一个典型的表的逻辑图,里面有 cf1cf2 两个 Column Family(以下简称CF),每个CF里面有两列。里面的红色和黄色小块表示有数据,其他地方都没有数据,这张图的表是一个稀疏矩阵,多个层叠的部分表示有多个版本的数据。

第二幅图(右上角)也是一个逻辑图,主要为了说明以下几个点:

  • 不同CF里面的数据是分开存储的;
  • 同一个CF里面的数据是按照row key顺序存储的;
  • 统一cell里面有多个版本的数据的时候,新版本数据在前,旧版本数据在后,这样方便先取到新版本数据。

第三幅图(右下角)是上面的逻辑存储在物理文件上的存储形式,需要注意以下几个点:

  • 不同CF的数据是存储在不同的文件里面的(Storefile或HFile),这就是为什么我们要将要一起使用的数据字段定义在同一个Column Family的原因;
  • 同一个文件里面还是按照 row key、CF、Column qualifier 排序的;
  • 每一条数据里面都要存储 Key(row key、Column Family、Column qualifier、Timestamp) 和value。在 RDBMS 里面我们设计字段名时一般要求能够“见名知意”,但在 HBase 里面不推荐这样做,Key的设计在保证功能的前提下,越短越好(比如仅用一个字母表示),至于其含义可以其它地方记录,比如文档里面。

第四幅图(左下角)是为了说明查询时指定各个 Key 对性能的影响:

  • 指定 row key 可以大幅度提高查询性能,因为根据 row key 可以确定在哪些 region 上面查(也就是说可以跳过那些不包含该 row key 的region)。在 scan 命令里面,可以通过 STARTROWSTOPROW 指定 row key 范围。
  • 指定 Column Family 可以大幅度提高查询性能,因为根据CF可以确定跳过哪些Storefile/HFile,一般查询时都建议指定CF。
  • 指定 Timestamp 也可以较大幅度提高查询性能,因为每个 Storefile 会存储它所保存的所有数据的时间区间,如果所指定的 Timestamp 不在该区间内,则直接跳过。
  • 指定 Column QualifierValue 的过滤条件可以提高查询性能,但提高的很少。因为必须把每个 Cell 的值读出来和指定的条件做对比。

逻辑视图 vs 物理视图

hbase2-2

不同列族的数据被存在了不同的 Store 中,StoreFile 以 HFile 格式保存在 HDFS 上。HFile 是 Hadoop 的二进制格式文件。实际上 StoreFile 就是对 HFile 做了轻量级包装,即 StoreFile 底层就是 HFile,HFile 以有序 KeyValue 的形式存储,HFile 的具体数据格式如下:

hbase2-3

HFile 使用多层索引来查询数据而不必读取整个文件,这种多层索引类似于一个 B+ tree:

  • KeyValues 有序存储。
  • rowkey 指向 index,而 index 则指向了具体的 data block,以 64 KB 为单位。
  • 每个 block 都有它的叶索引。
  • 每个 block 的最后一个 key 都被存储在中间层索引。
  • 索引根节点指向中间层索引。

trailer 指向原信息数据块,它是在数据持久化为 HFile 时被写在 HFile 文件尾部。trailer 还包含例如布隆过滤器和时间范围等信息。布隆过滤器用来跳过那些不包含指定 rowkey 的文件,时间范围信息则是根据时间来过滤,跳过那些不在请求的时间范围之内的文件。

HBase读写流程

读流程

有一个特殊的 HBase Catalog 表叫 Meta table(它其实是一张特殊的 HBase 表),包含了集群中所有 regions 的位置信息。Zookeeper 保存了这个 Meta table 的位置。

当 HBase 第一次读或者写操作到来时:

  • 客户端从 Zookeeper 那里获取是哪一台 Region Server 负责管理 Meta table。
  • 客户端会查询那台管理 Meta table 的 Region Server,进而获知是哪一台 Region Server 负责管理本次数据请求所需要的 rowkey。客户端会缓存这个信息,以及 Meta table 的位置信息本身。
  • 然后客户端会去访问那台 Region Server,获取数据。

对于以后的的读请求,客户端从可以缓存中直接获取 Meta table 的位置信息(在哪一台 Region Server 上),以及之前访问过的 rowkey 的位置信息(哪一台 Region Server 上),除非因为 Region 被迁移了导致缓存失效。这时客户端会重复上面的步骤,重新获取相关位置信息并更新缓存。

hbase2-4

写流程

  1. Client 的 Put 操作会将数据先写入 WAL。
  2. 当数据写入WAL,然后将数据拷贝到 MemStore。 MemStore 是内存空间,数据并未写入磁盘。
  3. 一旦数据成功拷贝到 MemStore。 Client 将收到 ACK。
  4. 当 MemStore 中的数据达到阈值,数据会写入 HFile。

MemStore 在内存中缓存 HBase 的数据更新,以有序 KeyValues 的形式,这和 HFile 中的存储形式一样。每个 Column Family 都有一个 MemStore,所有的更新都以 Column Family 为单位进行排序。

hbase2-5

在第二步的时候,因为只写入了内存,万一在还没写入到磁盘的时候断电了怎么办呢?因为 WAL 机制的存在,可以从操作日志从其他HRegionServer中回放这些操作恢复数据。

HBase Campaction(伸缩性)

Bigtable的设计目标是“可伸缩性”,为此放弃了一些别的特性,比如事务。而“可伸缩性”这个特点很大一部分就体现在Compaction这里。

HBase 的 MemStore 在满足阈值的情况下会将内存中的数据刷写成 HFile ,一个 MemStore 刷写就会形成一个 Hfile。随着时间的推移,同一个 Store 下的 HFile 会越来越多,文件太多会影响 HBase 查询性能,主要体现在查询数据的 io 次数增加。为了优化查询性能,HBase 会合并小的 HFile 以减少文件数量,这种合并HFile的操 作称为 Compaction,这也是为什么要进行 Compaction 的主要原因。

  1. 将多个小的 HFile 合并成一个更大的 HFile 以增加查询性能
  2. 在合并过程中对过期的数据(超过TTL,被删除,超过最大版本号)进行真正的删除,一般情况下的删除只是打了一个墓碑标记,因为HDFS不支持对文件的随机读写

在对HBase进行写操作的时候,进行Put和Update操作的时候,其实是新增了一条数据,即使是在进行Delete操作的时候,也是新增一条数据,只是这条数据没有value,类型为DELETE,这条数据叫做墓碑标记(Tobstone)。数据的真正删除是在compact操作时进行的。

Minor Compaction :会将邻近的若干个 HFile 合并,在合并过程中会清理 TTL 的 数据,但不会清理被删除的数据。

Major Compaction:会将一个 store 下的所有 HFile 进行合并,并且会清理掉过期的和被删除的数据,即在 Major Compaction 会删除全部需要删除的数据。值得 注意的是,一般情况下,Major Compaction 时间会持续比较长,整个过程会消耗 大量系统资源,对上层业务有比较大的影响。因此,生产环境下通常关闭自动触发 Major Compaction 功能,改为手动在业务低峰期触发。

hbase2-6

首先内存中维护着一个filesToCompact(合并队列),在该队列中的Hfile将会被Minor合并。

当有新的HFile文件产生时,如果同一个列簇下的文件数大于等于hbase.hstore.compaction.min 时,就会将符合合并规则的文件放入合并队列,合并规则如下:

• 如果该文件小于hbase.hstore.compaction.min.size, 则一定会被添加到合并队列中。

• 如果该文件大于hbase.hstore.compaction.max.size,则一定会从队列中被排除。

• 如果该文件小于它后面hbase.hstore.compaction.max(默认为10)个文件之和乘 hbase.hstore.compaction.ratio(默认为1.2),则该文件也将加入到合并队列中。

Compaction带来的问题

对读的影响:

读性能会在 compaction 期间略微降低,而在 compaction 后又回到一个稳定的水平,从下图可以看到图中会有许多毛刺这是因为当进行 compaction 时读性能就会短暂的降低,而在完成后又回到正常水平。

hbase2-7

对写的影响:

HFile个数超过 hbase.hstore.blockingStoreFiles(默认为7)时, 系统将会强制执行 compaction 操作进行文件合并, 此时写情况会被阻塞。在数据生成速度很快时,HFile的不断快速生成就需要进行频繁的 compaction 操作,从而限制写请求速度。

第二个问题是 compaction 操作会导致写放大。 一次写入的数据,被多次反复读取 与写入,会导致集群 IO 资源的浪费。

HBase高可用(灾备)

HDFS 数据备份

HBase本身是存储在 HDFS 上的,HDFS 默认是3备份,一份会写入本地节点,另外两个备份会被写入机甲感知得到的其它节点。WAL 和 HFiles 都会持久化到硬盘并备份。

Region Server故障恢复

当某个 Region Server 发生 crash 时,它所管理的 region 就无法被访问了,直到 crash 被检测到,然后故障恢复完成,这些 region 才能恢复访问。Zookeeper 依靠心跳检测发现节点故障,然后 HMaster 会收到 region server 故障的通知。

当 HMaster 发现某个 region server 故障,HMaster 会将这个 region server 所管理的 regions 分配给其它健康的 region servers。为了恢复故障的 region server 的 MemStore 中还未被持久化到 HFile 的数据,HMaster 会将 WAL 分割成几个文件,将它们保存在新的 region server 上。每个 region server 然后回放各自拿到的 WAL 碎片中的数据,来为它所分配到的新 region 建立 MemStore。

hbase2-8

HMaster HA

HMaster 有 HA 模式,即主备模式。同 一时间只有一个 HMaster 能成功在 Zookeeper 中注册 /hbase/master 节点,成为 Active 提供服务。

因为每台 HMaster 都和 Zookeeper 之间存在着心跳保持,当 Active HMaster 发生故障 时,Zookeeper 中的 /hbase/master 节点自动删除,其他 HMaster 此时如果成功注册 该节点,则变为新的 Active。成为 Active的 HMaster 需要从 Zookeeper 中加载完相应 的数据到内存,就可以提供服务。

在 HBase2.0 中,还有 HBase Read HA,Region 将不再只保存在某一单独的 Region Server 上,而是选择其他的 两个 Region Server 分别存储该 Region 的两个备份,这样某台 Region Server 挂掉时, 客户端仍然可以从其它 Region Server 上备份的 Region 中读到数据。

0%