HBASE存储架构.doc

上传人:文库蛋蛋多 文档编号:2392016 上传时间:2023-02-17 格式:DOC 页数:11 大小:1.02MB
返回 下载 相关 举报
HBASE存储架构.doc_第1页
第1页 / 共11页
HBASE存储架构.doc_第2页
第2页 / 共11页
HBASE存储架构.doc_第3页
第3页 / 共11页
HBASE存储架构.doc_第4页
第4页 / 共11页
HBASE存储架构.doc_第5页
第5页 / 共11页
点击查看更多>>
资源描述

《HBASE存储架构.doc》由会员分享,可在线阅读,更多相关《HBASE存储架构.doc(11页珍藏版)》请在三一办公上搜索。

1、HBase存储架构英文原文:HBase最隐秘的问题之一就是它的数据是如何存储的。虽然大多数用户都不会因为这个问题向你抱怨,但是如果你想学习哪些高级的配置选项并了解它们的意思,你可能就需要来了解一下这个存储问题了。“怎样才能把HBase调整到最适合我需求的状态?”你可能对于这样一系列类似的问题非常感兴趣。那么你就需要绕过这些问题来学习HBase的基础知识。另一个支持你学习这些基础知识的理由是有时候各种各样你想不到的灾难需要你恢复整个HBase。我首先学习了HBase中控制各种不同文件的独立的类,然后根据我对整个HBase存储系统的理解在脑海中构建HBase架构的图像。但是我发现想要在头脑中构建出

2、一幅连贯的HBase架构图片很困难,于是我就把它画了出来。你可能注意到了这不是一个UML图或者调用图。这个图混合了类和他们管理控制的文件,将注意力集中到了本文要讨论的主题上。后面我将会讨论这张图所涉及到的细节,包括一些配置文件项是如何影响底层的文件存储系统的。好,我们现在来分析这张图里面到底包含了什么。首先HBase操控两种基本类型的文件,一种用于存储WAL的log,另一种用于存储具体的数据。这两种文件主要由HRegionServer来管理,但是在有的情况下HMaster会跳过HRegionServer,直接操作这两种文件。你可能注意到了,这些文件都被存储在HDFS上面,并且每个文件包含了多个

3、数据块。配置文件中就有一个选项用来调整系统控制数据的大小。我们后面会详细讨论这个问题。接下来看一下数据的大致流程。假设你需要通过某个特定的RowKey查询一行记录,首先Client端会连接Zookeeper Qurom,通过Zookeeper,Client能获知哪个Server管理-ROOT- Region。接着Client访问管理-ROOT-的Server,进而获知哪个Server管理.META.表。这两个信息Client只会获取一次并缓存起来。在后续的操作中Client会直接访问管理.META.表的Server,并获取Region分布的信息。一旦Client获取了这一行的位置信息,比如这一

4、行属于哪个Region,Client将会缓存这个信息并直接访问HRegionServer。久而久之Client缓存的信息渐渐增多,即使不访问.META.表也能知道去访问哪个HRegionServer。注意:当HBase启动的时候HMaster负责分配Region给HRegionServer,这其中当然也包括-ROOT-表和.META.表的Region。接下来HRegionServer打开这个Region并创建一个HRegion对象。当HRegion打开以后,它给每个table的每个HColumnFamily创建一个Store实例。每个Store实例拥有一个或者多个StoreFile实例。Sto

5、reFile对HFile做了轻量级的包装。除了Store实例以外,每个HRegion还拥有一个MemStore实例和一个HLog实例。现在我们就可以看看这些实例是如何在一起工作的,遵循什么样的规则以及这些规则的例外。保留住Put现在看一下数据是怎样被写到实际的存储中去的。Client发起了一个HTable.put(Put)请求给HRegionServer,HRegionServer会将请求匹配到某个具体的HRegion上面。紧接着的操作时决定是否写WAL log。是否写WAL log由Client传递的一个标志决定,你可以设置这个标志:Put.writeToWAL(boolean)。WAL l

