HBase Architecture(译):上

转自:http://hi.baidu.com/rebeccacao/blog/item/059aee1ce51b5d014134179e.html

HBase Architecture(译):上
2012-01-04 14:01

译者: 2011-10-1

出处:http://duanple.blog.163.com/blog/static/70971767201191661620641/

  
1.   序

本文翻译自:http://ofps.oreilly.com/titles/9781449396107/architecture.html

作为开源类BigTable实现。HBase目前已经应用在很多互联网公司中。

项目主页:http://hbase.apache.org/ 

无论对于高级用户还是普通使用者来说,完整地理解所选择的系统在底层是如何工作的都是非常有用的。本章我们会解释下HBase的各个组成部分以及它们相互之间是如何协作的。


2.   Seek vs. Transfer

在研究架构本身之前,我们还是先看一下传统RDBMS与它的替代者之间的根本上的不同点。特别地,我们将快速地浏览下关系型存储引擎中使用的B树及B+树,以及作为Bigtable的存储架构基础的Log-Structured Merge Tree。

注:需要注意的是RDBMSs并不是只能采用B树类型的结构,而且也不是所有的NoSQL解决方案都使用了与之不同的结构。通常我们都能看到各式各样的混搭型的技术方案,它们都具有一个相同的目标:使用那些对手头上的问题来说最佳的策略。下面我们会解释下为什么Bigtable使用了类LSM-tree的方式来实现这个目标。


2.1.  B+

B+树有一些特性可以让用户根据key来对记录进行高效地插入,查找和删除。它可以利用每个segment(也称为一个page)的下界和上界以及key的数目来建立一个动态,多级索引结构。通过使用segments,达到了比二叉树更高的扇出{!很明显二叉树一个节点只有2个出度,而B+树是个多叉树,一个节点就是一个segment,因此出度大小就由segment本身存储空间决定,出度增加后,就使得树高度变低,减少了所需seek操作的数目},这就大大降低了查找某个特定的key所需的IO操作数。

 

此外,它也允许用户高效地进行range扫描操作。因为叶子节点相互之间根据key的顺序组成了一个链表,这就避免了昂贵的树遍历操作。这也是关系数据库系统使用B+树进行索引的原因之一。

 

在一个B+树索引中,可以得到page级别的locality(这里的page概念等价于其他一些系统中block的概念):比如,一个leaf pages结构如下。为了插入一个新的索引条目,比如是key1.5,它会使用一个新的key1.5 → rowid条目来更新leaf page。在page大小未超过它本身的容量之前,都比较简单。如果page大小超出限制,那么就需要将该page分割成两个新的page。参见图8.1

Figure 8.1. An example B+ tree with one full page

 

这里有个问题,新的pages相互之间不一定是相邻的。所以,现在如果你想查询从key1到key3之间的内容,就有可能需要读取两个相距甚远的leaf pages。这也是为什么大部分的基于B+-树的系统中都提供了OPTIMIZE TABLE命令的原因—该命令会顺序地对table进行重写,以删除碎片,减少文件尺寸,从而使得这种基于range的查询在磁盘上也是顺序进行的。


2.2.  Log-Structured Merge-Trees

另一方面,LSM-tree,选择的是一种与之不同的策略。进入系统的数据首先会被存储到日志文件中,以完全顺序地方式。一旦日志中记录下了该变更,它就会去更新一个内存中的存储结构,该结构持有最近的那些更新以便于快速的查找。

 

当系统已经积累了足够的更新,以及内存中的存储结构填满的时候,它会将key → record对组成的有序链表flush到磁盘,创建出一个新的存储文件。此时,log文件中对应的更新就可以丢弃了,因为所有的更新操作已经被持久化了。

 

存储文件的组织方式类似于B树,但是专门为顺序性的磁盘访问进行了优化。所有的nodes都被完全填充,存储为单page或者多page的blocks。存储文件的更新是以一种rolling merge的方式进行的,比如,只有当某个block填满时系统才会将对应的内存数据和现有的多page blocks进行合并。

 

