快乐学习
前程无忧、中华英才非你莫属!

Hbase核心概念介绍:第一章

一、前言

本章将介绍HBase相关的一些比较核心的理论概念并剖析其实现逻辑,深入浅出锄

绍HBase实现中所涉及的理论基础。当然,HBase的实现涉及很多方面的知识,本章分别

从存储结构、持久化实现、预写日志、写人和查询流程、数据备份及压缩等多方面阐述。

在阐述过程中,为了形象地说明问题,会枚举不少示例和代码讲解,希望能够加深读者的

认识。

HBase作为一个分布式、面向列的高性能数据库,无论批量写人和读取的性能都很高,

而能够具备这些特性,其内部实现必定使用了一些特殊、高效的算法和机制。如何保证海量

数据下的读写性能?如何保证容错?如何灾备如何存储实际数据和日志?等等问题都会浮

现在读者的脑海中。本章将会对这些问题做全面、深人解释,希望读者在阅读完本章后,都

能找到上述问题的答案。

核心结构

对于数据库产品,底层存储架构直接决定了数据库的特性和使用场景。众所周知,传

统RDBMS(关系型数据库)使用B树及B+树作为数据存储结构,B树及B+树作为成熟

的存储结构应用在数据库领域已经有几十年的历史。相对而言,RDBMS的替代品,NosQL

之一的HBase的底层存储结构与其有着根本上的不同。HBase使用LSM树

(日志结构合并树)作为底层存储的数据结构。

当然,RDBMS并不是只能采用B+树类型的结构,而且也不是所有的NoSQL解决方案

都使用了与之不同的结构。通常我们都能看到各式各样的混搭型的技术方案,它们都具有一个相同的目标;使用解决当前问题的最佳策略。下面将会解释LSM树与B+树的不同,以及为何选用LSM作为Hbase的底层存储架构。

二、 [HBase] LSM树 VS B+树

LSM树是Hbase里非常有创意的一种数据结构,它和传统的B+树不太一样,下面先说说B+树。

1 B+树

相信大家对B+树已经非常的熟悉,比如Oracle的普通索引就是采用B+树的方式,下面是一个B+树的例子:

根节点和枝节点很简单,分别记录每个叶子节点的最小值,并用一个指针指向叶子节点。

叶子节点里每个键值都指向真正的数据块(如Oracle里的RowID),每个叶子节点都有前指针和后指针,这是为了做范围查询时,叶子节点间可以直接跳转,从而避免再去回溯至枝和跟节点。

B+树最大的性能问题是会产生大量的随机IO,随着新数据的插入,叶子节点会慢慢分裂,逻辑上连续的叶子节点在物理上往往不连续,甚至分离的很远,但做范围查询时,会产生大量读随机IO。

对于大量的随机写也一样,举一个插入key跨度很大的例子,如7->1000->3->2000 ... 新插入的数据存储在磁盘上相隔很远,会产生大量的随机写IO.

从上面可以看出,低下的磁盘寻道速度严重影响性能(近些年来,磁盘寻道速度的发展几乎处于停滞的状态)。

2 LSM树

为了克服B+树的弱点,HBase引入了LSM树的概念,即Log-Structured Merge-Trees。

为了更好的说明LSM树的原理,下面举个比较极端的例子:

现在假设有1000个节点的随机key,对于磁盘来说,肯定是把这1000个节点顺序写入磁盘最快,但是这样一来,读就悲剧了,因为key在磁盘中完全无序,每次读取都要全扫描;

那么,为了让读性能尽量高,数据在磁盘中必须得有序,这就是B+树的原理,但是写就悲剧了,因为会产生大量的随机IO,磁盘寻道速度跟不上。

LSM树本质上就是在读写之间取得平衡,和B+树相比,它牺牲了部分读性能,用来大幅提高写性能

它的原理是把一颗大树拆分成N棵小树, 它首先写入到内存中(内存没有寻道速度的问题,随机写的性能得到大幅提升),在内存中构建一颗有序小树,随着小树越来越大,内存的小树会flush到磁盘上。当读时,由于不知道数据在哪棵小树上,因此必须遍历所有的小树,但在每颗小树内部数据是有序的。

