HBase的表结构
初次接触HBase,可能看到以下描述会懵:“基于列存储”,“稀疏MAP”,“RowKey”,“ColumnFamily”。
其实没那么高深,我们需要分两步来理解HBase, 就能够理解为什么HBase能够“快速地”“分布式地”处理“大量数据”了。
- 内存结构
- 文件存储结构
先介绍几个名称概念
行键RowKey:
- 行键,类似mysql中的主键,Table中的记录按照Row Key排序,行键是表结构的一部分;
- 由于Hbase只支持3中查询方式:
1、基于Rowkey的单行查询
2、基于Rowkey的范围扫描
3、全表扫描 - 因此,Rowkey对Hbase的性能影响非常大,Rowkey的设计就显得尤为的重要。
- rowkey 行键可以是任意字符串(最大长度是 64KB,实际应用中长度一般为 10-100bytes),最好是 16。
- 在 HBase 内部,rowkey 保存为字节数组。
- rowkey是行的唯一标识,相同行键的数据属于同一行
- HBase 会对表中的数据按照 rowkey 升序排序 (字典顺序)
- 【注意】字典序对int排序的结果是1,10,100,11,12,13,14,15,16,17,18,19,2,20,21,…,9,91,92,93,94,95,96,97,98,99。要保持整形的自然序,行键必须用0作左填充。
列族/列簇ColumnFamily
- Hbase通过列族划分数据的存储,列族下面可以包含任意多的列,实现灵活的数据存取。就像是家族的概念,我们知道一个家族是由于很多个的家庭组成的。列族也类似,列族是由一个一个的列组成(任意多)。
- Hbase表的创建的时候就必须指定列族。就像关系型数据库创建的时候必须指定具体的列是一样的。
- Hbase的列族不是越多越好,列族越多,在取一行数据需要参与IO、搜寻的文件就越多;官方推荐的是列族最好小于或者等于3。我们使用的场景一般是1个列族。
- 一个列族会储存一个物理文件;
- 通常将具有相同IO(读写)属性的列放在同一个列族下,IO属性即经常在一起查询的字段,由具体的实际业务中决定;
列Column
- 列为每一行的列名和对应的值;可以理解为mysql的列;
- 一个列族包含一个或多个列;列族是表结构的一部分,而列不是;
- 定位一个列,必须指定列族;
- 列名都以列族作为前缀,如:courses:history,courses:math;都属于courses这个列族;
单元格cell
- HBase 中通过 rowkey 和 columns 确定的为一个存储单元称为 cell;
- 每个 cell 都保存着同一份数据的多个版本。版本通过时间戳来索引。
- 由{rowkey, column( = + ), version} 唯一确定的单元。 Cell 中的数据是没有类型的,全部是字节码形式存贮。
时间戳TimeStamp/版本信息Version
- 每一个列存储的数据 可以存储多个版本的数据,存储每一个列数据的时候 默认添加一个时间戳信息 这个时间戳信息就是数据版本;即在Hbase中使用不同的timestame来标识相同rowkey行对应的不通版本的数据。
- 时间戳的类型是 64 位整型。时间戳可以由 hbase(在数据写入时自动)赋值,此时时间戳是精确到毫秒的当前系统时间。
- 时间戳也可以由 客户显式赋值。如果应用程序要避免数据版本冲突,就必须自己生成具有唯一性的时间戳。 每个 cell 中,不同版本的数据按照时间倒序排序,即最新的数据排在最前面。
- 为了避免数据存在过多版本造成的的管理 (包括存贮和索引)负担,hbase 提供了两种数据版 本回收方式,用户可以针对每个列簇进行设置。
保存数据的最后 n 个版本
保存最近一段时间内的版本(设置数据的生命周期 TTL)。
通过对比传统关系型数据库来了解内存结构
表1:HBase和RDBMS区别
表2:数据在 RDBMS 中的排布示例
表3:数据在 HBase 中的排布(逻辑上)
表4:数据在 HBase 中的排布(物理上)
图5:Hbase 中逻辑上数据的排布与物理上排布的关联
上图我们看到 Row1 到 Row5 的数据分布在两个 CF 中,并且每个 CF 对应一个 HFile。并且逻辑上每一行中的一个单元格数据,对应于 HFile 中的一行;
然后当用户按照 Row-key 查询数据的时候,HBase 会遍历两个 HFile,通过相同的 Row-Key 标识,将相关的单元格组织成行返回,这样便有了逻辑上的行数据。
图6:行存储和列存储数据读取过程
结合图5和图6,可以很清楚地看到,行式存储下一张表的数据都是放在一起的,但列式存储下都被分开保存了。所以它们就有了如下这些优缺点:
HBase集群结构
图7:HBase集群结构
region
- 是 HBase 中对表进行切割的单元,由 regionserver 负责管理
zookeeper
- 协调整个HBase的主从节点,负责主从节点之间的选举,保证任何时候,集群中只有一个master;
- 存贮所有Region的寻址入口
- 实时监控Region Server的状态,将Region server的上线和下线信息实时通知给Master
- 存储Hbase的schema,包括有哪些table,每个table有哪些column family
Master
- 是HBase 的主节点,负责整个集群的状态感知,负载均衡分配给Region Serve、负责用户表的元数据(schema)管理(可以配置多个用来实现 HA) ,Master 负载压力相对于 hdfs 的 namenode 会小很多;
- Master主要作用还在于通过HMaster维护系统表-ROOT-,.META.,记录regionserver所对应region变化信息。此外还负责监控处理当前hbase cluster中regionserver状态变化信息。
-
【备注】 -ROOT-和.META
① .META.:记录了用户表的Region信息,.META.可以有多个regoin
② -ROOT-:记录了.META.表的Region信息,-ROOT-只有一个region
Zookeeper中记录了-ROOT-表的location
Client访问用户数据之前需要首先访问Zookeeper,然后访问-ROOT-表,接着访问.META.表,最后才能找到用户数据的位置去访问:
RegionServer - RegionServer主要负责响应用户I/O请求,是HBase 中真正负责管理 region 的服务器,向HDFS文件系统中读写数据,是HBase中最核心的模块;
- 每一台 regionserver 会管理很多的 region,同一个 regionserver 上面管理的所有的region 不属于同一张表;
HDFS
- 用来存储 hbase 的系统文件,或者表的 region
HBase工作原理
- 首先我们需要知道 HBase 的集群是通过 Zookeeper 来进行机器之前的协调,也就是说 HBase Master 与 Region Server 之间的关系是依赖 Zookeeper 来维护。
- 当一个 Client 需要访问 HBase 集群时,Client 需要先和 Zookeeper 来通信,然后才会找到对应的 Region Server。
- 每一个 Region Server 管理着很多个 Region。对于 HBase 来说,Region 是 HBase 并行化的基本单元。因此,数据也都存储在 Region 中。这里我们需要特别注意,每一个 Region 都只存储一个 Column Family 的数据,并且是该 CF 中的一段(按 Row 的区间分成多个 Region)。
- Region 所能存储的数据大小是有上限的,当达到该上限时(Threshold),Region 会进行分裂,数据也会分裂到多个 Region 中,这样便可以提高数据的并行化,以及提高数据的容量。
- 每个 Region 包含着多个 Store 对象。每个 Store 包含一个 MemStore,和一个或多个 HFile。MemStore 便是数据在内存中的实体,并且一般都是有序的。
- 当数据向 Region 写入的时候,会先写入 MemStore。当 MemStore 中的数据需要向底层文件系统倾倒(Dump)时(例如 MemStore 中的数据体积到达 MemStore 配置的最大值),Store 便会创建 StoreFile,而 StoreFile 就是对 HFile 一层封装。所以 MemStore 中的数据会最终写入到 HFile 中,也就是磁盘 IO。由于 HBase 底层依靠 HDFS,因此 HFile 都存储在 HDFS 之中。这便是整个 HBase 工作的原理简述。
进阶问题:那么在 HBase 的工作过程中,如何保证数据的可靠性呢?
HBase 中的 HLog 机制是 WAL 的一种实现,而 WAL(一般翻译为预写日志)是事务机制中常见的一致性的实现方式。每个 Region Server 中都会有一个 HLog 的实例,Region Server 会将更新操作(如 Put,Delete)先记录到 WAL(也就是 HLog)中,然后将其写入到 Store 的 MemStore,最终 MemStore 会将数据写入到持久化的 HFile 中(MemStore 到达配置的内存阀值)。这样就保证了 HBase 的写的可靠性。
如果没有 WAL,当 Region Server 宕掉的时候,MemStore 还没有写入到 HFile,或者 StoreFile 还没有保存,数据就会丢失。或许有的读者会担心 HFile 本身会不会丢失,这是由 HDFS 来保证的。在 HDFS 中的数据默认会有 3 份。因此这里并不考虑 HFile 本身的可靠性。
进阶问题: HBase 是否可以替代原有的 RDBMS?
我们必须时刻谨记——HBase 并不适合所有问题,其设计目标并不是替代 RDBMS,而是对 RDBMS 的一个重要补充,尤其是对大数据的场景。当需要考量 HBase 作为一个备选项时,我们需要进行如下的调研工作:
首先,要确信有足够多数据,如果有上亿或上千亿行数据,HBase 才会是一个很好的备选。
其次,需要确信业务上可以不依赖 RDBMS 的额外特性,例如,列数据类型, 二级索引,SQL 查询语言等。再而,需要确保有足够硬件。且不说 HBase,一般情况下当 HDFS 的集群小于 5 个数据节点时,也干不好什么事情 (HDFS 默认会将每一个 Block 数据备份 3 分),还要加上一个 NameNode。
以下我给了一些使用 HBase 时候对表格设计的一些建议,读者也可以理解背后的含义。不过我并不希望这些建议成为使用 HBase 的教条,毕竟也有不尽合理的地方。
首先,一个 HBase 数据库是否高效,很大程度会和 Row-Key 的设计有关。因此,如何设计 Row-key 是使用 HBase 时,一个非常重要的话题。随着数据访问方式的不同,Row-Key 的设计也会有所不同。不过概括起来的宗旨只有一个,那就是尽可能选择一个 Row-Key,可以使你的数据均匀的分布在集群中。
这也很容易理解,因为 HBase 是一个分布式环境,Client 会访问不同 Region Server 获取数据。如果数据排布均匀在不同的多个节点,那么在批量的 Client 便可以从不同的 Region Server 上获取数据,而不是瓶颈在某一个节点,性能自然会有所提升。对于具体的建议我们一般有几条:
当客户端需要频繁的写一张表,随机的 RowKey 会获得更好的性能。
当客户端需要频繁的读一张表,有序的 RowKey 则会获得更好的性能。
对于时间连续的数据(例如 log),有序的 RowKey 会很方便查询一段时间的数据(Scan 操作)。
上面我们谈及了对 Row-Key 的设计,接着我们需要想想是否 Column Family 也会在不同的场景需要不同的设计方案呢。答案是肯定的,不过 CF 跟 Row-key 比较的话,确实也简单一些,但这并不意味着 CF 的设计就是一个琐碎的话题。在 RDBMS(传统关系数据库)系统中,我们知道如果当用户的信息分散在不同的表中,便需要根据一个 Key 进行 Join 操作。而在 HBase 中,我们需要设计 CF 来聚合用户所有相关信息。简单来说,就是需要将数据按类别(或者一个特性)聚合在一个或多个 CF 中。这样,便可以根据 CF 获取这类信息。上面,我们讲解过一个 Region 对应于一个 CF。那么设想,如果在一个表中定义了多个 CF 时,就必然会有多个 Region。当 Client 查询数据时,就不得不查询多个 Region。这样性能自然会有所下降,尤其当 Region 夸机器的时候。因此在大多数的情况下,一个表格不会超过 2 到 3 个 CF,而且很多情况下都是 1 个 CF 就足够了。