图8.2展示了一个多page的block如何从in-memory tree合并为一个存储磁盘上的树结构。最后,这些树结构会被用来merge成更大的树结构。

Figure 8.2. Multi-page blocks are iteratively merged across LSM trees

 

随着时间的推进将会有更多的flush操作发生,会产生很多存储文件,一个后台进程负责将这些文件聚合成更大的文件,这样磁盘seek操作就限制在一定数目的存储文件上。存储在磁盘上的树结构也可以被分割成多个存储文件。因为所有的存储数据都是按照key排序的,因此在现有节点中插入新的keys时不需要重新进行排序。

 

查找通过merging的方式完成,首先会搜索内存存储结构,接下来是磁盘存储文件。通过这种方式,从客户端的角度看到的就是一个关于所有已存储数据的一致性视图,而不管数据当前是否驻留在内存中。删除是一种特殊的更新操作,它会存储一个删除标记,该标记会在查找期间用来跳过那些已删除的keys。当数据通过merging被重新写回时,删除标记和被该标记所遮蔽的key都会被丢弃掉。

 

用于管理数据的后台进程有一个额外的特性,它可以支持断言式的删除。也就是说删除操作可以通过在那些想丢弃的记录上设定一个TTL(time-to-live)值来触发。比如,设定TTL值为20天,那么20天后记录就变成无效的了。Merge进程会检查该断言,当断言为true时,它就会在写回的blocks中丢弃该记录。

 

B数和LSM-tree本质上的不同点,实际上在于它们使用现代硬件的方式,尤其是磁盘。

Seek vs. Sort and Merge in Numbers

对于大规模场景,计算瓶颈在磁盘传输上。CPU RAM和磁盘空间每18-24个月就会翻番,但是seek开销每年大概才提高5%。

如前面所讨论的,有两种不同的数据库范式,一种是Seek,另一种是Transfer。RDBMS通常都是Seek型的,主要是由用于存储数据的B树或者是B+树结构引起的,在磁盘seek的速率级别上实现各种操作,通常每个访问需要log(N)个seek操作。

另一方面,LSM-tree则属于Transfer型。在磁盘传输速率的级别上进行文件的排序和merges以及log(对应于更新操作)操作。根据如下的各项参数:

·         10 MB/second transfer bandwidth

·         10 milliseconds disk seek time

·         100 bytes per entry (10 billion entries)

·         10 KB per page (1 billion pages)

在更新100,000,000条记录的1%时,将会花费:

·         1,000 days with random B-tree updates

·         100 days with batched B-tree updates

·         1 day with sort and merge

很明显,在大规模情况下,seek明显比transfer低效。

 

比较B+树和LSM-tree主要是为了理解它们各自的优缺点。如果没有太多的更新操作,B+树可以工作地很好,因为它们会进行比较繁重的优化来保证较低的访问时间。越快越多地将数据添加到随机的位置上,页面就会越快地变得碎片化。最终,数据传入的速度可能会超过优化进程重写现存文件的速度。更新和删除都是以磁盘seek的速率级别进行的,这就使得用户受限于最差的那个磁盘性能指标。

 

LSM-tree工作在磁盘传输速率的级别上,同时可以更好地扩展到更大的数据规模上。同时也能保证一个比较一致的插入速率,因为它会使用日志文件+一个内存存储结构把随机写操作转化为顺序写。读操作与写操作是独立的,这样这两种操作之间就不会产生竞争。

 

存储的数据通常都具有优化过的存放格式。对于访问一个key所需的磁盘seek操作数也有一个可预测的一致的上界。同时读取该key后面的那些记录也不会再引入额外的seek操作。通常情况下,一个基于LSM-tree的系统的开销都是透明的:如果有5个存储文件,那么访问操作最多需要5次磁盘seek。然而你没有办法判断一个RDBMS的查询需要多少次磁盘seek,即使是在有索引的情况下。


3.   Storage

