一、HBase简介
HBase 是一种分布式、面向列的 NoSQL 数据库,其设计思想来源于 Google 的 Big Table。HBase 能存储并处理海量的数据,仅需使用普通配置的硬件,就能够处理大型数据。HBase 与传统关系型数据库的对比:
本文从 HBase 的逻辑结构、数据结构、物理架构三个方面介绍 HBase 的原理与架构。
二、逻辑结构
- 行键(Rowkey):行键是 HBase 用来检索记录的主键。行键可以是任意字符串,存储时按照行键的字典序排序存储。访问 HBase 中的行有三种方式:(1)通过单个行键访问;(2)通过行键的范围查询访问;(3)使用过滤器进行全表扫描。
- 列族(Column Family):HBase中的每个列都归属于某个列族,列族在使用表之前定义,后续修改成本较大。HBase中列族数量有限制。
- 列(Column):一般都是从属于某个列族,跟列族不一样,列的数量一般的没有强限制的,一个列族当中可以有数百万个列,而且这些列都可以动态添加的。
- 时间戳(Time Stamp):HBase 中同一个数据可以被保存多个版本,版本通过时间戳来索引。
通过 {行键,列族,列,时间戳} 可以唯一确定一个列单元(Cell)并获取数据。和关系型数据库不同的是,HBase 中的数据是没有类型的,都是以字节码形式存储。
Rowkey设计
一条数据的唯一标识就是 rowkey。一个 HBase 数据库是否高效,很大程度会和 rowkey 的设计有关。随着数据访问方式的不同,rowkey 的设计也会有所不同。不过概括起来的宗旨只有一个,那就是尽可能选择一个 rowkey,可以使你的数据均匀的分布在集群中。具体的建议如下:
- 当客户端需要频繁的写一张表,随机的 rowkey 会获得更好的性能。
- 当客户端需要频繁的读一张表,有序的 rowkey 则会获得更好的性能。
- 对于时间连续的数据(例如 log),有序的 rowkey 会很方便查询一段时间的数据(Scan 操作)。
常用的打散 rowkey 的方案:
- 生成随机数、hash、散列值
- 字符串反转
- 字符串拼接
二级索引
HBase 不像 MySQL,它本身是没有二级索引的,只能根据 rowkey 来访问行。二级索引表就是把需要作为索引的列,拼到 rowkey 中,对应的列多了一个主表的 row key。通过上面的方法,确定出二级索引表的 start row key和 end row key,然后根据 filter 和索引表的列,过滤出需要行,取出其中的主表 rowkey 值,再去主表通过单个 rowkey 访问。
三、数据结构
很多数据库或文件系统都使用 B+ 树作为存储数据的数据结构,但是HBase 却使用的是 LSM(Log-Structured Merge Tree)树,这是为什么呢?
B+ 树虽然适合在磁盘中存储,并且从原理上来看它的读速度很快。但是它并非总是顺序读写磁盘,例如它的节点进行分裂操作时在内存中会拆成两个新的页表,存储到磁盘上很可能就是不连续的;或者其他更新插入删除等操作,需要循环利用磁盘快,也会造成不连续问题。这也是 HBase 不使用 B+ 树的原因,不进行优化的话随机 I/O 太多,范围查询和大量随机写时尤其明显。
LSM 树在读写之间作出取舍,通过牺牲部分读性能,使用顺序写来大幅提高写性能,因此适合写多读少,以及大规模数据读取的场景。
LSM 树首先在内存中构建一颗有序的小树,随着小树的逐渐增大,达到一定阈值时会 flush 到磁盘上。所以 LSM 树不像 B+ 树一样是一棵完整的大树,一棵 LSM 树就是一个个 B+ 树合起来。多次flush之后会形成多个数据存储文件,后台线程会按照配置自动将多个文件合并成一个,此时多颗小树就会被合并成一棵大树。但是读取时,由于不知道数据在哪棵小树上,因此必须遍历所有小树(所以才说 LSM 牺牲了部分读的性能),每棵小树内部数据是有序的。查询是先查内存中的部分,再去查磁盘上的部分。
四、物理架构
HBase 是一个分布式数据库,采用的是主从式架构,如上图所示。一个HBase 集群中包括一个 HMaster 和多个 Region Server,通过 Zookeeper 做分布式管理服务。
4.1 主要组件介绍
Zookeeper
HBase 使用 Zookeeper 做分布式管理服务,来维护集群中所有服务的状态。Zookeeper 维护了哪些 servers 是健康可用的,并且在 server 故障时做出通知;Zookeeper 使用一致性协议来保证分布式状态的一致性。
HMaster
- 管理用户的 DDL 操作(创建、删除、更新表)
- 统筹协调所有 Region Server:
(1)启动时分配 Regions,在故障恢复和负载均衡时重分配 Regions
(2)监控集群中所有 Region Server 实例(从 Zookeeper 获取通知信息)
Region Server
Region Server 是管理一批 Region 的机器节点,负责处理数据的读写请求。客户端请求数据时和 Region Server 交互。Region Server 主要包含以下部分:
Region
HBase 表(Table)根据 rowkey 的范围被水平拆分成若干个 Region。每个 Region 都包含了这个 Region 的 start key 和 end key 之间的所有行(row)。Regions 被分配给集群中的某些节点来管理,即 Region Server,由它们来负责处理数据的读写请求。每个 Region Server 大约可以管理 1000 个 regions。
HLog
又叫 WAL(Write Ahead Log )是分布式文件系统上的一个文件,所有写数据会首先被写入该文件中(更新 WAL 是在文件尾部追加的方式,这种磁盘操作性能很高,不会太影响请求的整体响应时间),它被用来做故障恢复。
MemStore
写缓存,在内存中存储了新的还未被持久化到硬盘的数据。注意每个 Region 的每个 Column Family 都会有一个 MemStore。MemStore 中累积了足够多的的数据后,整个有序数据集就会被写入一个新的 HFile 文件到 HDFS 上。整个过程是一个顺序写的操作,速度非常快,因为它不需要移动磁盘头。
HFile
HFile 在硬盘上(HDFS)存储 HBase 数据,以有序 KeyValue 的形式。HBase 为每个 Column Family 都创建一个 HFile,里面存储了具体的 Cell,也即 KeyValue 数据。HFile 使用类似于 B+ 树的索引来查询数据。
下图是数据的逻辑存储与物理存储的映射关系:
4.2 HBase读流程
- Client 先访问 zookeeper,获取是哪一台 Region Server 负责管理 meta table
- 访问该Region Server,根据 namespace、表名和 rowkey 在 meta table 中找到对应的 Region 信息,及其 Region Server。客户端会缓存这个信息,以及 meta table 的位置信息本身
- 先从 MemStore 找数据,如果没有再到 StoreFile 上读(StoreFile 是对 HFile 的封装)
对于以后的的读请求,客户端从可以缓存中直接获取 meta table 的位置信息,以及之前访问过的 rowkey 的位置信息。除非因为 Region 被迁移了导致缓存失效,这时客户端会重复上面的步骤,重新获取相关位置信息并更新缓存。
读合并
我们已经发现,每行(row)的 KeyValue cells 可能位于不同的地方,这些 cell 可能被写入了 HFile;可能是最近刚更新的,还在 MemStore 中;也可能最近刚读过,缓存在 Region Server 的读缓存中。所以,当读一行 row 时,一次 read 操作会将缓存、MemStore 和 HFile 中的 cell 进行合并。
由于每个 MemStore 可能会有多个 HFile,所以一次 read 请求可能需要多读个文件,这可能会影响性能,这被称为读放大(Read Amplification)
4.3 HBase写流程
写数据流程
- Client 向 Region Server 发送写请求(找到 Region Server 的过程与上文相同)
- Region Server 将数据写到 HLog,为了数据的持久化和恢复
- Region Server 将数据写到内存(MemStore),并反馈 Client 写成功
- 数据Flush:当 MemStore 数据达到阈值,将数据有序写入一个新的 HFile 文件到 HDFS 上,将内存中的数据删除,同时删除 HLog 中的历史数据。然后记录最后写入的数据的最大序列号(sequence number)
Minor Compaction
HBase 会自动合并一些小的 HFile,重写成少量更大的 HFiles。这个过程被称为 minor compaction。它使用归并排序算法,将小文件合并成大文件,有效减少 HFile 的数量。
Major Compaction
Major Compaction 合并重写每个 Column Family 下的所有的 HFiles,成为一个单独的大 HFile,在这个过程中,被删除的和过期的 cell 会被真正从物理上删除,这能提高读的性能。但是因为 major compaction 会重写所有的 HFile,会产生大量的硬盘 I/O 和网络开销。这被称为写放大(Write Amplification)。
Major compaction 可以被设定为自动调度。因为存在写放大的问题,major compaction 一般都安排在周末和半夜。
Region 分裂
一开始每个 table 默认只有一个 Region。当一个 Region 逐渐变得很大时,它会分裂(split)成两个子 Region,每个子 Region 都包含了原来 一半的数据,这两个子 Region 并行地在原来这个 Region Server 上创建,这个分裂动作会被报告给 HMaster。出于负载均衡的目的,HMaster 可能会将新的 Region 迁移给其它 Region Server。
Bulkload
除了通过 api 写入数据之外,HBase 还支持 bulkload 将大量数据进行批量导入,其过程是通过启动 MapReduce 任务直接生成 HFile 文件,再将 HFile 文件注册到 HBase。Bulkload 适合如下场景:
- 大量数据一次性加载到 HBase
- 每天定时产出的离线数据
- 使用 put 加载大量数据到HBase速度变慢,且查询速度变慢时
4.4 小结
上文 HBase 的架构,保证了 HBase 的如下优点:
- 强一致性: 当 write 返回时,所有的 reader 都会读到同样的值
- 扩展性:数据变大时 Region 会分裂;使用 HDFS 存储备份数据
- 故障恢复: 使用 Write Ahead Log (类似于文件系统中的日志)
- 与 Hadoop 结合:使用 MapReduce 处理 HBase 数据会非常有效率
但是,它也存在一些不足之处,即业务持续可靠性:
- WAL 回放很慢
- 故障恢复很慢
- Major Compaction 时候 I/O 会飙升