以上就是LSM树最本质的原理,有了原理,再看具体的技术就很简单了。

1)首先说说为什么要有WAL(Write Ahead Log),很简单,因为数据是先写到内存中,如果断电,内存中的数据会丢失,因此为了保护内存中的数据,需要在磁盘上先记录logfile,当内存中的数据flush到磁盘上时,就可以抛弃相应的Logfile。

2)什么是memstore, storefile?很简单,上面说过,LSM树就是一堆小树,在内存中的小树即memstore,每次flush,内存中的memstore变成磁盘上一个新的storefile。

3)为什么会有compact?很简单,随着小树越来越多,读的性能会越来越差,因此需要在适当的时候,对磁盘中的小树进行merge,多棵小树变成一颗大树。

三、理解这个图很重要

3.1 、底层持久化

绝大部分的用户不需要关心数据在HBase到底是如何存储的,但是当对HBase进行性能

调优和高级选项配置时,仍需要了解这方面的知识。例如,前面两章中提到的一些配置参数,

另一方面,如果HBase相关进程崩溃,或者更严重的情况如机器宕机,读者需要恢复数

据或者重新安装HBase,以及升级HBase版本。这个时候,读者需要知道数据存储的路径,

如何通过HDFS访问这些数据,如何使用故障恢复命令等。读者可以利用下面即将讲到的知

识来排查和解决上面提到的各种问题。

3.1.1 、 存储基本架构

通过上图可直观、形象地理解H8ase的文件存储层的各组成部分,图中展示了HBase

与HDFS是如何协作来存储数据的。

HBase处理的两种基本文件类型一个用于WAL(Write-Ahead Log),另一个用于实际

的数据存储。文件主要是由HRegionserver处理、在某些情况下,HMaster也会执行底

层的文件操作。当存储在HDFS中时,文件实际上会被划分为很多小Block,如果需要配置

系统来更好地处理更大或更小的文件时,则需要了解更多的细节内容,这些内容将在下

节里描述。

通常的工作流程是,一个新的客户端为找到某个特定的行键首先需要连接ZooKeeper

Qurom。它会从ZooKeeper检索持有-ROOT-Region的服务器名、通过这个信息,它询问拥

有-ROOT-Region的Regionserver,得到持有对应行健的.META表Region的服务器名。这

两个操作的结果都会被缓存下来,因此只需要查找一次、最后,它就可以查询.META.服务

器,然后检索包含给定行键的Region所在的服务器、

一旦它知道了给定的行键所处的位置,比如,在哪个Region里,它也会缓存该信息,

同时直接连接持有该Region的HRegionserver。现在,客户端就有了去哪里获取行的完整信

息而不需要再去查询.META.服务器。

在启动HBase时,HMaster负责把Region分配给每个HRegionserver,包括-ROOT一

和.META表。

HRegionserver打开Region然后创建对应的HRegion对象,当HRegion被打开后,它就

会为表中预先定义的每个HColumn Family创建一个Store实例,每个Store实例又可能有多

个StoreFile实例,StoreFile是对被称为HFile的实际存储文件的一个简单封装、一个Store

实例还会有二个Memstore,以及一个由HRegionserver共享的HLog实例(WAL相关的类)。

3.1.2 、HDFS文件

Hbase在Hadoop上有一个可配置的根目录,默认配置为/hbase,该路径可以通过配置hbase.site.xml 配置

Hbase文件可以分为两类,一是直接位于Hbase根目录下面的文件;二是位于表目录下面的文件。

1.根目录

第二类文件是由HLog实例处理的WAL文件,这些文件创建在HBase根目录下一个称

为·logs的目录中。.logs目录下包含针对每个取egionserver的子目录。在每个子目录下,通常有

几个HLog文件(因为日志的切换而产生),来自相同Regionserver的Region共享这些HLog文件。

对于最近创建的文件通常日志文件大小为o,因为HDFs正使用二个内置的append支持来

对文件进行写人,同时对于读取者来说只有那些完整的Block才是可用的,包括hadoop fs-lsr命

令。尽管写操作的数据被安全地持久化,但是当前写人的日志文件大小信息有些轻微的不同步。

日志文件每小时切换一次(默认60分钟)后,这个时间是由hbase.regionserver.logroll.period