HBase一个比较不为人知的方面是数据在底层是如何存储的。大部分的用户可能从来都不需要关注它。但是当你需要按照自己的方式对各种高级配置项进行设置时可能就得不得不去了解它。Chapter 11, Performance Tuning列出了一些例子。Appendix A, HBase Configuration Properties有一个更全的参考列表。

 

需要了解这些方面的另一个原因是,如果因为各种原因,灾难发生了,然后你需要恢复一个HBase安装版本。这时候,知道所有的数据都存放在哪,如何在HDFS级别上访问它们,就变得很重要了。你就可以利用这些知识来访问那些通常情况下不可访问的数据。当然,这种事情最好不发生,但是谁能保证它不会发生呢?


3.1.  概览

作为理解HBase的文件存储层的各组成部分的第一步,我们先来画张结构图。Figure 8.3, “HBase handles files in the file system, which stores them transparently in HDFS”展示了HBase和HDFS是如何协作来存储数据的。

Figure 8.3. HBase handles files in the file system, which stores them transparently in HDFS

 

上图表明,HBase处理的两种基本文件类型:一个用于write-ahead log,另一个用于实际的数据存储。文件主要是由HRegionServer处理。在某些情况下,HMaster也会执行一些底层的文件操作(与0.90.x相比,这在0.92.0中有些差别)。你可能也注意到了,当存储在HDFS中时,文件实际上会被划分为很多小blocks。这也是在你配置系统来让它可以更好地处理更大或更小的文件时,所需要了解的地方。更细节的内容,我们会在 the section called “HFile Format”里描述。

 

通常的工作流程是,一个新的客户端为找到某个特定的行key首先需要联系Zookeeper Qurom。它会从ZooKeeper检索持有-ROOT- region的服务器名。通过这个信息,它询问拥有-ROOT- region的region server,得到持有对应行key的.META.表region的服务器名。这两个操作的结果都会被缓存下来,因此只需要查找一次。最后,它就可以查询.META.服务器然后检索到包含给定行key的region所在的服务器。

 

一旦它知道了给定的行所处的位置,比如,在哪个region里,它也会缓存该信息同时直接联系持有该region的HRegionServer。现在,客户端就有了去哪里获取行的完整信息而不需要再去查询.META.服务器。更多细节可以参考the section called “Region Lookups”

注:在启动HBase时,HMaster负责把regions分配给每个HRegionServer。包括-ROOT-和.META.表。更多细节参考the section called “The Region Life Cycle”

HRegionServer打开region然后创建对应的HRegion对象。当HRegion被打开后,它就会为表中预先定义的每个HColumnFamily创建一个Store实例。每个Store实例又可能有多个StoreFile实例,StoreFile是对被称为HFile的实际存储文件的一个简单封装。一个Store实例还会有一个Memstore,以及一个由HRegionServer共享的HLog实例(见the section called “Write-Ahead Log”)。

 


3.2.  Write Path

客户端向HRegionServer产生一个HTable.put(Put)请求。HRegionServer将该请求交给匹配的HRegion实例。现在需要确定数据是否需要通过HLog类写入write-ahead log(the WAL)。该决定基于客户端使用
方法


Put.setWriteToWAL(boolean)

所设置的flag。WAL是一个标准的Hadoop SequenceFile,里面存储了HLogKey实例。这些keys包含一个序列号和实际的数据,用来replay那些在服务器crash之后尚未持久化的数据。

 

一旦数据写入(or not)了WAL,它也会被放入Memstore。与此同时,还会检查Memstore是否满了,如果满了需要产生一个flush请求。该请求由HRegionServer的单独的线程进行处理,该线程会把数据写入到位于HDFS上的新HFile里。同时它也会保存最后写入的序列号,这样系统就知道目前为止持久化到哪了。


3.3.  Files

HBase在HDFS上有一个可配置的根目录,默认设置为”/hbase”。 the section called “Co-Existing Clusters”说明了在共享HDFS集群时如何换用另一个根目录。可以使用hadoop dfs -lsr命令来查看HBase存储的各种文件。在此之前,我们先创建并填写一个具有几个regions的table:

