深入理解HBase架构

在这篇博客文章中,我们主要深入看一下H Base 的体系结构以及在 NoSQL 数据存储解决方案主要优势。

1. HBase架构组件

从物理上来说 HBase 由主从模式架构的三种服务组成:

  • RegionServer:负责为读写提供数据。访问数据时,客户端可直接与 RegionServer 进行通信。
  • HBase Master:也称之为 HMaster,负责 Region 的分配,DDL(创建、删除表)操作等。
  • Zookeeper:作为 HDFS 一部分的,负责维护活跃集群的状态。

Hadoop DataNode 负责存储 RegionServer 管理的数据。所有 HBase 的数据都存储在 HDFS 文件中。RegionServer 和 HDFS DataNode 往往会部署在一起,这样 RegionServer 就能够实现数据本地化(即数据放在离需要尽可能近的地方)。在数据写入 HBase 时满足数据本地性,但是随着 Region 的迁移(由于故障恢复或者负载均衡等原因)可能不再满足本地性了(数据还在原先的 RegionServer 上,只是 Region 交给新的 RegionServer 管理),等到完成数据压缩才能恢复本地性。

NameNode 维护所有构成文件的物理数据块的元数据信息。

1.1 RegionServer与Region

HBase 表根据 RowKey 的开始和结束范围水平拆分为多个 Region。每个 Region 都包含了 StartKey 和 EndKey 之间的所有行。每个 Region 都会分配到集群的一个节点上,即 RegionServer,由它们为读写提供数。RegionServer 大约可以管理 1000 多个 Region。

1.2 HMaster

Region 的分配,DDL(创建,删除表)操作均由 HMaster 负责处理。

HMaster具体负责:

  • 协调 RegionServer:(1)在启动时分配 Region、在故障恢复或者负载均衡时重新分配 Region。(2)监视集群中的所有 RegionServer 实例(侦听来自 Zookeeper 的通知)。
  • 管理员功能:创建,删除,更新表的接口。

1.3 ZooKeeper

HBase 使用 ZooKeeper 作为分布式协调服务来维护集群中的服务状态。Zookeeper 维护哪些服务处于活跃状态并且是可用的,并提供服务故障通知。Zookeeper 使用一致性协议来保证分布式状态的一致性。请注意,需要有三到五台机器来保证一致性协议。

2. 组件如何协同工作

Zookeeper 用于协调分布式系统成员的共享状态信息。RegionServer 与 Active HMaster 通过会话与 Zookeeper 进行连接。Zookeeper 通过心跳为活跃会话维护一个临时节点。

每个 RegionServer 都会创建一个临时节点。HMaster 监视这些节点以发现可用的 RegionServer,并且还监视这些节点是否出现故障。Zookeeper 使用第一个发现的 HMaster,通创建一个临时节点来确保只有它处于 Active 状态。Active HMaster 将心跳发送到 Zookeeper,非 Active HMaster 则侦听 Active HMaster 的故障通知。

如果 RegionServer 或 Active HMaster 无法发送心跳,则会导致会话过期,并会删除相应的临时节点。Active HMaster 侦听 RegionServer,并恢复发生故障的 RegionServer。非 Active HMaster 侦听 Active HMaster 是否出现故障,如果 Active HMaster 发生故障,那么一个非 Active HMaster 会变为 Active 状态。

3. HBase首次读写

HBase 中有一个特殊的目录表(META表),保存了集群中所有 Region 的位置。META 表的位置存储在 Zookeeper 中。

如下是客户端第一次读写时发生的情况:

  • 客户端从 ZooKeeper 中获取负责管理 META 表的 RegionServer。
  • 客户端查询 META 服务来获取我们要访问的 RowKey 所对应的 RegionServer。客户端会将该信息与 META 表位置进行缓存。
  • 客户端查询 RowKey 所在的 RegionServer 并从中获取行。

为了以后的读请求,客户端会缓存检索的 META 表位置以及之前读取的 RowKey。在后面,我们一般不再需要查询 META 表,除非由于 Region 迁移导致缓存失效,然后会重新查询并更新缓存。

4. HBase Meta表

元数据表

META 表是一个 HBase 表,保存了系统中所有的 Region。META 表类似一棵 B 树。META 表结构如下所示:

  • Key:Region 开始键,Region ID
  • Value:RegionServer

