跳到主要内容

HBase介绍以及对比MongoDB

· 阅读需 11 分钟

HBase 全称 Hadoop Database,是一种高可靠性、高性能、面向列的可扩展分布式存储系统,可以在廉价的 PC 服务器上构建大规模结构化存储集群。

HBase 的目标是存储和处理大量数据,仅使用标准硬件配置即可处理包含数千行和列的海量数据,可支撑 PB 级别的数据存储、亿级 QPS 的查询。

它适用于实时性要求不高的业务场景。HBase 存储的是 Byte 数组,不关心数据类型,从而允许动态、灵活的数据模型。

架构解析

HBase 由 HMaster 和 HRegionServer 组成,遵循主从服务器体系结构。HBase 将逻辑表划分为多个数据块 HRegion,并将它们存储在 HRegionServer 中。

HMaster 负责管理所有 HRegionServer。它本身不存储任何数据,只保存数据到 HRegionServer 的映射(元数据)。

集群中的所有节点由 Zookeeper 协调,用于处理 HBase 运行期间可能遇到的各种问题。HBase 的基本架构如下所示:

客户端: 使用 HBase 的 RPC 机制与 HMaster 和 HRegionServer 通信,提交请求并获得结果。管理类操作,客户端通过 HMaster 执行 RPC;数据读写操作,客户端通过 HRegionServer 执行 RPC。

Zookeeper: 集群中每个节点的状态信息都注册到 Zookeeper,HMaster 借此可以随时感知每个 HRegionServer 的健康状态,同时也避免了 HMaster 的单点故障。

HMaster: 管理所有 HRegionServer,告诉它们需要维护哪些 HRegion,并监视所有 HRegionServer 的运行状况。当新的 HRegionServer 向 HMaster 注册时,HMaster 告诉它等待数据分配;当某个 HRegion 失效时,HMaster 会把它负责的所有 HRegion 标记为未分配,再分配给其他 HRegionServer。HMaster 没有单点问题——HBase 可以启动多个 HMaster,通过 Zookeeper 的选举机制保证集群中始终有一个 HMaster 在运行,从而提高集群的可用性。

HRegion: 当表的大小超过预设值时,HBase 会自动将表划分为不同的区域,每个区域包含表中所有行的一个子集。对用户来说,每个表就是一个数据集合,用主键(RowKey)加以区分;从物理上讲,一个表被分为多个块,每个块就是一个 HRegion,用「表名 + 开始/结束主键」来区分。一个 HRegion 保存表中一段连续的数据,完整的表数据则存储在多个 HRegion 中。

HRegionServer: HBase 的所有数据通常存储在底层的 HDFS 中,用户通过 HRegionServer 访问这些数据。一般集群的一个节点上只运行一个 HRegionServer,每个 HRegion 也只由一个 HRegionServer 维护。HRegionServer 主要负责响应用户 I/O 请求,向 HDFS 文件系统读写数据,是 HBase 中的核心模块。HRegionServer 内部管理一系列 HRegion 对象,每个 HRegion 对应逻辑表中一段连续的数据。HRegion 由多个 HStore 组成,每个 HStore 对应逻辑表中一个列族的存储。可以看出,每个列族就是一个集中的存储单元,因此为了提高操作效率,最好把具有相同 I/O 特性的列放在同一个列族中。

HStore: 它是 HBase 存储的核心,由 MemStore 和 StoreFile 组成。MemStore 是内存缓冲区,用户写入的数据会先放入 MemStore;当 MemStore 写满时,会 Flush 成一个 StoreFile(底层实现是 HFile)。当 StoreFile 文件数量增加到某个阈值时,会触发 Compact 合并操作,将多个 StoreFile 合并为一个,并在合并过程中执行版本合并和数据删除。因此可以看出,HBase 只是不断追加数据,所有的更新和删除都在后续的 Compact 过程中完成,用户的写入操作进入内存后即可立即返回,保证了 HBase 的写入性能。StoreFile 经过不断 Compact 会逐渐形成越来越大的文件;当单个 StoreFile 的大小超过某个阈值时,会触发 Split 操作,把当前 HRegion 拆分为 2 个 HRegion,父 HRegion 下线,HMaster 把两个子 HRegion 分配给相应的 HRegionServer,从而把原 HRegion 的负载压力分流到这两个 HRegion 上。

