HBase技术介绍

http://www.searchtb.com/2011/01/understanding-hbase.html


一  HBase简介


HBase – Hadoop Database,是一个高可靠性、高性能、面向列、可伸缩的分布式存储系统,利用HBase技术可在廉价PC Server上搭建起大规模结构化存储集群。

HBase是Google Bigtable的开源实现,类似Google Bigtable利用GFS作为其文件存储系统,HBase利用Hadoop HDFS作为其文件存储系统;Google运行MapReduce来处理Bigtable中的海量数据,HBase同样利用Hadoop MapReduce来处理HBase中的海量数据;Google Bigtable利用 Chubby作为协同服务,HBase利用Zookeeper作为对应。

Bigtable/HBase的特点:


1.Fundamentally Distributed(分布式存储)
项目从最初规划上,就是为海量数据服务的,当然分布式存储的思想也是扎根于其血脉中。分布式系统主要需要考虑两个方面:partitioning(分区存储,也可以理解为通常说的Sharding)、replication(数据复制,主要是将数据复制成多份以提高可用性)。


2.Column Oriented(列式存储)
和普通的RDBMS不一样,普通的RDBMS通常是行式存储的,一行数据是连续存在一段磁盘空间上的。而列式存储是将各个列分别进行连续的存储。也正是因此,它对于处理字段中的NULL字段,能够不占用过多的空间。同时能够支持灵活松散的列定义。也就是我们通常所说的schema-less。


3.Sequential write(顺序写磁盘)
BigTable型系统的一个特点是其对写性能进行的优化。它的写都是通过先记一条操作日志,然后直接写在内存中的数据集合,然后其集合按条件或定时将数据flush到磁盘。这里涉及到的记操作日志或者数据flush到磁盘都会顺序的磁盘操作。故而避免了磁盘随机操作造成的无谓的磁盘寻道时间。


4.Merged read(读操作数据合并)
上面说到写操作是通过定时将数据直接flush到磁盘进行的,每次flush都会生成一个数据块,那可能造成一个数据在多个数据块中的情况,而在读的时候就需要将这多个版本中的值进行合并。其中在判断一个数据块是否包含指定值时使用了bloom-filter算法。


5.Periodic Data Compaction(定期数据合并)
同样是上面说到的,一个数据可能存在于多个数据块,如果我们不做处理,随着时间的推移,数据块会越来越多。所以BigTable型系统会进行定时的数据合并。在上面讲到的将内存中的数据直接flush到磁盘的过程中,flush之前进行了一次数据的排序操作,既是说存在磁盘中的块中的数据,都是顺序的,那么对一堆顺序的数据进行排重合并,其实和我们熟知的多路归并排序很相似。故而其定时数据合并的效率也是非常高的。

上图描述了Hadoop EcoSystem中的各层系统,其中HBase位于结构化存储层,Hadoop HDFS为HBase提供了高可靠性的底层存储支持,Hadoop MapReduce为HBase提供了高性能的计算能力,Zookeeper为HBase提供了稳定服务和failover机制。

此外,Pig和Hive还为HBase提供了高层语言支持,使得在HBase上进行数据统计处理变的非常简单。 Sqoop则为HBase提供了方便的RDBMS数据导入功能,使得传统数据库数据向HBase中迁移变的非常方便。


二 HBase访问接口

1.       Native Java API,最常规和高效的访问方式,适合Hadoop MapReduce Job并行批处理HBase表数据

2.       HBase Shell,HBase的命令行工具,最简单的接口,适合HBase管理使用

3.       Thrift Gateway,利用Thrift序列化技术,支持C++,PHP,Python等多种语言,适合其他异构系统在线访问HBase表数据

4.       REST Gateway,支持REST 风格的Http API访问HBase, 解除了语言限制

5.       Pig,可以使用Pig Latin流式编程语言来操作HBase中的数据,和Hive类似,本质最终也是编译成MapReduce Job来处理HBase表数据,适合做数据统计