5. RegionServer组成

RegionServer 在 HDFS 数据节点上运行,并包含如下组件:

  • WAL:预写日志是分布式文件系统上的一个文件。用于存储还没持久化存储的新数据,并在出现故障时可以进行恢复。
  • BlockCache:读缓存,将经常读取的数据存储在内存中。内存不足时删除最近最少使用的数据。
  • MemStore:写缓存,存储还没写入磁盘的新数据。在写入磁盘之前先对其进行排序。每个 Region 的每个列族都有一个 MemStore。
  • HFile:将行以有序的 KeyValue 形式存储在磁盘上。

5.1 HBase写入步骤

当客户端发出 Put 请求时,第一步是将数据写入预写日志 WAL 中:

  • 新内容将追加到 WAL 文件(存储在磁盘上)末尾。
  • WAL 用于恢复服务器崩溃时还没持久化的数据。

第二步是将数据写入 WAL 后,将其存储在 MemoryStore 中(写缓存)。然后将 Put 请求的确认返回给客户端。

5.2 MemStore

MemStore 将更新以有序的 KeyValue 形式存储在内存中(与存储在 HFile 中相同)。每个列族只有一个 MemStore。

5.3 Region Flush

当 MemStore 累积足够多的数据时,整个有序集都会被写入到一个新的 HFile。随着时间的流逝,每个 MemStore 会有多个 HFile,因为存储在 MemStore 中的 KeyValue 会不断地刷写到磁盘上。HFile 是存储实际的单元值或 KeyValue 实例的地方。

请注意,这也是为什么 HBase 中的列族数量受到限制的一个原因。每个列族都有一个 MemStore。当 MemStore 满之后就会刷写到磁盘。同时还会保存最后写入的序列号,以便系统知道到目前为止所持久化的内容。

最大序列号存储为每个 HFile 中的一个 meta 字段,以反映持久化在何处结束以及在何处继续。当 Region 启动时,会读取序列号,并将最大的序列号用作新编辑内容的序列号。

5.4 HFile

数据以有序的 key/values 形式存储在 HFile 中。当 MemStore 累积足够多的数据时,就会将整个有序 KeyValue 集顺序写入到一个新的 HFile 中。顺序写入的方式会非常快,因为它避免了移动磁盘驱动器磁头。

5.4.1 HFile索引

HFile 包含多层索引,从而使 HBase 无需读取整个文件即可查找数据。多级索引类似一个 B+ 树:

  • 键值对以升序存储
  • Rowkey 对应索引指向 64KB 大小的数据块
  • 每个数据块都有自己的叶子索引
  • 每个数据块的最后一个键放在中间索引中
  • 根索引指向中间索引

三种索引类型:(1) Root Index:根索引 (2) Intermediate Index:中间索引 (3) Leaf Index:叶子索引

Trailer 指向 meta 数据块,并将数据写入到持久化文件的末尾。Trailer 还包含诸如布隆过滤器和时间范围之类的信息。布隆过滤器可以帮助我们跳过不包含在特定行键的文件。时间范围信息可以帮助我们跳过不在读取的时间范围内的文件。

刚才我们讨论的索引,在 HFile 被打开时会被载入内存,这样数据查询只要一次磁盘查询。

6. 读取合并

我们已经看到,对应于一行的 KeyValue 单元可以存储在多个位置,已经持久化的行单元位于 HFiles 中,最近更新的单元位于 MemStore 中,而最近读取的单元位于 BlockCache 中。因此,当我们读取一行时,系统如何获取对应的单元返回?读取操作需要通过以下步骤合并来 BlockCache、MemStore 以及 HFiles 中的键值:

  • 首先,扫描程序在 BlockCache(读缓存) 中查找行单元。最近读取过的键值存储在这里,并且当内存不足时需要删除最近最少使用的数据。
  • 接下来,扫描程序在 MemStore(写缓存) 中查找,这里包含最近的写入。
  • 如果扫描程序在 MemStore 和 BlockCache 中没有找到所有行单元,那么 HBase 将使用 BlockCache 索引和布隆过滤器将 HFiles 加载到内存中,这里可能包含目标行单元。