6、og文件是一个标准的Hadoop SequenceFile(现在还在讨论是否应该把文件格式改成一个更适合HBase的格式)。在文件中存储了HLogKey,这些Keys包含了和实际数据对应的序列号,用途是当RegionServer崩溃以后能将WAL log中的数据同步到永久存储中去。做完这一步以后,Put数据会被保存到MemStore中,同时会检查MemStore是否已经满了,如果已经满了,则会触发一个Flush to Disk的请求。HRegionServer有一个独立的线程来处理Flush to Disk的请求,它负责将数据写成HFile文件并存到HDFS上。它也会存储最后写入的数据序列号,

7、这样就可以知道哪些数据已经存入了永久存储的HDFS中。现在让我们来看看这些存储文件。存储文件HBase在HDFS上面的所有文件有一个可配置的根目录,默认根目录是/hbase。通过使用hadoop的DFS工具就可以看到这些文件夹的结构。在根目录下面你可以看到一个.logs文件夹,这里面存了所有由HLog管理的WAL log文件。在.logs目录下的每个文件夹对应一个HRegionServer,每个HRegionServer下面的每个log文件对应一个Region。有时候你会发现一些oldlogfile.log文件(在大多数情况下你可能看不到这个文件),这个文件在一种异常情况下会被产生。这个异常情

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

9、isk。然后就可以把这个oldlogfile.log文件删除了。注意:有时候你可能会发现另一个叫做oldlogfile.log.old的文件,这是由于HMaster做了重复分割log文件的操作并发现oldlogfile.log已经存在了。这时候就需要和HRegionServer以及HMaster协商到底发生了什么,以及是否可以把old的文件删掉了。从我目前遇到的情况来看,old文件都是空的并且可以被安全删除的。HBase的每个Table在根目录下面用一个文件夹来存储,文件夹的名字就是Table的名字。在Table文件夹下面每个Region也用一个文件夹来存储,但是文件夹的名字并不是Region

10、的名字,而是Region的名字通过Jenkins Hash计算所得到的字符串。这样做的原因是Region的名字里面可能包含了不能在HDFS里面作为路径名的字符。在每个Region文件夹下面每个ColumnFamily也有自己的文件夹,在每个ColumnFamily文件夹下面就是一个个HFile文件了。所以整个文件夹结构看起来应该是这个样子的:/hbase/在每个Region文件夹下面你会发现一个.regioninfo文件,这个文件用来存储这个Region的Meta Data。通过这些Meta Data我们可以重建被破坏的.META.表,关于.regioninfo的应用你可以参考HBASE-7和

11、HBASE-1867。有一件事情前面一直没有提到,那就是Region的分割。当一个Region的数据文件不断增长并超过一个最大值的时候(你可以配置这个最大值 hbase.hregion.max.filesize),这个Region会被切分成两个。这个过程完成的非常快,因为原始的数据文件并不会被改变,系统只是简单的创建两个Reference文件指向原始的数据文件。每个Reference文件管理原始文件一半的数据。Reference文件名字是一个ID,它使用被参考的Region的名字的Hash作为前缀。例如:1278437856009925445.3323223323。Reference文件只含有

12、非常少量的信息,这些信息包括被分割的原始Region的Key以及这个文件管理前半段还是后半段。HBase使用HalfHFileReader类来访问Reference文件并从原始数据文件中读取数据。前面的架构图只并没有画出这个类,因为它只是临时使用的。只有当系统做Compaction的时候原始数据文件才会被分割成两个独立的文件并放到相应的Region目录下面,同时原始数据文件和那些Reference文件也会被清除。前面dump出来的文件结构也证实了这个过程,在每个Table的目录下面你可以看到一个叫做compaction.dir的目录。这个文件夹是一个数据交换区,用于存放split和compac

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