6.       Hive,当前Hive的Release版本尚没有加入对HBase的支持,但在下一个版本Hive 0.7.0中将会支持HBase,可以使用类似SQL语言来访问HBase


三 HBase数据模型

1 Table & Column Family

Row Key Timestamp Column Family
URI Parser
r1 t3 url=http://www.taobao.com title=天天特价
t2 host=taobao.com  
t1    
r2 t5 url=http://www.alibaba.com content=每天…
t4 host=alibaba.com  

Ø  Row Key: 行键,Table的主键,Table中的记录按照Row Key排序

Ø  Timestamp: 时间戳,每次数据操作对应的时间戳,可以看作是数据的version number

Ø  Column Family:列簇,Table在水平方向有一个或者多个Column Family组成,一个Column Family中可以由任意多个Column组成,即Column Family支持动态扩展,无需预先定义Column的数量以及类型,所有Column均以二进制格式存储,用户需要自行进行类型转换。


2 Table & Region
当Table随着记录数不断增加而变大后,会逐渐分裂成多份splits,成为regions,一个region由[startkey,endkey)表示,不同的region会被Master分配给相应的RegionServer进行管理:


3 -ROOT- && .META. Table
HBase中有两张特殊的Table,-ROOT-和.META.


Ø  .META.:记录了用户表的Region信息,.META.可以有多个regoin

Ø  -ROOT-:记录了.META.表的Region信息,-ROOT-只有一个region

Ø  Zookeeper中记录了-ROOT-表的location



Client访问用户数据之前需要首先访问zookeeper,然后访问-ROOT-表,接着访问.META.表,最后才能找到用户数据的位置去访问,中间需要多次网络操作,不过client端会做cache缓存。


四 MapReduce on HBase
在HBase系统上运行批处理运算,最方便和实用的模型依然是MapReduce,如下图:


HBase Table和Region的关系,比较类似HDFS File和Block的关系,HBase提供了配套的TableInputFormat和TableOutputFormat API,可以方便的将HBase Table作为Hadoop MapReduce的Source和Sink,对于MapReduce Job应用开发人员来说,基本不需要关注HBase系统自身的细节。

五 HBase系统架构


1 Client
HBase Client使用HBase的RPC机制与HMaster和HRegionServer进行通信,对于管理类操作,Client与HMaster进行RPC;对于数据读写类操作,Client与HRegionServer进行RPC

2 Zookeeper
Zookeeper Quorum中除了存储了-ROOT-表的地址和HMaster的地址,HRegionServer也会把自己以Ephemeral方式注册到Zookeeper中,使得HMaster可以随时感知到各个HRegionServer的健康状态。此外,Zookeeper也避免了HMaster的单点问题,见下文描述

3 HMaster
HMaster没有单点问题,HBase中可以启动多个HMaster,通过Zookeeper的Master Election机制保证总有一个Master运行,HMaster在功能上主要负责Table和Region的管理工作:

1.       管理用户对Table的增、删、改、查操作
2.       管理HRegionServer的负载均衡,调整Region分布
3.       在Region Split后,负责新Region的分配
4.       在HRegionServer停机后,负责失效HRegionServer 上的Regions迁移

4 HRegionServer
HRegionServer主要负责响应用户I/O请求,向HDFS文件系统中读写数据,是HBase中最核心的模块。

总的来说,一个表由多个Region组成,一个Region包括多个store,每个store存储一个列簇,每个Strore又由一个memStore和0至多个StoreFile组成。



HRegionServer内部管理了一系列HRegion对象,每个HRegion对应了Table中的一个Region,HRegion中由多个HStore组成。每个HStore对应了Table中的一个Column Family的存储,可以看出每个Column Family其实就是一个集中的存储单元,因此最好将具备共同IO特性的column放在一个Column Family中,这样最高效。