如前所述,每个 MemStore 可能有多个 HFile,这意味着对于读操作而言,可能必须查找多个文件,这可能会影响性能。这称为读取放大。

7. HBase压缩

7.1 Minor压缩

HBase 会自动选择一些较小的 HFile,将它们重写合并为一些较大的 HFile。 此过程称为 Minor 压缩。这样通过将比较多且较小的文件重写为比较少但较大的文件可以减少存储文件的数量。

7.2 Major压缩

Major 压缩会将一个 Region 中的所有 HFile 合并重写为每个列族一个 HFile,在此过程中会删除已删除或已过期的单元。这样可以提高读取性能,但是由于 Major 压缩会重写所有文件,因此这个过程可能会发生大量磁盘 I/O 和网络流量。这称为写放大。

Major 压缩可以调整为自动运行。由于写放大,通常需要在周末或晚上进行 Major 压缩。Major 压缩还可以使由于服务器故障或负载均衡而变成远程文件重新回到 RegionServer 数据本地性。

8. Region拆分

让我们快速了解一下 Region:

  • 一个表可以水平拆分为一个或多个 Region。Region 在开始键和结束键之间包含连续的,有序的行
  • 每个 Region 默认大小为1GB
  • 表的 Region 由 RegionServer 提供给客户端
  • RegionServer 大约可以管理 1,000个 Region(可能属于同一表或不同表)

最初,每个表只有一个 Region。当 Region 过大时,会分为两个子 Region。两个子 Region(代表原始 Region 的一半)可以在同一 RegionServer 上并行打开,拆分时会报告给 HMaster。出于负载均衡的原因,HMaster 可能会将新 Region 迁移到其他服务器。

8.1 读取负载均衡

拆分最初发生在同一个 RegionServer 上,但是出于负载均衡的考虑,HMaster 可能会将新 Region 迁移至其他 RegionServer。这会导致新的 RegionServer 从远程 HDFS 节点上访问数据,需要等到 Major 压缩时才将数据文件移动到新的 RegionServer 的本地节点上。HBase 数据在写入时是在本地节点的,但是在迁移 Region 时(用于负载均衡或故障恢复),会丢失数据本地性。

Region 迁移只是逻辑上的迁移,数据还在原先的 RegionServer 上,只是 Region 交给新的 RegionServer 管理。

9. HDFS数据备份

所有读写请求都来自/发往主节点。HDFS 会备份 WAL 和 HFile 数据块。HFile 数据块备份会自动进行。HBase 依赖 HDFS 来保证存储文件的数据安全。当数据写入 HDFS 时,一个副本写入本地,然后将其备份到辅助节点,而第三个副本被写入第三节点。

WAL 文件和 HFiles 被持久化到磁盘上并被备份,那么 HBase 如何恢复在 MemStore 中更新但未持久化到 HFiles 中的数据?答案请参见下一部分。

10. 故障恢复

当 RegionServer 发生故障时,崩溃的 Region 会不可用,直到执行检测和恢复步骤时才可以使用。当失去 RegionServer 心跳信号时,Zookeeper 认定为节点发生故障。然后,HMaster 将被告知 RegionServer 发生故障。

当 HMaster 检测到 RegionServer 崩溃时,HMaster 将发生崩溃的 RegionServer 中的 Region 重新分配给 Active RegionServer。为了恢复崩溃的 RegionServer 中的 MemStore 内容(还未刷写到磁盘)。HMaster 将属于崩溃 RegionServer 的 WAL 拆分为不同的文件,并将这些文件存储在新 RegionServer 的数据节点中。然后每个 RegionServer 回放各自拿到的拆分的 WAL,以重建该 MemStore。

11. 数据恢复

WAL 文件包含一系列编辑,其中每一个编辑都表示一个 Put 或 Delete 操作。编辑是按时间顺序写入的,因此,持久化时将内容追加到存储在磁盘上的 WAL 文件的末尾。

如果数据仍在内存中但未持久化保存到 HFile 时发生故障,该怎么办?重放 WAL。通过读取 WAL,将包含的编辑内容写入到当前的 MemStore 并对其进行排序来完成 WAL 的重放。最后,刷写 MemStore 以将更改写入 HFile。

欢迎关注我的公众号和博客:

原文:An In-Depth Look at the HBase Architecture

赏几毛白!