HLog: 每个 HRegionServer 都有一个 HLog 对象,它是实现预写日志(Write-Ahead Log)的类。用户每次向 MemStore 写入数据时,也会把一份数据副本写入 HLog 文件。HLog 文件定期滚动并删除旧文件(其中的数据已持久化到 StoreFile)。当 HMaster 通过 Zookeeper 检测到某个 HRegionServer 意外终止时,会先处理遗留的 HLog 文件,按不同的 HRegion 拆分其中的日志数据,放入对应的 HRegion 目录,然后重新分配这些失效的 HRegion。接管这些 HRegion 的 HRegionServer 在加载过程中会发现有历史 HLog 需要处理,于是把 HLog 中的数据 Replay 到 MemStore,再刷新到 StoreFile,完成数据恢复。

HBase 基于 BigTable 模型,是一个稀疏的、长期存储(在 HDFS 上)、多维、排序的映射表。该表的索引是行关键字、列关键字和时间戳。HBase 的数据都是字符串,没有类型。

HBase 读写过程

下图是 HRegionServer 的数据存储关系图。如上所述,HBase 使用 MemStore 和 StoreFile 来存储对表的更新。数据在更新时首先写入 HLog 和 MemStore,MemStore 中的数据是排序的。

当 MemStore 累积到某个阈值时,会创建一个新的 MemStore,旧的 MemStore 被加入 Flush 队列,由单独的线程刷新到磁盘上成为 StoreFile。同时,系统会在 Zookeeper 中记录一个 CheckPoint,表明该时间点之前的数据变更已经持久化。当系统发生意外时,MemStore 中的数据可能丢失,这种情况下就用 HLog 来恢复 CheckPoint 之后的数据。

StoreFile 是只读的,一旦创建便无法修改,因此 HBase 的更新其实是一种追加操作。当 HStore 中的 StoreFile 数量达到某个阈值时,会执行合并操作,把对相同 key 的修改合并成一个大的 StoreFile;当 StoreFile 的大小达到某个阈值时,又会把它拆分为两个 StoreFile。

写操作流程

  • 步骤 1:客户端通过 Zookeeper 的调度向 HRegionServer 发送写数据请求,将数据写入 HRegion。
  • 步骤 2:数据写入 HRegion 的 MemStore,直到 MemStore 达到预设阈值。
  • 步骤 3:MemStore 中的数据被 Flush 成 StoreFile。
  • 步骤 4:随着 StoreFile 文件数量增加到特定阈值,触发 Compact 合并操作,将多个 StoreFile 合并为一个,同时进行版本合并和数据删除。
  • 步骤 5:StoreFile 通过不断的 Compact 操作,逐渐形成越来越大的 StoreFile。
  • 步骤 6:单个 StoreFile 的大小超过阈值后,触发 Split 操作,把当前 HRegion 拆分为两个新的 HRegion。父 HRegion 下线,新拆分出的两个子 HRegion 由 HMaster 分配给相应的 HRegionServer,从而把原 HRegion 的压力分流出去。

读操作流程

  • 步骤 1:客户端访问 Zookeeper,找到 -ROOT- 表,进而获得 .META. 表的信息。
  • 步骤 2:从 .META. 表中查询目标数据所在的 HRegion 信息,找到对应的 HRegionServer。
  • 步骤 3:通过 HRegionServer 获取需要查找的数据。
  • 步骤 4:HRegionServer 的内存分为 MemStore 和 BlockCache 两部分,MemStore 主要用于写数据,BlockCache 主要用于读数据。读请求会先到 MemStore 中查数据,查不到再查 BlockCache,还查不到才访问 StoreFile,并把读取的结果放入 BlockCache。

HBase 使用场景

半结构化或非结构化数据:对于字段无法预先定义好或者比较杂乱的数据结构,很难用固定 schema 建模,适合交给 HBase。如果随着业务增长需要存储更多字段,RDBMS 需要停机维护变更表结构,而 HBase 支持动态增加列。