HStore存储是HBase存储的核心了,其中由两部分组成,一部分是MemStore,一部分是StoreFiles。MemStore是Sorted Memory Buffer,用户写入的数据首先会放入MemStore,当MemStore满了以后会Flush成一个StoreFile(底层实现是HFile),当StoreFile文件数量增长到一定阈值,会触发Compact合并操作,将多个StoreFiles合并成一个StoreFile,合并过程中会进行版本合并和数据删除,因此可以看出HBase其实只有增加数据,所有的更新和删除操作都是在后续的compact过程中进行的,这使得用户的写操作只要进入内存中就可以立即返回,保证了HBase I/O的高性能。当StoreFiles Compact后,会逐步形成越来越大的StoreFile,当单个StoreFile大小超过一定阈值后,会触发Split操作,同时把当前Region Split成2个Region,父Region会下线,新Split出的2个孩子Region会被HMaster分配到相应的HRegionServer上,使得原先1个Region的压力得以分流到2个Region上。下图描述了Compaction和Split的过程:



在理解了上述HStore的基本原理后,还必须了解一下HLog的功能,因为上述的HStore在系统正常工作的前提下是没有问题的,但是在分布式系统环境中,无法避免系统出错或者宕机,因此一旦HRegionServer意外退出,MemStore中的内存数据将会丢失,这就需要引入HLog了。每个HRegionServer中都有一个HLog对象,HLog是一个实现Write Ahead Log的类,在每次用户操作写入MemStore的同时,也会写一份数据到HLog文件中(HLog文件格式见后续),HLog文件定期会滚动出新的,并删除旧的文件(已持久化到StoreFile中的数据)。当HRegionServer意外终止后,HMaster会通过Zookeeper感知到,HMaster首先会处理遗留的 HLog文件,将其中不同Region的Log数据进行拆分,分别放到相应region的目录下,然后再将失效的region重新分配,领取 到这些region的HRegionServer在Load Region的过程中,会发现有历史HLog需要处理,因此会Replay HLog中的数据到MemStore中,然后flush到StoreFiles,完成数据恢复。

六 HBase存储格式
HBase中的所有数据文件都存储在Hadoop HDFS文件系统上,主要包括上述提出的两种文件类型:

1.       HFile, HBase中KeyValue数据的存储格式,HFile是Hadoop的二进制格式文件,实际上StoreFile就是对HFile做了轻量级包装,即StoreFile底层就是HFile
2.       HLog File,HBase中WAL(Write Ahead Log) 的存储格式,物理上是Hadoop的Sequence File

1 HFile
现在我们将深入HBase存储架构的核心,探讨HBase具体的数据存储文件的结构。HFile就是这个数据存储文件的结构(Ryan Rawson就是靠它扬名立万的)。创建HFile这样一个文件结构的目的只有一个:快速高效的存储HBase的数据。HFile是基于Hadoop TFile的(参见 HADOOP-3315)。HFile模仿了Google Bigtable中SSTable的格式。原先HBase使用Hadoop的MapFile,但是这种文件已经被证明了效率差。

现在让我们来看看这个文件结构到底是什么样的。




首先HFile文件是不定长的,长度固定的只有其中的两块:Trailer和FileInfo。正如图中所示的,Trailer中有指针指向其他数据块的起始点。File Info中记录了文件的一些Meta信息,例如:AVG_KEY_LEN, AVG_VALUE_LEN, LAST_KEY, COMPARATOR, MAX_SEQ_ID_KEY等。Data Index和Meta Index块记录了每个Data块和Meta块的起始点。

Data Block是HBase I/O的基本单元,为了提高效率,HRegionServer中有基于LRU的Block Cache机制。每个Data块的大小可以在创建一个Table的时候通过参数指定,大号的Block有利于顺序Scan,小号Block利于随机查询。每个Data块除了开头的Magic以外就是一个个KeyValue对拼接而成, Magic内容就是一些随机数字,目的是防止数据损坏。后面会详细介绍每个KeyValue对的内部构造。

