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