14、什么样的。首先这个文件是不定长的,长度固定的只有其中的两块:Trailer和FileInfo。正如图中所示的,Trailer中有指针指向其他数据块的起始点。Index数据块记录了每个Data块和Meta块的起始点。Data块和Meta块都是可有可无的,但是对于大部分的HFile,你都可以看到Data块。那么每个块的大小是如何确定的呢?这个值可以在创建一个Table的时候通过HColumnDescriptor(实际上应该称作FamilyDescriptor)来设定。这里我们可以看一个例子:NAME = docs, FAMILIES = NAME = cache, COMPRESSION = NO

15、NE, VERSIONS = 3, TTL = 2147483647, BLOCKSIZE = 65536, IN_MEMORY = false, BLOCKCACHE = false, NAME = contents, COMPRESSION = NONE, VERSIONS = 3, TTL = 2147483647, BLOCKSIZE = 65536, IN_MEMORY = false, BLOCKCACHE = false, 从这里可以看出这是一个叫做docs的Table,它有两个Family:cache和contents,这两个Family对应的HFile的数据块的大小都是64K

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

17、。后面我们会深入Data Block内部去了解它的详细构造。在HBase的配置文件中你会看到一个参数:hfile.min.blocksize.size,这个参数看上去只会用在数据迁移或者通过工具直接创建HFile的过程中。(貌似HBase创建HFile不会使用这个参数,HBase使用的是.META.表中记录的那个值)。呼,到现在为止解释的还不错吧。好了,让我们继续。有时候我们可能会想知道一个HFile是否正常以及它里面包含了什么内容。没问题,已经有一个应用程序来做这件事了。HFile.main()本身就提供了一个用来dump HFile的工具。这里有一个dump文件的例子:第一部分是存储具体数

18、据的KeyValue对,每个数据块除了开头的Magic以外就是一个个KeyValue对拼接而成。后面会详细介绍每个KeyValue对的内部构造。第二部分是Tailer块的具体内容,最后一部分是FileInfo块的具体内容。Dump HFile还有一个作用就是检查HFile是否正常。KeyValue对HFile里面的每个KeyValue对就是一个简单的byte数组。但是这个byte数组里面包含了很多项,并且有固定的结构。我们来看看里面的具体结构:开始是两个固定长度的数值,分别表示Key的长度和Value的长度。紧接着是Key,开始是固定长度的数值,RowLength表示Row的长度,紧接着是Ro

19、w,然后是固定长度的数值,表示Family的长度,然后是Family,接着是Qualifier,然后是两个固定长度的数值,表示Time Stamp和Key Type。Value部分没有这么复杂的结构,就是纯粹的数据。java.org.apache.hadoop.hbase.KeyValue是用来处理这个KeyValue,你可能会发现在这个类里面有两个方法:getKey和getRow。getKey当然是用来获取Key的内容,那么getRow是什么?其实是用来获取RowKey的。RowKey不是HBase的基本元素吗?是的,这个类已经不是纯粹的Key&Value处理,它已经打上了HBase的烙印。

20、好了,这篇文章就到此为止了,它对HBase的存储架构做了一个大致的介绍。希望这篇文章对于那些想更深入的挖掘HBase细节的人来说,能作为一个起点。HBase 数据文件在HDFS上的存储Posted on July 24, 2010 by Harry_Ding HBase 数据文件在HDFS上的存储英文原文:在HDFS上面最不明确的事情之一就是数据的冗余。它完全是自动进行的,因为无法得知其中详细的信息,我们需要做的就是相信它。HBase完全相信HDFS存储数据的安全性和完整性,并将数据文件交给HDFS存储。正是因为HDFS的数据冗余方式对于HBase来说是完全透明的,产生了一个问题:HBase的

21、效率会受到多大的影响?说的简单一点,当HBase需要存取数据时,如何保证有一份冗余的数据块离自己最近?当我们对HBase做一次MapReduce的扫描操作时,这个问题尤其显现出来。所有的RegionServer都在从HDFS上面读取数据,理想的状况当然是每个RegionServer要读取的数据都离自己很近。这个问题就牵扯到HBase的数据文件是如何在HDFS上面存储的。让我们首先抛开HBase,假设要处理的数据就是HDFS上面的数据块,看看Hadoop是如何工作的。MapReduce总是有一个建议,那就是在每个TaskTracker上面Map/Reduce程序要处理的数据在本地就有一份冗余。这