关于如何设定数据块的大小,我们应用一段HFile源码中的注释:
我们推荐将数据块的大小设置为8KB至1MB。大的数据块比较适合顺序的查询(比如Scan),但不适合随机查询,想想看,每一次随机查询可能都需要你去解压缩一个大的数据块。小的数据块适合随机的查询,但是需要更多的内存来保存数据块的索引(Data Index),而且创建文件的时候也可能比较慢,因为在每个数据块的结尾我们都要把压缩的数据流Flush到文件中去(引起更多的Flush操作)。并且由于压缩器内部还需要一定的缓存,最小的数据块大小应该在20KB – 30KB左右。可能从前面的描述你会发现数据块(Data Block)是数据压缩的一个单位。


HFile里面的每个KeyValue对就是一个简单的byte数组。但是这个byte数组里面包含了很多项,并且有固定的结构。我们来看看里面的具体结构:


开始是两个固定长度的数值,分别表示Key的长度和Value的长度。紧接着是Key,开始是固定长度的数值,表示RowKey的长度,紧接着是RowKey,然后是固定长度的数值,表示Family的长度,然后是Family,接着是Qualifier,然后是两个固定长度的数值,表示Time Stamp和Key Type(Put/Delete)。Value部分没有这么复杂的结构,就是纯粹的二进制数据了。

2 HLogFile



上图中示意了HLog文件的结构,其实HLog文件就是一个普通的Hadoop Sequence File,Sequence File 的Key是HLogKey对象,HLogKey中记录了写入数据的归属信息,除了table和region名字外,同时还包括 sequence number和timestamp,timestamp是“写入时间”,sequence number的起始值为0,或者是最近一次存入文件系统中sequence number。

HLog Sequece File的Value是HBase的KeyValue对象,即对应HFile中的KeyValue,可参见上文描述。

七 流程

接下来看一下数据的大致流程。

1 读

假设你需要通过某个特定的RowKey查询一行记录,首先Client端会连接Zookeeper Qurom,通过Zookeeper,Client能获知哪个Server管理-ROOT- Region。接着Client访问管理-ROOT-的Server,进而获知哪个Server管理.META.表。这两个信息Client只会获取一次并缓存起来。在后续的操作中Client会直接访问管理.META.表的Server,并获取Region分布的信息。一旦Client获取了这一行的位置信息,比如这一行属于哪个Region,Client将会缓存这个信息并直接访问HRegionServer。久而久之Client缓存的信息渐渐增多,即使不访问.META.表也能知道去访问哪个HRegionServer。


注意:当HBase启动的时候HMaster负责分配Region给HRegionServer,这其中当然也包括-ROOT-表和.META.表的Region。

接下来HRegionServer打开这个Region并创建一个HRegion对象。当HRegion打开以后,它给每个table的每个HColumnFamily创建一个Store实例。每个Store实例拥有一个或者多个StoreFile实例。StoreFile对HFile做了轻量级的包装。除了Store实例以外,每个HRegion还拥有一个MemStore实例和一个HLog实例。


2 写

现在看一下数据是怎样被写到实际的存储中去的。Client发起了一个HTable.put(Put)请求给HRegionServer,HRegionServer会将请求匹配到某个具体的HRegion上面。紧接着的操作时决定是否写WAL log。是否写WAL log由Client传递的一个标志决定,你可以设置这个标志:Put.writeToWAL(boolean)。WAL log文件是一个标准的Hadoop SequenceFile(现在还在讨论是否应该把文件格式改成一个更适合HBase的格式)。在文件中存储了HLogKey,这些Keys包含了和实际数据对应的序列号,用途是当RegionServer崩溃以后能将WAL log中的数据同步到永久存储中去。做完这一步以后,Put数据会被保存到MemStore中,同时会检查MemStore是否已经满了,如果已经满了,则会触发一个Flush to Disk的请求。HRegionServer有一个独立的线程来处理Flush to Disk的请求,它负责将数据写成HFile文件并存到HDFS上。它也会存储最后写入的数据序列号,这样就可以知道哪些数据已经存入了永久存储的HDFS中。