...

Flush命令会将内存数据写入存储文件,否则我们必须等着它直到超过配置的flush大小才会将数据插入到存储文件中。最后一轮的put命令循环是为了再次填充write-ahead log。

 

下面是上述操作完成之后,HBase根目录下的内容:

$HADOOP_HOME/bin/hadoop dfs -lsr /hbase

       ...
      

注:由于空间的限制,我们对输出内容进行了删减,只留下了文件大小和名称部分。你自己在集群上运行命令时可以看到更多的细节信息。

文件可以分成两类:一是直接位于HBase根目录下面的那些,还有就是位于table目录下面的那些。


3.3.1.             Root Level Files

第一类文件是由HLog实例处理的write-ahead log文件,这些文件创建在HBase根目录下一个称为.logs的目录。Logs目录下包含针对每个HRegionServer的子目录。在每个子目录下,通常有几个HLog文件(因为log的切换而产生)。来自相同region server的regions共享同一系列的HLog文件。

 

一个有趣的现象是log file大小被报告为0。对于最近创建的文件通常都是这样的,因为HDFS正使用一个内建的append支持来对文件进行写入,同时只有那些完整的blocks对于读取者来说才是可用的—包括hadoop dfs -lsr命令。尽管put操作的数据被安全地持久化,但是当前被写入的log文件大小信息有些轻微的脱节。

 

等一个小时log文件切换后,这个时间是由配置项:hbase.regionserver.logroll.period控制的(默认设置是60分钟),你就能看到现有的log文件的正确大小了,因为它已经被关闭了,而且HDFS可以拿到正确的状态了。而在它之后的那个新log文件大小又变成0了:

 

249962 /hbase/.logs/foo.internal,60020,1309812147645/ \
foo.internal%2C60020%2C1309812147645.1309812151180
       0 /hbase/.logs/foo.internal,60020,1309812147645/ \
foo.internal%2C60020%2C1309812147645.1309815751223

 

当日志文件不再需要时,因为现有的变更已经持久化到存储文件中了,它们就会被移到HBase根目录下的.oldlogs目录下。这是在log文件达到上面的切换阈值时触发的。老的日志文件默认会在十分钟后被master删除,通过hbase.master.logcleaner.ttl设定。Master默认每分钟会对这些文件进行检查,可以通过hbase.master.cleaner.interval设定。

 

hbase.id和hbase.version文件包含集群的唯一ID和文件格式版本号:
$hadoop dfs -cat /hbase/hbase.id
$e627e130-0ae2-448d-8bb5-117a8af06e97

$
hadoop dfs -cat /hbase/hbase.version
7

 

它们通常是在内部使用因此通常不用关心这两个值。此外,随着时间的推进还会产生一些root级的目录。splitlog和.corrupt目录分别是log split进程用来存储中间split文件的和损坏的日志文件的。比如:
 

上面的例子中没有损坏的日志文件,只有一个分阶段的split文件。关于log splitting过程参见the section called “Replay”


3.3.2.             Table Level Files

HBase中的每个table都有它自己的目录,位于HBase根目录之下。每个table目录包含一个名为.tableinfo的顶层文件,该文件保存了针对该table的HTableDescriptor(具体细节参见the section called “Tables”)的序列化后的内容。包含了table和column family schema信息,同时可以被读取,比如通过使用工具可以查看表的定义。.tmp目录包含一些中间数据,比如当.tableinfo被更新时该目录就会被用到。


3.3.3.             Region Level Files

在每个table目录内,针对表的schema中的每个column family会有一个单独的目录。目录名称还包含region name的MD5 hash部分。比如通过master的web UI,点击testtable链接后,其中User Tables片段的内容如下:

 

testtable,row-500,1309812163930.d9ffc3a5cd016ae58e23d7a6cb937949.

 

