1 HBase 简介
1.1 HBase 的定义
HBase 是一种列式存储的 NoSQL 数据库,专为处理和存储海量数据而设计,其理论基础来源于 Google 的 BigTable 论文。HBase 被视为一个可靠性高、性能优越且可扩展的分布式存储系统。
HBase 的数据存储依赖于 HDFS,后者以其高容错性而著称,旨在低成本硬件上运行。基于 Hadoop 的架构使 HBase 自带强大的扩展能力和高吞吐量。
HBase 使用键值存储方法,允许在数据量增加时保持查询性能的稳定。作为一个面向列的数据库,HBase 能够将表中部分字段独立存储在不同的机器上,从而有效分散负载。但这种复杂的存储结构也意味着,即便数据量较小,性能提升也不会特别显著。
HBase 并非永远快速,它的优势在于处理大规模数据时性能下降不明显。通常,HBase 适用于以下两种情况:
单表数据量超过千万且并发请求较高。
数据分析需求较低,或者不需要实时性和灵活性。
1.2 HBase 的发展背景
MySQL 是一种关系型数据库,许多人在学习数据库时首先接触到它。然而,MySQL 在性能上存在瓶颈,通常单个表的行数不应超过500万,文件大小也不应超过2GB。
以互联网公司的核心用户表为例,当数据量达到千万或亿级时,尽管可以通过优化来加速查询,但单条数据检索的耗时往往超出预期。例如,在查询用户ID为1的用户名称时,系统返回的是“aa”。但由于 MySQL 以行为单位存储,查询名称时需要访问整行数据,包括年龄和电子邮件等字段,这将影响查询效率。

当表的列数过多时,我们称之为宽表,通常的优化方法是进行竖直拆分:

在这种情况下,查找名称时只需访问 User_basic 表,避免了多余字段的干扰,从而提高查询效率。如果一张表的行数过多,查询效率也会受到影响,这样的表被称为高表,通常可以通过水平拆表来提升效率:

水平拆分常用于生成大量日志信息的场景,可以按日或按月进行拆分,从而将高表转变为矮表。
上述拆分方法似乎解决了宽表和高表的问题,但当公司业务发生变化,例如需要添加用户的微信字段时,如何调整表结构呢?最简单的方式是直接新增一列:

然而,并非所有用户都有微信号,这就需要考虑是设置默认值还是采取其他措施。如果需要扩展更多列,但并非所有用户都有这些属性,扩展的复杂性将增加。这时可以考虑使用 JSON 格式的字符串,将可选信息汇总,且属性字段可动态扩展:

虽然这种数据存储方式看似不错,但 MySQL 的一个致命缺陷是当数据量达到一定阈值时,无论怎样优化,性能始终无法满足需求。而在大数据领域,数据量常常达到 PB 级,这种存储方式显然无法有效应对。HBase 针对上述问题提供了优雅的解决方案。
1.3 HBase 的设计理念
针对高表、宽表以及数据列动态扩展的问题,HBase 结合了水平切分、垂直切分和列扩展的方法。
在设计表时,为了解决宽表和列扩展的问题,可以直接使用 JSON 格式进行存储:

那么高表该如何解决呢?可以将表按行切分为多个区域(partition),每个区域存储部分行:

在解决了高表、宽表和动态扩展列的问题后,如果数据量大但查询速度不足,该怎么办?可以利用缓存,将查询结果存入缓存,下次直接从缓存中获取数据。对于数据插入,可以将待插入的数据存入缓存,由数据库直接从缓存中获取并插入,这样程序不需等待数据插入成功,从而提高并行处理能力。
使用缓存时,需要考虑如果服务器宕机,缓存数据未及时插入数据库会导致数据丢失。可以借鉴 Redis 的持久化策略,添加操作日志以记录插入操作,以便在宕机后重启时恢复数据。这一架构设计如下:

这就是 HBase 的基本实现思路。接下来将深入探讨 HBase 的设计细节。
2 HBase 概述
2.1 HBase 的主要特点
海量存储
HBase 适合存储 PB 级别的海量数据,能够在几十到百毫秒内返回查询结果。
列式存储
数据在 HBase 中是按列族存储的,每个列族可以包含多个列,创建表时必须指定列族。
高并发处理
在高并发的情况下,HBase 的单个 IO 延迟变化不大,能够提供高并发和低延迟的服务。
稀疏性
HBase 的列具有灵活性,可以在列族中自由添加任意数量的列,未存储数据的列不会占用存储空间。
易于扩展
基于 RegionServer 的扩展,通过水平添加 RegionServer 机器,提升 HBase 的处理能力。
基于存储的扩展(HDFS).
2.2 HBase 的逻辑结构
在逻辑层面,HBase 的存储模型如下:

表(Table):
每个表由一个或多个列族组成,数据的属性如姓名、年龄和 TTL(超时时间)等都在列族中定义。定义列族后,表初始为空,只有添加数据行后,表才会有数据。
列(Column):
HBase 中的每个列由列族(Column Family)和列限定符(Column Qualifier)共同定义,例如 info:name 和 info:age。创建表时只需指定列族,而列限定符可以动态添加。
列族(Column Family):
多个列组成一个列族。创建表时无需预先创建列,在 HBase 中列是可变的!唯一需要确定的是列族的数量和名称,这在创建表时就已决定。许多属性如数据过期时间、数据块缓存和压缩设置都定义在列族上。
HBase 会尽量将相同列族的多个列数据存储在同一台机器上。
行(Row):
一行包含多个列,这些列根据列族进行分类。行的数据从表定义的列族中选取。由于 HBase 是面向列的数据库,一行的数据可以分布在不同的服务器上。
行键(RowKey):
RowKey 类似于 MySQL 中的主键,在 HBase 中每个 RowKey 必须唯一,并且按字典顺序排序。如果用户未指定 RowKey,系统会自动生成一个不重复的字符串。查询数据时只能通过 RowKey 检索,因此 RowKey 的设计至关重要。
区域(Region):
Region 是若干行数据的集合。HBase 中的 Region 会根据数据量动态分裂,Region 基于 HDFS 实现,所有的存取操作都通过 HDFS 客户端完成。同一 RowKey 的数据不会被拆分到多个 Region 服务器上。
RegionServer:
RegionServer 是存放 Region 的服务,负责管理和维护 Region。
2.3 HBase 的物理存储
上述只是基本的逻辑结构,底层的物理存储结构更为关键,详见下图:

命名空间(Namespace):
命名空间类似于关系型数据库中的数据库概念,每个命名空间下可以有多个表。HBase 预设有两个命名空间:hbase 和 default,hbase 中存放 HBase 内置表,default 是用户的默认命名空间。
时间戳(Timestamp):
时间戳用于区分数据的不同版本,每条记录写入时如果未指定时间戳,系统会自动添加写入时间。读取数据时一般只提取最新版本的数据。这是因为 HBase 的底层 HDFS 支持增删查,但不支持修改。
单元格(Cell):
单元格是通过 {Rowkey, Column Family: Column Qualifier, Timestamp} 唯一确定的单元。单元格中的数据以字节码形式存储,没有类型限制。
3 HBase 的底层架构

3.1 客户端(Client)
客户端提供访问 HBase 的接口,并维护相应的缓存,以加速 HBase 的访问,例如缓存元数据。
3.2 Zookeeper
HBase 通过 Zookeeper 实现主节点的高可用性、RegionServer 的监控、元数据的入口以及集群配置。