配置项控制。等日志文件切换后,可以查看其正确大小,因为该文件已经被关闭了,所以

HDFS可以获取正确的状态。而其之后的新日志文件大小又会变成0

当日志文件不再需要时,现有的变更已经持久化到存储文件中了,它们就会被移到

HBase根目录的.o161ogs目录中。这是在日志文件达到上面的切换阈值时触发的。老的日志

文件默认会在10分钟后被Mast·r删除,通过hbase.master.lowleaner.ttl设定。Master默认每

分钟会对这些文件进行检查,可以通过hbase.master.cleaner.interval设定。

hbase.id 和hbase.version 文件包含集群的唯一ID和文件格式版本号、这两个文件都是内部使用、通常无需关心。

此外,随着时间的推移还会产生一些根目录级的文件。splitlog和.corrupt 目录分别是日志split进程用来存储总监split文件的损坏的日志文件。.archive 用于存储表的归档和快照。

2、表目录

Hbase中的每个表都有它自己的目录,位于hbase根目录只有放下。 每个表目录包含一个名为.tableinfo 的顶层文件,该文件保存了针对该表 HTableDescriptor 序列化后的内容,包含了元数据信息,

同时可以被读取,比如通过实用工具可以查看表的定义;.tmp目录包含一些中间数据,比如当 .tableinfo 被更新时该目录就会被用到。

3、Region

Region目录也包含了一个.regioninfowenjian ,其中包含了Region的HRegionInfo的序列化信息,类似于 .tableinfo,它也可以通过外部工具来查看关于Region的相关信息。

hbase的 hbck工具可以用它来生成丢失的表条目元数据。

可选的.tmp目录是按需创建的,用来存储临时文件,比如某个合并产生的重新写回的文

件。一旦该过程结束,它们会被立即移入Region目录。在极端情况下,你可能会看到一些残

留文件,在重新打开Region时它们会被清除。

在WAL回放期间,任何尚未提交的修改会写人每个Region各自对应的文件中。这是

第一个阶段,之后假设日志splitting过程成功完成,然后会将这些文件原子性地move到

recovered.edits目录下。当打开该Region时,Regionserver能够看到这些恢复文件然后回放

相应的记录。

注意:

WAL的spliting和Region的splitting之间有明显的区别。有些时候,在文件系统中

很难区分文件和目录的不同,因为它们两个都涉及了splits这个名词。为避免错误和

混淆,这里强调了这个区别,以确保你已经理解了二者的不同。

且一个Region因为容量大小而需要split,会创建一个与之对应的SPlits目录,用来筹

划产生两个子Region。通常只需要几秒或更短的时间,该过程成功之后它们会被移人表目录

下用来形成两个新的Region,每个代表原始Region的一半。

换句话说,如果发现一个Region目录下没有.tmp目录,那么说明目前它上面没有执行合并。如果也没有Recovered.edits目录,那么说明目前没有针对她的WAL回放。

4、Hbase Region的拆分和合并

•拆分决定因素

Hbase中的表的region的拆分主要由以下的因素决定

Min (R^2 * “hbase.hregion.memstore.flush.size”, “hbase.hregion.max.filesize”)

其中R是该region中所包含的该表的region的数量,一般情况下如果memstore.flush.size 的值是128M,那么当开始新建一张表并开始写入数据时,当达到128M开始第一次拆分,之后依次是512MB, 1152MB, 2GB, 3.2GB, 4.6GB, 6.2GB时候开始进行拆分,当达到max.filesize的设定值时便会永远在storefile 达到max.filesize进行拆分(默认情况下这个值是10G,在0.90.x版本是256M,现在版本中的默认是是10G),需要特别注意的是filessize指的是store下的storefile的大小并不是整个region的大小,一个region可能包含很多个store,确切的说是该表有多少个family就有多少个store,当某个family下的storefile达到以上标准是就会拆分整个region而不管改region下的其他的store下的storefile是否已经达到触发条件。按照我的理解,当第一次拆分后所生成的两个新的region,而这两个region有可能会被分配给不同的region server所以当这个region server 进行第二次拆分的时候R的值有可能是1而不是2,这样一直到所有的region server的节点全部分配到一个region为此(这还需要进一步去验证,因为我至今还搞不清楚当一个region进行拆分式会不会把拆分后的两个region都assign给父region所在的region server,当然我这样的猜测的依据是当出现一个hot region的时候通过手工拆分region可以达到负载均衡的目的,既然是达到负载均衡那肯定是把拆分后的俩region分配到了不同的region sever否则负载均衡大约无从谈起)。

 •预拆分