HBase在HDFS上面的所有文件有一个可配置的根目录,默认根目录是/hbase。通过使用hadoop的DFS工具就可以看到这些文件夹的结构。

在根目录下面你可以看到一个.logs文件夹,这里面存了所有由HLog管理的WAL log文件。在.logs目录下的每个文件夹对应一个HRegionServer,每个HRegionServer下面的每个log文件对应一个Region。

有时候你会发现一些oldlogfile.log文件(在大多数情况下你可能看不到这个文件),这个文件在一种异常情况下会被产生。这个异常情况就是HMaster对log文件的访问情况产生了怀疑,它会产生一种称作“log splits”的结果。有时候HMaster会发现某个log文件没人管了,就是说任何一个HRegionServer都不会管理这个log文件(有可能是原来管理这个文件的HRegionServer挂了),HMaster会负责分割这个log文件(按照它们归属的Region),并把那些HLogKey写到一个叫做oldlogfile.log的文件中,并按照它们归属的Region直接将文件放到各自的Region文件夹下面。各个HRegion会从这个文件中读取数据并将它们写入到MemStore中去,并开始将数据Flush to Disk。然后就可以把这个oldlogfile.log文件删除了。

注意:有时候你可能会发现另一个叫做oldlogfile.log.old的文件,这是由于HMaster做了重复分割log文件的操作并发现oldlogfile.log已经存在了。这时候就需要和HRegionServer以及HMaster协商到底发生了什么,以及是否可以把old的文件删掉了。从我目前遇到的情况来看,old文件都是空的并且可以被安全删除的。

HBase的每个Table在根目录下面用一个文件夹来存储,文件夹的名字就是Table的名字。在Table文件夹下面每个Region也用一个文件夹来存储,但是文件夹的名字并不是Region的名字,而是Region的名字通过Jenkins Hash计算所得到的字符串。这样做的原因是Region的名字里面可能包含了不能在HDFS里面作为路径名的字符。在每个Region文件夹下面每个ColumnFamily也有自己的文件夹,在每个ColumnFamily文件夹下面就是一个个HFile文件了。所以整个文件夹结构看起来应该是这个样子的:

/hbase////


在每个Region文件夹下面你会发现一个.regioninfo文件,这个文件用来存储这个Region的Meta Data。通过这些Meta Data我们可以重建被破坏的.META.表,关于.regioninfo的应用你可以参考HBASE-7和HBASE-1867。

有一件事情前面一直没有提到,那就是Region的分割。当一个Region的数据文件不断增长并超过一个最大值的时候(你可以配置这个最大值 hbase.hregion.max.filesize),这个Region会被切分成两个。这个过程完成的非常快,因为原始的数据文件并不会被改变,系统只是简单的创建两个Reference文件指向原始的数据文件。每个Reference文件管理原始文件一半的数据。Reference文件名字是一个ID,它使用被参考的Region的名字的Hash作为前缀。例如:1278437856009925445.3323223323。Reference文件只含有非常少量的信息,这些信息包括被分割的原始Region的Key以及这个文件管理前半段还是后半段。HBase使用HalfHFileReader类来访问Reference文件并从原始数据文件中读取数据。前面的架构图只并没有画出这个类,因为它只是临时使用的。只有当系统做Compaction的时候原始数据文件才会被分割成两个独立的文件并放到相应的Region目录下面,同时原始数据文件和那些Reference文件也会被清除。

在每个Table的目录下面你可以看到一个叫做compaction.dir的目录。这个文件夹是一个数据交换区,用于存放split和compact Region过程中生成的临时数据。

你可能感兴趣的:(Hadoop)