22、样程序只需要与本地数据交互,减少了网络流量并提高了效率。为了做到这一点,HDFS会把大文件分割成很多小文件来存储,我们称之为数据块(Block)。每个数据块的大小比操作系统数据块的大小要大得多,默认是64M,但通常我们选择128M,或者某个更大的值(这取决与你的文件大小,最好你的单个文件大小总是大于一个数据块)。在MapReduce中,每个数据块会被分配给一个Task,这个Task就负责处理这个数据块中的数据。所以数据块越大,产生的Task就越少,需要mapper的数量就越少。Hadoop自己知道每个数据块存储的位置,这样在任务分配的时候就可以直接在存储数据块的机器上启动Task,或者选择一个

23、最近机器启动Task。真是因为每个数据块有多份冗余,使得Hadoop有更大的选择空间。只要找到一份冗余符合条件就行了,不是吗?这样Hadoop就可以保证在MapReduce期间Task总是操作本地数据。让我们回到HBase,现在你已经理解了Hadoop是如何保证在MapReduce的过程中每个Task都尽量处理本地数据。如果你看过HBase的存储架构你就会知道HBase只是简单的将HFile和WAL log存储在HDFS上面。通过简单的调用HDFS的API来创建文件:FileSystem.create(Path path)。接下来你会关心两件事情的效率:1)随机的访问 2)通过MapReduc

24、e扫描全表。我们当然希望当每个RegionServer读取数据时存储数据的数据块就在本地。它能做到吗?第一种情况,你有两个集群,一个集群装Hadoop,另一个集群装HBase,两个集群是分隔开的,只有网线来传输数据。好了,讨论到此为止,神也帮不了你。第二种情况,你有一个大的集群,每台机器都混装了Hadoop和HBase,每个RegionServer上面都有一个DataNode(这是我们最希望看到的)。好,这样的话RegionServer就具备了从本地读取数据的前提。我们还剩下一个问题,如何保证每个RegionServer管理的Region所对应的HFile和WAL log就存在本地的DataN

25、ode上面?设想一种情况,你对HBase创建了大量的数据,每个RegionServer都管理了各自的Region,这时你重启了HBase,重启了所有的RegionServer,所有的Region都会被随机的分配给各个RegionServer,这种情况下你显然无法保证我们希望的本地数据存储。在讨论如何解决这个问题之前我们先强调一点:HBase不应该频繁的被重启,并且部署的架构不应该被频繁的改变,这是能解决这个问题的一个基础。写入HDFS的文件都有一个特点,一旦写入一个文件就无法更改(由于种种原因)。因此HBase会定期的将数据写入HDFS中并生成一个新文件。这里有一个让人惊奇的地方:HDFS足够

26、聪明,它知道如何将文件写到最合适的地方。换句话说,它知道把文件放到什么地方使得RegionServer用起来最方便。如果想知道HDFS如何做到这一点,我们需要深入学习Hadoop的源代码,看看前面提到的FileSystem.create(Path path) 具体是怎么工作的。在HDFS中实际调用的函数是:DistributedFileSystem.create(Path path), 他看起来是这个样子的:public FSDataOutputStream create(Path f) throws IOException return create(f, true);public FSDa

27、taOutputStream create(Path f, FsPermission permission, boolean overwrite, int bufferSize, short replication, long blockSize, Progressable progress) throws IOException return new FSDataOutputStream(dfs.create(getPathName(f), permission, overwrite, replication, blockSize, progress, bufferSize), statis

28、tics);其中dfs是一个连接到HDFS NameNode的DFSClient。当你向HDFS写入数据的时候,数据都流过DFSClient.DFSOutputStream,DFSClient将这些数据收集,积攒到一定程度后,作为一个Block写入到DataNode里面。将一个Block写到DataNode的过程都发生在DFSClient.DFSOutputStream.DataStreamer里面,它是一个运行在后台的守护线程。注意,从现在开始我们将逐渐揭开解决问题的秘密方法。在接收到一个Block以后,DataStreamer需要知道这个Block应该被写到哪些DataNode上面,同时它