记录非常稀疏:RDBMS 表的列数是固定的,值为空的列白白浪费存储空间;HBase 中为空的列不占用存储,既节省空间又提高读取性能。

多版本数据:根据 RowKey 和列标识定位到的值可以有任意数量的版本(时间戳不同),因此对需要存储变更历史的数据,用 HBase 非常方便。

大量数据:当数据量越来越大时,RDBMS 会逐渐扛不住,于是先做读写分离——一台主库负责写,多台从库负责读,服务器成本翻倍;压力继续增加,主库扛不住了,就开始分库,把几乎不相关的数据分开部署,一些 join 查询没法用了,需要引入中间层;数据量再进一步增加,单表记录越来越多,查询变得非常慢,又不得不分表(例如按 ID 取模拆成多个表)来减少单表记录数。经历过这个过程的人都知道其中的繁琐。

HBase 就简单多了:只需向集群添加新节点,HBase 就会自动水平拆分,并且与 Hadoop 的无缝集成保证了数据可靠性(HDFS)和高性能的海量数据分析能力(MapReduce)。

HBase 与 MapReduce

HBase 中 Table 与 Region 的关系,和 HDFS 中 File 与 Block 的关系有些相似。由于 HBase 提供了与 MapReduce 交互的 API,例如 TableInputFormat 和 TableOutputFormat,HBase 的数据表可以直接作为 Hadoop MapReduce 的输入和输出,方便了 MapReduce 应用的开发,且无需关注 HBase 系统本身的细节。

数据结构分析

HBase 使用 LSM(Log-Structured Merge)树,传统数据库 InnoDB 使用 B+ 树。

LSM 树 的特点:

  1. 侧重写入
  2. 读需要访问多棵树,IO 次数比 B+ 树多,波动大
  3. 写都是顺序 IO;随机读是随机 IO,顺序读是顺序 IO

优点:

  1. 写性能大幅度提高
  2. 不受 SSD 随机写入放大干扰
  3. 不受空间放大干扰

缺点:

  1. 读性能有牺牲,IO 次数比 B+ 树大
  2. 需要定期 Compaction,对整体网络/磁盘 IO 存在放大

HBase 的优点

  1. 数据按列存储,查询时只访问所涉及的列,大量降低系统 I/O;同一列数据类型一致、空值不存储,可以高效压缩存储。
  2. Key/Value 的存储方式,意味着即便面临海量数据的增长,也几乎不会导致查询性能下降。
  3. 作为列式数据库(相对于传统的行式数据库),当单张表字段很多时,可以将不同的列(以 Region 为单位)分散到不同的服务实例上,分散负载压力。

HBase 的缺点

  1. 原生不支持二级索引,只能通过主键访问;社区实现的二级索引与数据更新之间有时延,会带来头疼的一致性问题
  2. 宽表模型概念拗口,难于理解,并且要求提前建模,不够灵活
  3. 支持的编程语言种类少(Java、Thrift、RESTful API),没有 SQL,只能使用 API
  4. 集群结构复杂,有 8 种不同类型的节点
  5. 数据类型低级,只支持字节流,开发不友好
  6. 无一致性快照功能
  7. 需要定时 Compact,对持续读写场景影响很大
  8. 不支持表的关联操作,因此数据分析是 HBase 的弱项,常见的 group by 或 order by 只能通过编写 MapReduce 来实现

小结

HBase 适合特别大数据量的写入场景:写入数据后,再异步对数据进行分析处理。对应的业务场景是对实时性要求不高、但写入量特别大的场景,如硬件与车载系统的数据监控采集、用户画像等。它主要解决海量存储问题,后续集成数据分析组件,可以完成大数据分析、用户行为、实时推荐、风控等功能。

MongoDB 本文没有做过多介绍。在同等机器集群配置下,MongoDB 的写入能力不如 HBase;但 MongoDB 写入数据时会创建对应的索引,查询速度和可查询的维度都高于 HBase——HBase 查询数据的维度有限,从查询灵活度和各种条件下的查询效率而言,MongoDB 略胜一筹,更适合需要实时查询分析的场景。若要提高 MongoDB 的写入能力,则需要加入更多高性能的集群节点。实际选型时,还是要结合自己的业务场景来分析匹配。

评论 / COMMENTS