region拆分的另一个问题是当一个新表刚开始建立的时候,默认是一个region这就意味着所有的客户端请求都要去请求这个region所在的region server,这样子其实限制了整个集群的能力,一直到数据足够大到能够触发拆分,并且拆分到的region能够均匀的分布到各个region server中去之后才能发挥整个集群的威力。按照上面的拆分约定,第一次拆分仅仅是store的数据达到128M,这并不算大,但是并不意味着可以等待这样的自动拆分,设想一种极端的情况,表建立之后就有1000个客户端同时请求写入数据,那么要等待自动拆分到足够region的数量才能够均衡这样的1000个客户端请求,那效率依然是十分低下的。解决的办法是在创建表的时候进行预拆分,就是在建表的时候先预先创建多个region这样子客户端请求的时候就可以分散到不同的region server去请求写入。

第一种对表进行预拆分的方法

$ hbase org.apache.Hadoop.hbase.util.RegionSplitter test_table HexStringSplit -c 10 -f f1:f2:f3

表名:test_table

拆分的region的数量:10

family:f1,f2,f3

第二中预拆分的方法是,当知道rowkey的分布的时候可以指定每个预拆分的region的rowkey的开始值:

hbase(main):015:0> create 'test_table', 'f1', SPLITS=> ['a', 'b', 'c']

•手动拆分

当出现hot region的时候,通常是因为rowkey设计不合理造成的,比如说是自增长的ID,会导致连续的行集中在同一个region中(默认是10G),如果这个region中连续的rowkey都是近一个月的数据,而客户端请求得最为频繁的也是当月的数据,那么就会造成所有请求当月数据的的客户端都连接到该region,导致阻塞,所以彻底解决办法是对rowkey进行良好的设计以让rowkey在集群中均匀分布,如果hot region已经产生,那么就需要手动的去拆分region以让拆分出来的两个region或者数个region分布到不同的region server中去

hbase(main):024:0> split 'b07d0034cbe72cb040ae9cf66300a10c', 'b'

•拆分流程

 详细的拆分流程可以参考我转的文章《Apache HBase Region Splitting and Merging》,总得来说可以分为,当一个region要拆分的时候会首先将该region下线,然后block住所有的对该region的客户端请求(所以在事务系统中在拆分的策略上进行均衡),拆分的时候会在首先在父region下建立两个referencefile用来指向父region的首行和末行,但是referencefile并不会从父region中拷贝数据,之后HDFS上建立两个字region的目录然后分别拷贝上步建立的referencefile文件,记住referencefile虽然只是指向父region的数据但是却可以当做子的storefile(分别占据父region的一半数据)使用,完成子region的创建之后会想meta表发送新产生的region的元数据信息。当子的region的memstore不断flush成为新的storefile文件,达到一定数量级的时候就会触发合并(下面会提到),合并的时候就会去父region提取属于它的那一半的数据加上它自己的新生成的storefile的数据,合并完成之后就会自动删除referencefile的文件。

•合并

 客户端的数据写入region的时候并不会直接写到storefile中而是先写到内存缓冲区memstore(比如大小128M)中,满了之后会flush到store下成为单独的storefile,所以一个store下可能会有多个storefile,当storefile的个数达到设定值(默认是3)时就会合并成一个大的storefile,而当一个storefile达到上述的临界值又会自动进行拆分。

打赏
赞(0) 打赏
未经允许不得转载:同乐学堂 » Hbase核心概念介绍:第一章

特别的技术,给特别的你!

联系QQ:1071235258QQ群:710045715

觉得文章有用就打赏一下文章作者

非常感谢你的打赏,我们将继续给力更多优质内容,让我们一起创建更加美好的网络世界!

支付宝扫一扫打赏

微信扫一扫打赏

error: Sorry,暂时内容不可复制!