MD5 hash部分是”d9ffc3a5cd016ae58e23d7a6cb937949”,它是通过对region name的剩余部分进行编码生成的。比如”testtable,row-500,1309812163930”。尾部的点是整个region name的一部分:它表示这是一种包含hash的新风格的名称。在HBase之前的版本中,region name中并不包含hash。 

注:需要注意的是-ROOT-和.META.元数据表仍然采用老风格的格式,比如它们的region name不包含hash,因此结尾就没有那个点。

.META.,,1.1028785192

对于存储在磁盘上的目录中的region names编码方式也是不同的:它们使用Jenkins hash来对region name编码。 

Hash是用来保证region name总是合法的,根据文件系统的规则:它们不能包含任何特殊字符,比如”/”,它是用来分隔路径的。这样整个的region文件路径就是如下形式:
 

/<hbase-root-dir>/<tablename>/<encoded-regionname>/<column-family>/<filename>
 

在每个column-family下可以看到实际的数据文件。文件的名字是基于Java内建的随机数生成器产生的任意数字。代码会保证不会产生碰撞,比如当发现新生成的数字已经存在时,它会继续寻找一个未被使用的数字。

 

Region目录也包含一个.regioninfo文件,包含了对应的region的HRegionInfo的序列化信息。类似于.tableinfo,它也可以通过外部工具来查看关于region的相关信息。hbase hbck工具可以用它来生成丢失的table条目元数据。

 

可选的.tmp目录是按需创建地,用来存放临时文件,比如某个compaction产生的重新写回的文件。一旦该过程结束,它们会被立即移入region目录。在极端情况下,你可能能看到一些残留文件,在region重新打开时它们会被清除。

 

在write-ahead log replay期间,任何尚未提交的修改会写入到每个region各自对应的文件中。这是阶段1(看下the section called “Root Level Files”中的splitlog目录),之后假设log splitting过程成功完成-然后会将这些文件原子性地move到recovered.edits目录下。当该region被打开时,region server能够看到这些recovery文件然后replay相应的记录。

Split vs. Split

在write-ahead log的splitting和regions的splitting之间有明显的区别。有时候,在文件系统中很难区分文件和目录的不同,因为它们两个都涉及到了splits这个名词。为避免错误和混淆,确保你已经理解了二者的不同。

一旦一个region因为大小原因而需要split,一个与之对应的splits目录就会创建出来,用来筹划产生两个子regions。如果这个过程成功了—通常只需要几秒钟或更少—之后它们会被移入table目录下用来形成两个新的regions,每个代表原始region的一半。

 

换句话说,当你发现一个region目录下没有.tmp目录,那么说明目前它上面没有compaction在执行。如果也没有recovered.edits目录,那么说明目前没有针对它的write-ahead log replay。

 

注:在HBase 0.90.x版本之前,还有一些额外的文件,目前已被废弃了。其中一个是oldlogfile.log,该文件包含了对于相应的region已经replay过的write-ahead log edits。oldlogfile.log.old(加上一个.old扩展名)表明在将新的log文件放到该位置时,已经存在一个oldlogfile.log。另一个值得注意的是在老版HBase中的compaction.dir,现在已经被.tmp目录替换。

 

本节总结了下HBase根目录下的各种目录所包含的一系列内容。有很多是由region split过程产生的中间文件。在下一节里我们会分别讨论。


3.4.  Region Splits

当一个region内的存储文件大于hbase.hregion.max.filesize(也可能是在column family级别上配置的)的大小时,该region就需要split为两个。起始过程很快就完成了,因为系统只是简单地为新regions(也称为daughters)创建两个引用文件,每个只持有原始region的一半内容。 

Region server通过在parent region内创建splits目录来完成。之后,它会关闭该region这样它就不再接受任何请求。 

Region server然后开始准备生成新的子regions(使用多线程),通过在splits目录内设置必要的文件结构。里面包括新的region目录及引用文件。如果该过程成功完成,它就会把两个新的region目录移到table目录下。.META.table会进行更新,指明该region已经被split,以及子regions分别是谁。这就避免了它被意外的重新打开。实例如下: 


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值