29、也应该让NameNode知道这个Block写到了哪些DataNode上面。它的做法是联络NameNode:Hi,我这里有一个文件的一个Block,请告诉我应该写在哪些DataNode上面?nodes = nextBlockOutputStream(src);-long startTime = System.currentTimeMillis();lb = locateFollowingBlock(startTime);block = lb.getBlock();nodes = lb.getLocations();-return namenode.addBlock(src, clientName

30、);这时NameNode收到了一个添加Block的请求,它包含两个参数:src和clientName其中src标明了这个Block属于哪个文件,clientName则是client端的名称。我们跳过一些简单的步骤来看最重要的一步:public LocatedBlock getAdditionalBlock(String src, String clientName) throws IOException INodeFileUnderConstruction pendingFile = checkLease(src, clientName);fileLength = pendingFputeCo

31、ntentSummary().getLength();blockSize = pendingFile.getPreferredBlockSize();clientNode = pendingFile.getClientNode();replication = (int)pendingFile.getReplication();/ choose targets for the new block tobe allocated.DatanodeDescriptor targets = replicator.chooseTarget(replication, clientNode, null, bl

32、ockSize);最重要的一步就是replicator.chooseTarget(),它的具体实现如下:private DatanodeDescriptor chooseTarget(int numOfReplicas, DatanodeDescriptor writer, List excludedNodes, long blocksize, int maxNodesPerRack, List results) if (numOfReplicas = 0 | clusterMap.getNumOfLeaves()=0) return writer;int numOfResults = res

33、ults.size();boolean newBlock = (numOfResults=0);if (writer = null & !newBlock) writer = (DatanodeDescriptor)results.get(0);try switch(numOfResults) case 0:writer = chooseLocalNode(writer, excludedNodes, blocksize, maxNodesPerRack, results);if (numOfReplicas = 0) break;case 1:chooseRemoteRack(1, resu

34、lts.get(0), excludedNodes, blocksize, maxNodesPerRack, results);if (numOfReplicas = 0) break;case 2:if (clusterMap.isOnSameRack(results.get(0), results.get(1) chooseRemoteRack(1, results.get(0), excludedNodes, blocksize, maxNodesPerRack, results); else if (newBlock) chooseLocalRack(results.get(1), e

35、xcludedNodes, blocksize, maxNodesPerRack, results); else chooseLocalRack(writer, excludedNodes, blocksize, maxNodesPerRack, results);if (numOfReplicas = 0) break;default:chooseRandom(numOfReplicas, NodeBase.ROOT, excludedNodes, blocksize, maxNodesPerRack, results); catch (NotEnoughReplicasException

36、e) FSNamesystem.LOG.warn(“Not able to place enough replicas, still in need of ” + numOfReplicas);return writer;这段代码很清楚的说明了整个的选择过程,NameNode总是为第一份冗余优先选择本地节点作为存储空间,对于第二份冗余,则是优先选择另一个机架的节点。如果前两份冗余位于不同机架,第三份冗余偏向于选择与第一份冗余相同的机架,否则选择不同的机架。大于三份的冗余就听天由命,随机挑选节点了。总结一下,基于当前的情况,每个Region Server运行的时间越长,那么数据的存储地点就越稳定

37、,每个Region Server就能保证它要管理的数据在本地就有一份拷贝。这样无论是Scan还是MapReduce都能达到效率的最优化。最后要说的是HBase Team正在致力于重新设计MasterServer分配Region的机制。新的设计能够尽量保证每个Region被分配给拥有最多Region Block的Region Server。这将能够部分解决重启RegionServer所带来的问题。This entry was posted in CloudingComputing, HBase Research and tagged HBase HDFS 存储. Bookmark the permalink.

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 建筑/施工/环境 > 项目建议


备案号:宁ICP备20000045号-2

经营许可证:宁B2-20210002

宁公网安备 64010402000987号