1. 什么是 Hbase ?
HBase 是一个高可靠性、高性能、面向列、可伸缩的分布式存储系统,目标是存储并处理大型的数据。
HBase 是 Google Bigtable 的开源实现,不同之处在于:Google Bigtable 使用 GFS 作为其文件存储系统, HBase 利用 Hadoop HDFS 作为其文件存储系统;Google Bigtable 利用 Chubby 作为协同服务, HBASE 利用 Zookeeper 作为协同服务。
Hbase 是一个面向列存储的分布式存储系统,它的优点在于可以实现高性能的并发读写操作,同时 Hbase 还会对数据进行透明的切分,这样就使得存储本身具有了水平伸缩性。
2. Hbase 的数据模型是什么样的?
HBase 的数据存储模型当中包含以下几个概念:
1 、 row :一行数据包含一个唯一标识 Row-Key 、多个 column 以及对应的值。在 HBase 中,一张表中所有 row 都按照 Row-Key 的字典序(二进制位移计算)由小到大排序。
2 、 column :与关系型数据库中的列不同, HBase 中的 column 由 column family (列簇)以及 qualifier (列名)两部分组成。column family 在表创建的时候需要指定,用户不能随意增减。column family 下可以设置任意多个 qualifier ,因此可以理解为 HBase 中的列可以动态增加。
3 、 cell :单元格,由五元组( row , column , timestamp , type , value )组成的结构,其中 type 表示 Put/Delete 这样的操作类型, timestamp 代表这个 cell 的版本。这个结构在数据库中实际是以 KV 结构存储的,其中( row , column , timestamp , type )是 K , value 字段对应 KV 结构的 V 。
4 、 timestamp :每个 cell 在写入 HBase 的时候都会默认分配一个时间戳作为该 cell 的版本, HBase 支持多版本特性,即同一 Row-Key 、 column 下可以有多个 value 存在,这些 value 使用 timestamp 作为版本号。
HBase 是一个面向列的数据库,在表中它由行排序。表模式定义只能列族,也就是键值对。一个表有多个列族以及 每一个列族可以有任意数量的列 。后续列的值连续存储在磁盘上。表中的每个单元格值都具有时间戳。总之,在一个 HBase :表是行的集合,行是列族的集合,列族是列的集合,列是键值对的集合。
表 1 逻辑存储视图
表 2 物理存储视图
HBase 的逻辑存储视图由行键、时间戳、列族组成一个类似二维表一样的结构,但是在实际存储的时候,数据库存储的数据是以列族为单位进行存储的,是完全将行数据按列族的方式进行分列。
3. Hbase 的存储架构是什么样的?
图 1 Hbase 物理存储架构
Region 是 HBase 中分布式存储和负载均衡的最小单元,不同的 Region 分布到不同的 RegionServer 上,如图 Table1 、 Table2 中均有多个 Region ,这些 Region 分布在不同的 RegionServer 中。Region 虽然是分布式分布式存储的最小单元,但并不是存储的最小单元, Store 是存储的最小单元。Region 由一个或者多个 Store 组成,每个 Store 会保存一个 Column Family ;每个 Store 又由一个 MemStore 或 0 至多个 Hfile 组成;MemStore 存储在内存中, HFile 存储在 HDFS 中。
Hbase 在数据存储的过程当中,涉及到的物理对象分为如下:
1 、 HMaster: 负责 DDL 创建或删除 tables ,同一时间只能有一个 active 状态的 master 存在。
2 、 Zookeeper: 判定 HMaster 的状态,记录 Meta Table 的具体位置;
3 、 Region: 一张 BigTable 的一个分片( Shard ),记录着 key 的开始和结束;
4 、 WAL: 预写日志,持久化且顺序存储,一个 RegionServer 维护一套 WAL ;
5 、 RegionServer: RegionServer 中维护多个 region , region 里包含 MemStore 以及多个 HFiles ;
6 、 MemStore: 对应一个 BigTable 的 Column Family ,存在于文件缓存中,拥有文件句柄;
7 、 BlockCache: 读缓存,存于内存;(Row-Key – > row) ;
8 、 HFiles: 从 MemStore Flush 出来的文件,本身是持久化的,存储于 HDFS 的 DataNode 之中,每次 Flush 生成一个新的 HFile 文件,文件包含有序的键值对序列。
4. Hbase 是如何进行数据的读写?
图 2 Hbase 读写原理图
数据写入流程(如左图):
1 、客户端首先从 Zookeeper 找到 meta 表的 region 位置,然后读取 meta 表中的数据, meta 表中存储了用户表的 region 信息。
2 、根据 namespace 、表名和 Row-Key 信息。找到写入数据对应的 region 信息
3 、找到这个 region 对应的 regionServer ,然后发送请求。
4 、把数据分别写到 HLog ( write ahead log )和 memstore 各一份。
5 、 memstore 达到阈值后把数据刷到磁盘,生成 storeFile 文件。
6 、删除 HLog 中的历史数据。
数据读出流程(如右图):
1 、客户端首先与 Zookeeper 进行连接;从 Zookeeper 找到 meta 表的 region 位置,即 meta 表的数据存储在某一 HRegionServer 上;客户端与此 HRegionServer 建立连接,然后读取 meta 表中的数据;meta 表中存储了所有用户表的 region 信息,我们可以通过 scan 'hbase:meta' 来查看 meta 表信息。
2 、根据要查询的 namespace 、表名和 Row-Key 信息。找到写入数据对应的 region 信息。
3 、找到这个 region 对应的 regionServer 发送请求,并找到相应 region 。
4 、先从 memstore 查找数据,如果没有,再从 BlockCache 上读取。
5 、如果 BlockCache 中也没有找到,再到 StoreFile 上进行读取,从 storeFile 中读取到数据之后,不是直接把结果数据返回给客户端,而是把数据先写入到 BlockCache 中,目的是为了加快后续的查询;然后在返回结果给客户端。
5. Hbase 的存储引擎是什么类型的?
首先需要确定的是 Hbase 的存储引擎是 LSM-Tree (可以参考之前的文章:DB 存储引擎知识系列之三:LSM-Tree 存储引擎详细分解)。
通过之前文章对 LSM-Tree 的介绍,我们知道 LSM-Tree 相比较 B+Tree 而言,最大的特点就是在于通过牺牲部分读性能,利用分层合并的思想,将小树合并为大树,将无序数据合并为有序数据,然后统一刷入磁盘,从而大大提高了写的性能。那么 HBase 套用到 LSM 中, Memstore 就是 LSM 当中的 Memtable ,也就是 C0 层的小树写入, HFiles 就是 LSM 当中的 SSTables ,也就是 Cn 层的合并之后的树的顺序写入。
除此之外 Hbase 在实现 Hbase 的时候,其实还是有自己独到的地方:
1 、 Minor vs Major Compaction :Minor Compaction ,根据配置策略,自动检查小文件,合并到大文件,从而减少碎片文件,然而并不会立马删除掉旧 HFile 文件;Major Compaction ,每个 CF 中,不管有多少个 HFiles 文件,最终都是将 HFiles 合并到一个大的 HFile 中,并且把所有的旧 HFile 文件删除,即 CF 与 HFile 最终变成一一对应的关系。
2 、 BlockCache :除了 MemStore (也就是 MemTable ) 以外, HBase 还提供了另一种缓存结构, BlockCache 。BlockCache 本质上是将热数据放到内存里维护起来,避免 Disk I/O ,当然即使 BlockCache 找不到数据还是可以去 MemStore 中找的,只有两边都不存在数据的时候,才会读内存里的 HFile 索引寻址到硬盘,进行一次 I/O 操作。HBase 将 BucketCache 和 LRUBlockCache 搭配使用,称之为 CombinedBlockCache 。系统在 LRUBlockCache 中主要存储 Index Block ,而将 Data Block 存储在 BucketCache 中。因此一次随机读需要首先在 LRUBlockCache 中查到对应的 Index Block ,然后再到 BucketCache 查找对应数据块。
3 、 HFile :HFile 的数据结构也是 Hbase 的重要改进之处。
图示是 HFile 的数据结构,主要包含四个部分:数据块、头信息、索引信息、地址信息。索引就是 HFile 内置的一个 B+ 树索引,当 RegionServer 启动后并且 HFile 被打开的时候,这个索引会被加载到 Block Cache 即内存里;KeyValues 存储在增长中的队列中的数据块里,数据块可以指定大小,默认 64k ,数据块越大,顺序检索能力越强;数据块越小,随机读写能力越强,需要权衡。
6. Hbase 与传统的 RDBMS 有什么区别?
表 4 列式数据库与关系型数据库的区别
介绍了很多 HBase 与 RDBMS 的区别。那么什么时候最需要 HBase ,或者说 HBase 是否可以替代原有的 RDBMS ?对于这个问题,我们必须时刻谨记 HBase 并不适合所有场景,其最终目标并不是完全替代 RDBMS ,而是对 RDBMS 的一个重要补充。当需要考量 HBase 作为一个备选选型产品的时候,我们需要考虑以下几个关键问题。
1、 业务场景是否符合非 ACID 事务原则?
2、 数据的业务特性上是否需要复杂查询,例如 SQL 实现的复杂连接、排序、复杂条件等?
3、 业务场景是不是可以通过读取列族数据的方式更有效地实现,数据是否可以用字符型表示?
4、 数据量是否足够发挥 Hbase 列式数据库的优势?
5、 是否可以找到合适的 Row-key ?随机性的 Row-key 适合频繁写,有序的 Row-key 适合大量的读。
7. Hbase 如何解决热点的问题?
HBase 中的行是按照 Row-Key 的字典顺序排序的,这种设计优化了扫描操作,可以将相关的行存放在临近位置,便于扫描。然而糟糕的 Row-Key 设计是热点的源头。一旦由于 Row-Key 设计与业务场景不相符,大量访问会使热点 region 所在的单个机器超出自身承受能力,引起性能下降甚至不可用,这也会影响同一个 RegionServer 上的其他 region 。
那么如何避免这样的问题发生呢?通常会有以下几种设计思想可供参考:
1 、反转:将 Row-Key 的字符串可变的部分提到前面,相对固定的部分提到后面。这样就会打乱 Row-Key 的有序性,在一定程度上降低了批量数据写的性能,但是读的时候就会减少热点查询,通过牺牲部分写的性能而提升读的性能。
2 、前缀:将每一个 Row-Key 加一个随机字符前缀,使得数据分散在多个不同的 Region ,达到 Region 负载均衡的目标。最终消除局部热点,解决热点读写的问题。
3 、散列:通过哈希散列的方式将 Row-Key 重新设计,使得数据分散在不同的 Region ,同时效果要比前缀的方式更好,因为在读的时候,它是可以通过哈希的计算减少读性能的损耗。既解决了热点问题,同时也不必消耗太多的读性能。
原题:NOSQL DB:Hbase 列式数据库七