“数据资产会取代20世纪传统有形资产的地位,成为资产负债表的重要组成部分。”
“数据的价值已经超越了传统企业广泛认同的价值边界”
-----海量数据的重要性。
Google和Amazon是认识到数据价值的典范,它们已经开始开发满足自己业务需求的解决方案。例如,Google在一系列的技术出版物中描述了基于商业硬件的可扩展的存储和处理系统。开源社区利用Google的这些思想实现了开源Hadoop项目的两个模块:HDFS& MapReduce。
Hadoop擅长存储任意的,半结构化的数据,甚至是非结构化的数据,可以帮助用户在分析数据的时候决定如何解释这些数据,同样允许用户随时更改数据分类的方式:一旦用户更新了算法,只需要重新分析数据。
目前Hadoop几乎是所有现有数据库系统的一种补充,它给用户提供了数据存储的无限空间,支持用户在恰当的时候存储和获取数据,并且针对大文件的存储,批量访问和流式访问做了优化。这使得用户对数据的分析变得简单快捷,但是用户同样需要访问分析后的最终数据,这种需求需要的不是批量模式而是随机访问模式,这种模式相对于数据库系统来说,相当于一种全表扫描和使用索引。
通常用户在随机访问结构化数据的时候都会查询数据库。
列式存储数据库以列为单位聚合数据,然后将列值顺序地存入磁盘,这种存储方法不同于行式存储的传统数据库,行式存储数据库联系地存储整行。
列式存储的出现主要基于这样一种假设:对于特定的查询,不是所有的值都是必须的。尤其是在分析型数据库里,这种情形很常见,因此需要选择一种更为合适的存储模式。
在这种新型的设计中,减少I/O只是众多主要因素之一,它还有其它的优点:因为列的数据天生是相似的,即便逻辑上每一行之间有轻微的不同,但仍旧比按行存储的结构聚集在一起的数据更利于压缩,因为大多数的压缩算法只关注有限的压缩窗口。
像增量压缩或前缀压缩这类的专业算法,是基于列存储的类型定制的,因而大幅提高了压缩比。更好的压缩比有利于在返回结果时降低带宽的消耗。
值得注意的是,从典型的RDBMS的角度看,HBase并不是一个列式存储的数据库,但是它利用了磁盘上的列存储格式,这也是RDBMS与HBase最大的相似之处,因为HBase以列式存储的格式在磁盘上存储数据。但它与传统的列式数据库有很大的不同,传统的列式数据库比较适合实时存储数据的场景,HBase比较适合键值对的数据存取或者有序的数据存取。
Facebook每天的增量存储到Hadoop集群的数据量超过15TB,并且随后会对所有这些数据进行处理。这些数据已不是点击流日志,用户点击了他们的网站或点击了使用Facebook提供的社交插件的网站, Facebook每一步操作都会被记录并保存,这非常适合以批处理的模式,为预测和推荐系统构建机器学习模型。
Facebook还有一个实时组件,就是他们的消息系统,其中包括聊天,涂鸦墙和电子邮件,每个月会产生超过1350亿条数据。
基于事务的ACID机制,数据强一致性。
参照完整性(负责约束不同的表结构之间的关系,利用特定域语言,即SQL,能够写出任意复杂的SQL查询语句。最终用户不需要关系实际上数据是怎样存储的,只需要关心更高层次的概念)
采用标准的SQL进行数据操作
适合事务性操作
物理结构基于行进行存储
数据量急剧增长会导致数据库变慢。
索引带来的额外负载
不适合超大规模的数据分析操作
读写分离
增加缓存,如memcached
数据分区
过去的四五年时间里,为了解决问题,创新的前进步伐由缓慢变得出奇得快,好像每周都会发布新的框架和项目来满足需求。我们看到了所谓的NoSQL解决方案问世了,NoSQL是Eric Evans针对Johan Oskarsson提出的“为新兴的新数据存储空间1①命名”问题而创造的一个名词。
正是因为这类新产品还没有合适的名称,NoSQL一举成名。在激烈的讨论中,它被认为是“SQL”的克星,或者说,它给仍旧考虑使用传统RDBMS的人带来了瘟疫……只是开个玩笑!
实际上,为特定的问题制定差异化的专用解决方案的想法并不新鲜。像Berkeley DB、Coherence、GT.M这样的系统,以及面向对象的数据库系统都已经出现了好多年,有些甚至都可以追溯到20世纪80年代初,从定义上来看,它们都属于NoSQL。
标示符号化(tagword)实际上是一个不错的选择:最新的存储系统不提供通过SQL查询数据的手段,只提供一些比较简单的、类似于API接口的方式来存取数据。
但是,也有一些工具为NoSQL数据存储提供了SQL语言的入口,用于执行一些关系数据库中常用的复杂条件查询。因此,从查询方式上的限制来说,关系型数据库和非关系型数据库并没有严格的区分。
实际上两者在底层上是有区别的,尤其涉及到模式或者ACID事务特性时,因为这与实际的存储架构是相关的。很多这一类的新系统首先做的事情是:抛弃一些限制因素以提升扩展性(这一点会在1.3.1节讨论)。例如,它们通常不支持事务或辅助索引。更重要的是,这一类系统是没有固定模式的,可以随着应用的改变而灵活变化。
一致性模型
在这本书里,我们经常会提到一致性问题,所以有必要在这里对它稍加介绍。一开始的一致性是保证数据库客户端操作的正确性,数据库必须保证每一步操作都是从一个一致的状态到下一个一致的状态。系统没有明确地指定如何实现这个功能,以便系统可以有多种选择。最终,系统要选择是进入下一个一致的状态,还是回退到上一个一致的状态,从而保证一致性。
*严格一致性:数据的变化是原子的,一经改变即时生效,这是一致性的最高形式。*
*顺序一致性:每个客户端看到的数据依照它们操作执行的顺序而变化。*
*因果一致性:客户端以因果关系顺序观察到数据的改变。*
*最终一致性:在没有更新数据的一段时间里,系统将通过广播保证副本之间的数据一致性。
*弱一致性:没有做出保证的情况下,所有的更新会通过广播的形式传递,展现给不同客户端的数据顺序可能不一样。
采用最终一致性策略的系统还可以细分为几个子类,并且这些子策略还可以共存。亚马逊的首席技术官Werner Vogels在一篇名为“Eventually Consistent”的文章中列举了这几个子类。这篇文章还谈到了CAP定理(CAPtheorem){![见Eric Brewer的论文http://www.cs.berkeley.edu/~brewer/cs262b-2004/PODC-keynote.pdf,后期Gilbert与Lynch发表了PDF版,详情见http://lpd.epfl.ch/sgilbert/pubs/BrewersConjecture-SigAct.pdf。]},其中指出,一个分布式系统只能同时实现一致性、可用性和分区容忍性(或分区容错性)中的两个。CAP定理是热点话题,不过它不是区分分布式系统的唯一方法,但CAP定理指出了,开发一套同时满足以上需求的分布式系统是比较困难的。例如,Vogels提到:
“在一系列的研究结果里发现,在较大型的分布式系统中,由于网络分隔,一致性与可用性不能同时满足。这意味着三个要素最多只能同时实现两个,不可能三者兼顾;放宽一致性的要求会提升系统的可用性……提升一致性意味着系统需要牺牲一定的可用性。”
放宽一致性来提高系统可用性是一个非常有效的提议。不过这种方案会强制让应用层去解决一致性的问题,因此也会增加系统的复杂度。
数据有多种存储的方式,包括键/值对(类似于HashMap)、半结构化的列式存储和文档结构存储。用户的应用如何存取数据?同时数据模式是否随着时间而变化?
内存还是持久化?坦率来说做出这个决定并不难,其主要原因是,我们可以将其与RDBMS进行对比,它们通常持久化存储数据到磁盘中。即使需要的是纯粹的内存模式,也仍旧有其他方案。一旦考虑持久化存储,就需要考虑选择的方案是否会影响到访问模式?
严格一致性还是最终一致性?问题是存储系统如何实现它的目标:必须降低一致性要求吗?虽然这种问题很粗浅,但是在特定的场景中会产生巨大影响。因为一致性可能会影响操作延时,即系统响应读写请求的速度。这需要权衡投入和产出后得到一个折中结果。2
分布式模式还是单机模式?这种架构看起来像什么?是仅仅运行在单个机器上,还是分布在多台机器上,但分布及扩展规则由客户端管理,换句话说,由用户自己的代码管理?也许分布式模式仅仅是个事后的工作,并且只会在用户需要扩展系统时产生问题。如果系统提供了一定的扩展性,那么需要用户采取特定的操作吗?最简单的解决方案就是一次增加一台机器,并且设置好分区(这点对于不支持虚拟分区的系统非常重要),设置时需要考虑同时提高每个分区的处理能力,因为系统的每个部分都需要提供均衡的性能。
用户必须了解自己的应用程序的访问模式。是读多写少?还是读写相当?或者是写多读少?是用范围扫描数据好,还是用随机读请求数据更好?有些系统仅仅对这些情况中的一种支持得非常好,有些系统则对各种情况都提供了很好的支持。
辅助索引支持用户按不同的字段和排序方式来访问表。这个维度覆盖了某些完全没有辅助索引支持且不保证数据排序的系统(类似于HashMap,即用户需要知道数据对应键的值),到某些可能通过外部手段简单支持这些功能的系统。如果存储系统不提供这项功能,用户的应用可以应对或自已模拟辅助索引吗?
机器会崩溃是一个客观存在的问题,需要有一套数据迁移方案来应对这种情况(关于这一点可以参考在“一致性模型”中讨论的CAP定理)。每个数据存储如何进行服务器故障处理?故障处理完毕之后是否可以正常工作?这与之前讨论的“一致性模型”维度有关系,因为失去一台服务器可能会造成数据存储的空洞(hole),甚至使整个数据存储不可用。如果替换掉故障服务器,那么恢复100%服务的难度有多大?从一个正在提供服务的集群中卸载一台服务器时,也会遇到类似的问题。
当用户需要存储TB级的数据时,尤其当这些数据差异性很小或由可读性文本组成时,压缩会带来非常好的效果,即能节省大量的原始数据存储。有些压缩算法可以将此类的数据压缩到原始文件大小的十分之一。有可选择的压缩组件吗?又有哪些压缩算法可用?
假如用户有高读写吞吐率的需求,就要考虑配置一套能够随着负载变化自动均衡处理能力的系统。虽然这样不能完全解决该问题,但是也可以帮助用户设计高读写吞吐量的程序。
RDBMS提供了很多这类的操作(因为它是一个集中式的面向单服务器的系统),但这些操作在分布式系统中较难实现,这些操作可以帮助用户避免多线程造成的资源竞争,也可以帮助用户完成无共享应用服务器的设计。有了这些比较并交换(compare and swap,CAS)操作,或者说检查并设置(check and set)操作,在设计系统的时候可以有效地降低客户端的复杂度。
众所周知,复杂的事务处理,如两阶段提交,会增加多个客户端竞争同一个资源的可能性。最糟糕的情况就是死锁,这种情况也很难解决。用户需要支持的系统采用哪种锁模型?这种锁模型能否避免等待和死锁?
稍后我们会回顾这些维度,看看HBase适合用在哪里,其优势何在。现在需要指出的是,一定要根据实际的需求来仔细选择最适合的维度。按照实际情况来设计解决方案,要知道没有硬性规定说:RDBMS不能很好地解决的问题,NoSQL就能完美解决。重要的是正确地评估需求,然后再做出明智的选择,有需要的话甚至可以采用混合使用的方案。
可以用一个有趣的词来形容这个情况—阻抗匹配(impedance match),意思就是要为一个给定问题找到一个理想的解决方案,除了使用通用的解决方案,还应该知道有什么可用的解决方案,从而找到最适合于解决该问题的系统。
RDBMS非常适合事务性操作,但不见长于超大规模的数据分析处理,因为超大规模的查询需要进行大范围的数据记录扫描或全表扫描。分析型数据库可以存储数百或数千TB的数据,在一台服务器上做查询工作的响应时间,会远远超过用户可接受的合理响应时间。垂直扩展服务器性能,即增加CPU核数和磁盘数目,也并不能很好地解决该问题。
更糟糕的是,RDBMS的等待和死锁的出现频率,与事务和并发的增加并不是线性关系,准确地说,与并发数目的平方以及事务规模的3次方甚至5次方相关3。分区通常是一个不切合实际的解决方案,因为它需要客户端采用非常复杂的方式和较高的代价来维护分区信息。
一些商业RDBMS也解决过类似的问题,但它们往往只是特定地解决了问题的某几个方面,更重要的是,它们非常非常的昂贵。而一些开源的RDBMS解决方案中,往往放弃了其中的一些甚至全部的关系型特性,如辅助索引,来换取更高的性能拓展能力。
问题是,为了性能而一直放弃以上关系型特性是否值得?用户可以反范式化(见1.3.3节)数据模型来避免等待,并且可以通过降低锁粒度的方式来尽量避免死锁。数据增长时,无需重新分区迁移数据并内嵌水平扩展性的方法。最后,用户还要面对容错和数据可用性问题,采用提高扩展性的机制,用户最终会得到一个NoSQL的解决方案,更确切地说,HBase可以满足以上多种需求。
2003年,Google发表了一篇论文,叫“TheGoogle File System”。这个分布式文件系统简称GFS,它使用商用硬件集群存储海量数据。文件系统将数据在节点之间冗余复制,这样的话,即使一台存储服务器发生故障,也不会影响数据的可用性。它对数据的流式读取也做了优化,可以变处理变读取。
不久,Google又发表了另外一篇论文,叫“Mapreduce:Simplifed Data Procesing on large clusters”MapReduce是GFS架构的一个补充,因为它能充分利用GFS集群中的每个商用服务器提供的大量CPU。MapReduce加上GFS形成了处理海量数据的核心力量,包括构建Google的搜索引擎。
不过以上描述的两个系统都缺乏实时随机存取数据的能力。GFS的另一个缺陷就是,它适合存储少许非常非常大的文件,而不适合存储成千上万的小文件,因为文件的元数据信息最终要存储在主节点的内存中,文件越多主节点的压力越大。
意识到RDBMS在大规模处理中的缺点,工程师开始考虑问题的其它切入点:摒弃关系型的特点,采用简单的API来进行增,查,改,删操作,再加一个扫描函数,再较大的键范围或全表范围上迭代扫描。这些努力的成果最终在2006年的论文“BigTable:A Distributed Storage System for Structured Data”发表了。
“BigTable”是一个管理结构化数据的分布式存储系统,它可以扩展到非常大:如在成千上万的商用服务器上存储PB级的数据。。。。一个稀疏的,分布式的,持久的多维排序映射。
Hbase实现了BigTable存储架构。
Apache HBase是Hadoop Database的简称,HBase是一个分布式,可扩展的,面向大数据存储的数据库。
HBase是一个分布式的,面向列的开源数据库。该技术来源于Fay Chang所撰写的Google论文“Bigtable:一个结构化数据的分布式存储系统”。就像BigTable利用了Google文件系统(File System)所提供的分布式数据存储一样,HBase在Hadoop智商提供了类似于BigTable的能力。HBase是Apache的Hadoop项目的子项目。HBase不同于一般的关系型数据库,它是一个适合于非结构化数据存储的数据块。另一个不同的是HBase基于列而不是基于行的模式。
Hbase是运行在Hadoop上的NoSQL数据库,它是一个分布式的和可扩展的大数据仓库,也就是说HBase能够利用HDFS的分布式处理模式,并从Hadoop的MapReduce程序模型中获益。这意味着在一组商业硬件上存储许多具有数十亿行和上百万列的大表。除去Hadoop的优势,HBase本身就是十分强大的数据库,它能够融合key/value存储模式带来实时查询的能力,以及通过MapReduce进行离线处理或者批处理的能力。总的来说,Hbase能够让你在大量的数据中查询记录,也可以从中获得综合分析报告。
HBase-Hadoop Database,是一个高可靠,高性能,面向列,可伸缩的分布式存储系统,利用HBase技术可在廉价PC Server上搭建起来大规模结构化存储集群。
HBase是Google BigTable的开源实现,类似GoogleBigtable利用GFS作为其文件存储系统,HBase利用Hadoop HDFS作为其文件存储系统;Google运行MapReduce来处理Bigtable中的海量数据,HBase同样利用Hadoop MapReduce来处理HBase中的海量数据;Google Bigtable利用Chubby作为协调服务,HBase利用Zookeeper作为对应。
Hadoop EcoSystem中的各层系统。其中HBase位于结构化存储层,HadoopHDFS为HBase提供了高可靠性的底层存储支持,Hadoop MapReduce为HBase提供了高性能的计算能力,Zookeeper为HBase提供了稳定服务和Failover机制。
此外,Pig和Hive为HBase提供了高层语言的支持,使得在HBase上进行数据统计处理变得简单。Sqoop则为HBase提供了方便的RDBMS数据导入功能,使得传统数据库数据向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,可以使用PigLatin流式编程语言来操作HBase中的数据,和Hive类似,本质最终也是编译成MapReduce Job来处理HBase表数据,适合做数据统计。
6. Hive,可以使用SQL语言来访问HBase。
(1)HBase以表的形式存储数据。表由行和列族组成。列划分为若干个列族,其逻辑视图如下:
(2)行键(RowKey)行键是字节数组,任何字符串都可以作为行键。
表中的行根据行键进行排序,数据按照Row key的字节序(byte order)排序存储;
所有对表的访问都要通过行键(单个Rowkey访问,或RowKey范围访问,或全表扫描)
(3)列族(ColumnFamily)列族必须在定义时给出,每个CF可以有一个或多个列成员(ColumnQualifier),列成员不需要在表定义时给出,新的列族成员可以随后按需、动态加入
-- 数据按CF分开存储,HBase所谓的列式存储就是根据CF分开存储(每个CF对应一个Store),这种设计非常适合于数据分析的情形。
(4)时间戳(Timestamp)每个Cell可能有多高版本,它们之间用时间戳区分。
(5)单元格(Cell)
-- Cell 由行键,列族:限定符,时间戳唯一决定
-- Cell中的数据是没有类型的,全部以字节码形式存贮。
(6)Region
HBase自动把表水平按Row划分成多个区域(Region),每个Region会保存一个表里面某段连续的数据。
每个表一开始只有一个region,随着数据不断插入表,region不断增大,当增大到一个阀值的时候,region就会等分会两个新的region;
当table中的行不断增多,就会有越来越多的region。这样一张完整的表被保存在多个Region 上。
HRegion是HBase中分布式存储和负载均衡的最小单元。最小单元表示不同的HRegion可以分布在不同的HRegionServer上。但一个HRegion不会拆分到多个server上。
Region是HBase中分布式存储和负载的最小单元。每个Table一开始只有一个region。随着记录越来越多,单个region太大,达到阈值,分裂成2个region(region split)。
不同region分布在不同的regionserver上。
Region虽然是分布式存储中的最小单元,但不是存储的最小单元。
Region由多个store组成,每个store保持一个ColumnFamily。
每个store由一个memStore和0到多个StoreFile组成。
MemStore存储在内存中。
StoreFile存储在HDFS上,底层成为HFile。
存储排序的键值映射结构。
文件由连续的块(默认64K)组成。
块的索引信息存储在文件尾部。
打开HFile时会有限在内存中加载块的信息。
Row Key:行健,Table的主键,Table中的记录按照Row Key排序。
Timestamp:时间戳,每次数据操作对应的时间戳,可以看做是数据的version number。
Column Family:列簇,Table在水平方向有一个或者多个ColumnFamily组成,一个Column Family中可以由任意多个Column组成,即Column Family支持动态扩展,无需预先定义Column的数量以及类型,所有Column均以二进制格式存储,用户需要自行进行类型转换。
当Table随着记录数不断增加而变大后,会逐渐分裂成多份splits,成为regions,一个region有【startkey,endkey】表示,不同的region会被Master分配给相应的RegionServer进行管理:
HBase中有两张特殊的Table,-ROOT-和.META.
.META.: 记录了用户表的Region信息,.META.可以由多个region。
-ROOT-: 记录了.META.表的Region信息,-ROOT-只有一个region。
Zookeeper中记录了-ROOT-表的位置。
Client访问用户数据之前需要首先访问Zookeeper,然后访问-ROOT-表,接着访问.META.表,最后才能找到用户数据的位置去访问,中间需要多次网络操作,不过client端会做cache缓存。
1. 大:一个表可以由上一行,上百万列
2. 面向列:面向列(族)的存储和权限控制,列(族)独立检索。
3. 稀疏:对于为空的列,并不占用存储空间,因此,表可以设计的非常稀疏。
4. 数据多版本:每个单元格中的数据可以有多个版本,默认情况下版本号自动分配,是单元格插入时的时间戳。
5. 数据类型单一:所有数据都是bytes【】数据,没有类型。
海量数据量存储
高并发操作
数据随机读写操作
数据强一致性
在HBase系统上运行批处理运算,最方便和使用的模型依然是MapReduce,如下图:
HBase Table和Region的关系,比较类似HDFSFILE 和Block的关系,HBase提供了配置的TableInputFormat和TableOutPutFormat API,可以方便的将HBase Table作为Hadoop MapReduce的Source和Sink,对于MapReduce Job应用开发人员来说,基本不需要关注HBase系统自身的细节。
HBase Client使用HBase的RPC机制与HMaster和HRegionServer进行通信,对于管理类操作,Client与HMaster进行RPC;对于数据读写类操作,Client与HRegionServer进行RPC。
包含访问HBase的接口,并维护cache来加快对HBase的访问,比如region的位置信息。
Zookeeper Quorum中除了存储了-ROOT-表的地址和HMaster的地址,HRegionServer也会把自己以Ephemeral方式注册到Zookeeper中,使得HMaster可以随时感知到各个HRegionServer的健康状态。此外,Zookeeper也避免了HMaster的单点问题。
通过选举,保证任何时候,集群中只有一个HMaster,HMaster与RegionServer启动时会定期Zookeeper注册。
存储所有的Region的寻址入口。
实时监控RegionServer的上线和下线信息,并实时通知给HMaster。
存储HBase的schema和table元数据,如column family。
Zookeeper的引入使得HMaster不再是单点故障。
HMaster没有单点功能,HBase中可以启动多个HMaster,通过Zookeeper的MasterElection机制保障总有一个Master运行,HMaster在功能上主要负责Table和Region的管理工作。
1. 管理用户对Table的增删改查操作
2. 管理HRegionServer的负载均衡,调整Region分布
3. 在Region Split后,负责新Region的分配。
4. 在HRegionServer停机后,负责失效HRegionServer上的Regions迁移。
HRegionServer主要负责响应用户I/O请求,向HDFS文件系统中读写数据,是HBase中最核心的模块。
HRegionServer内部管理了一些列HRegion对象,每个HRegion对应了Table中的一个Region,HRegion中由多个HStore组成。每个HStore对应了Table中的一个ColumnFamily的存储,可以看出每个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过程中进行的,这使得用户的写操作只要进入内存中就可以立即返回,保证了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文件定期会滚动出新的,HMaster首先会处理遗留的HLog文件,将其中不同Region的Log数据进行拆分,分布放到相应region的目录下,然后再将失效的region重新分配,领取到region的HRegionServer在Load Region的过程中,会发现有历史HLog需要处理,因此会Replay HLog中的数据到MemStore中,然后flush到StoreFiles,完成数据恢复。
HBase中所有数据文件都存储在HadoopHDFS文件系统上,主要包括两种文件类型:
1. HFile,HBase中KeyValue数据的存储格式,HFile是Hadoop的二进制格式文件,实际上StoreFile就是对HFile做了轻量级包装,即StoreFile底层就是HFile。
2. HLog File,HBase中WAL(Write AheadLog)的存储格式,物理上是Hadoop的Sequence File。
下图是HFile的存储格式:
首先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里面的每个KeyValue对就是一个简单的Byte数组。但是这个byte数组里面包含了很多项,并且有固定的结构。我们来看看里面的具体结构:
开始是两个固定长度的数值,分别表示Key的长度和Value的长度。紧接着是Key,开始是固定长度的数值,表示RowKey的长度,紧接着是 RowKey,然后是固定长度的数值,表示Family的长度,然后是Family,接着是Qualifier,然后是两个固定长度的数值,表示Time Stamp和Key Type(Put/Delete)。Value部分没有这么复杂的结构,就是纯粹的二进制数据了。
上图中示意了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,可参见上文描述。
HLog是一个实现Write Ahead Log类,每次写MemStore前写入HLog。
每个RegionServer维护一个HLog。
HLog定期删除旧的文件(已经持久化到HFile的数据)。
HMaster通过Zookeeper感知
处理HLog,针对不同的Region拆分Log数据。
重新分配失效region。
通过HLog信息重新写进MemStore,然后flush到HFile。
把小的HFile合并成大的,减少HFile数量,提升读效率。
执行时严重影响HBase性能
触发后不能停止。
把多个HFile合成一个。
一个store下的所有文件合并
删除过期版本数据
删除delete marker数据
指定见个时间或手动执行
当region中的数据过大时,触发splitting。用middle key分割成两个相邻region。速度很快,毫秒级。可以手动触发。
真正split。创建现有HFile的引用文件,为真正分开。Compact时才重写数据。
create 'member','member_id','address_id','info'
alter'member',{NAME=>'member_id',METHOD=>'delete'}
put'member','scutshuxue','info:age','24'
put'member','scutshuxue','info:birthday','1987-06-17'
put'member','scutshuxue','info:company','alibaba'
put'member','scutshuxue','address_id:contry','china'
put'member','scutshuxue','address_id:province','zhejiang'
put'member','scutshuxue','address_id:city','hangzhou'
get 'member','scutshuxue'
[root@node4 input]# hdfs dfs -mkdir -p /tmp/hbase/test/tsv/input
[root@node4 input]# ls
data.tsv
[root@node4 input]# hdfs dfs -putdata.tsv /tmp/hbase/test/tsv/input
[root@node4 input]#
create 'datatsv1','d'
[root@node4 tmp]# hdfs dfs -copyFromLocal-p input.file /tmp/inputfile
[root@node4 tmp]# hdfs dfs -cat/tmp/inputfile/input.file
row1,"c1","c2"
row2,"c1","c2"
row3,"c1","c2"
row4,"c1","c2"
[root@node4 tmp]#
[root@node4 ~]# hbase org.apache.hadoop.hbase.mapreduce.ImportTsv-Dimporttsv.separator="," -Dimporttsv.bulk.output=/tmp/storefileoutput1-Dimporttsv.columns=HBASE_ROW_KEY,d:c1,d:c2 datatsv1 /tmp/inputfile/input.file
SLF4J: Class path contains multiple SLF4Jbindings.
SLF4J: Found binding in[jar:file:/usr/lib/hbase/lib/slf4j-log4j12-1.6.4.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in[jar:file:/usr/lib/hadoop/lib/slf4j-log4j12-1.7.5.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Seehttp://www.slf4j.org/codes.html#multiple_bindings for an explanation.
2016-07-26 15:38:54,673 INFOzookeeper.RecoverableZooKeeper: Process identifier=hconnection-0x125f7833connecting to ZooKeeper ensemble=node2:2181,node1:2181,node3:2181
2016-07-26 15:38:54,680 INFOzookeeper.ZooKeeper: Client environment:zookeeper.version=3.4.5-transwarp--1,built on 05/08/2016 03:05 GMT
2016-07-26 15:38:54,680 INFOzookeeper.ZooKeeper: Client environment:host.name=node4
2016-07-26 15:38:54,680 INFOzookeeper.ZooKeeper: Client environment:java.version=1.7.0_71
2016-07-26 15:38:54,680 INFOzookeeper.ZooKeeper: Client environment:java.vendor=Oracle Corporation
2016-07-26 15:38:54,680 INFOzookeeper.ZooKeeper: Client environment:java.home=/usr/java/jdk1.7.0_71/jre
...
[root@node4 ~]# hbaseorg.apache.hadoop.hbase.mapreduce.LoadIncrementalHFiles /tmp/storefileoutput1datatsv1
SLF4J: Class path contains multiple SLF4Jbindings.
SLF4J: Found binding in[jar:file:/usr/lib/hbase/lib/slf4j-log4j12-1.6.4.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in[jar:file:/usr/lib/hadoop/lib/slf4j-log4j12-1.7.5.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Seehttp://www.slf4j.org/codes.html#multiple_bindings for an explanation.
2016-07-26 15:42:09,756 INFOzookeeper.RecoverableZooKeeper: Process identifier=hconnection-0x34e0907cconnecting to ZooKeeper ensemble=node2:2181,node1:2181,node3:2181
2016-07-26 15:42:09,762 INFOzookeeper.ZooKeeper: Client environment:zookeeper.version=3.4.5-transwarp--1,built on 05/08/2016 03:05 GMT
2016-07-26 15:42:09,762 INFOzookeeper.ZooKeeper: Client environment:host.name=node4
2016-07-26 15:42:09,762 INFOzookeeper.ZooKeeper: Client environment:java.version=1.7.0_71
。。。
2016-07-26 15:42:11,409 WARNhbase.HBaseConfiguration: Config option"hbase.regionserver.lease.period" is deprecated. Instead, use"hbase.client.scanner.timeout.period"
2016-07-26 15:42:11,415 DEBUGclient.ClientSmallScanner: Finished with small scan at {ENCODED =>1588230740, NAME => 'hbase:meta,,1', STARTKEY => '', ENDKEY => ''}
2016-07-26 15:42:11,416 DEBUGmapreduce.LoadIncrementalHFiles: Going to connect to serverregion=datatsv1,,1469518446829.00d8ee13a34442fcdb4172bf167aabda.,hostname=node1,60020,1468946301029, seqNum=1 for row with hfile group[{[B@15931c47,hdfs://nameservice1/tmp/storefileoutput1/d/58c7ef5fe4ac426684609135f3158f73}]
[root@node4 ~]# hbase shell
HBase Shell; enter 'help
Type "exit
Version 0.98.6-transwarp-tdh450, rUnknown,Sat May 7 11:03:34 EDT 2016
hbase(main):002:0> scan 'datatsv1'
ROW COLUMN+CELL
row1 column=d:c1, timestamp=1469518734265, value="c1"
row1 column=d:c2, timestamp=1469518734265, value="c2"
row2 column=d:c1,timestamp=1469518734265, value="c1"
row2 column=d:c2, timestamp=1469518734265, value="c2"
row3 column=d:c1, timestamp=1469518734265, value="c1"
row3 column=d:c2,timestamp=1469518734265, value="c2"
row4 column=d:c1, timestamp=1469518734265, value="c1"
row4 column=d:c2, timestamp=1469518734265, value="c2"
4 row(s) in 0.1380 seconds
hbase(main):003:0>
用户已经安装了Java运行时环境(Java Runtime Environment,JRE),Hadoop和HBase要求都至少是1.6版本以上的JRE。
64位操作系统。
使用单核CPU,同时运行3个或者更多的JAVA进程和操作系统的服务进程是不合理的。在生产系统中,通常采用的是多核处理器。四核的处理器能够满足需求,但六核的处理器更受欢迎。大多数硬件支持一个以上的CPU,所以系统可以使用两个四核CPU,达到了八核。这样每一个基本的Java进程都可以独立占有一个核,而像Java垃圾回收这样的后台进程则可以并行执行。此外还有超线程(hyperthreading),它更加加大了这种处理优势。
实践证明,使用Java时,不应该为每一个进程设置过多的内存。内存在Java术语中称为堆(heap),会在使用过程中产生许多碎片,在最坏的情况下,整个堆需要重写一次,这与众所周知的磁盘碎片整理相似,但重写堆不能在后台运行。Java在运行时环境会暂停所有进程内的逻辑并进行清理,这可能会导致不少问题。设置的堆越大,这个过程花的时间就越长。进程并不需要大量的内存,合适的内存大小可以避免上述问题。
数据存储在slave机器上,因此slave服务器需要大量的存储空间。
Master和slave一定要使用带RAID固件(RAIDfirmware)。这类磁盘与消费级磁盘的主要区别是,一旦硬件出错,RAID固件马上失效,因此DataNode进程可以快速知道发生了故障。
一般情况下最好是回避具有专有功能和选项的特殊硬件,这样这些服务器就可以根据需求通过简单的组合,达到扩容集群容量的目的。
就网络而言,推荐使用双端口千兆以太网网卡,即双通道绑定网卡。如果已经能够支持万兆网或者无线带宽(Infiniband)技术,应该毫不犹豫地使用它。
Hadoop与HBase是基于Linux系统或者Unix系统开发的,还可以在其他类Unix系统上运行。
推荐大部分运行HBase的生产系统使用CentOS或RHEL。
比较常见的文件系统是ext3,ext4,XFS。
需要Java才能运行HBase。Java1.6以及更高的版本才能很好地支持HBase。
目前HBase只能依赖特定的Hadoop版本,其中主要原因之一是HBase与Hadoop直接的远程过程调用(RemoteProcedure Call,RPC)API,RPC协议是版本化的,并且调用方与被调用方互相匹配,细微的差异就可能导致通信失败。
HBase最常使用的文件系统是HDFS,但不仅仅是HDFS,因为HBase使用的文件系统是一个可插拔的架构,用户可以使用其他任何支持Hadoop接口文件系统代替HDFS。
HBase选择HDFS作为文件系统,是因为HDFS具有所有必须的功能。HBase增加了随机存取层,是HDFS缺失的部分,是对Hadoop的理想补充。
HBase有两个运行模式:单机模式和分布式模式。
单机模式是默认模式。在单机模式中,HBase并不适用HDFS,仅使用本地文件系统---Zookeeper程序与HBase程序运行在同一个JVM程序中,Zookeeper绑定到客户端的常用端口上,以便客户端可以与HBase进行通信。
分布式模式可以进一步细分成伪分布式模式---所有守护进程都运载单个节点上,以及完全分布式模式---进程运行在物理服务器集群中。
用户已经掌握了如何使用过滤器来减少服务器端通过网络返回到客户端的数据量。HBase中还有一些特性让用户甚至可以把一部分计算也转移到数据的存放端:协处理器(coprocessor)。
使用客户端API,配合筛选机制,例如,使用过滤器或限制列族的范围,都可以控制被返回到客户端的数据量。
协处理器允许用户在region server上运行自己的代码,更准确地说允许用户执行region级的操作,并且可以使用与RDBMS中触发器类似的功能。在客户端,用户不用关心操作具体在哪里执行,HBase的分布式框架会帮助用户把这些工作变得透明。
在HBase中数据最终会存储在一张表或多张表中,使用表的主要原因是控制表中的所有列已达到共享表内的某些特性的目的。
在HBase中,所有的存储文件都被划分成若干个小存储块,这些小存储块在get或scan操作时会加载到内存中,它们类似于RDBMS中的存储单元页。这个参数的默认大小是64KB。
这里有个重要的不同点:列族的块,或者说HFile的块不同于HDFS层面的块。HDFS层面提到的块是用于拆分大文件以提供分布式存储,且便于mapreduce框架进行并行计算的存储单元---默认是64MB。HBase中HFile块大小默认是64KB,是HDFS中块大小的1024分之一,主要用于高效加载和缓存数据,并不依赖于HDFS的块大小,并且只用于HBase内部。
HBase Shell是HBase集群的命令行接口。用户可以使用Shell访问本地货远程服务器并与其进行交互,Shell同时提供了客户端和管理功能的操作。
HBase Shell是基于Jruby的,JRuby是基于Ruby实现的Java虚拟机。
HBase面对不同的编程语言拥有不同的客户端。
访问HBase的均是目前比较流行的编程语言和环境。用户可以直接使用HBase客户端API,或者使用一些能够将请求转换成API调用的代理。这些代理将原生Java API包装成其它协议,这样客户端可以使用API提供的任意外部语言来编写程序。
客户端与网关之间的协议是由当前可用选择以及远程客户端的需求决定的。最常用的就是REST(Representational State Transfer,表述性状态转移),其基于现有网络技术。实际的传输是典型的HTTP协议---它是Web应用的标准传输协议。由于协议层负责传输可互操作格式的数据,这使得REST成为异构系统之家传输数据的理想选择。
HBase最大的特点之一就是可以紧密地与Hadoop的MapReduce框架集成。
一旦region超过了配置中region大小的最大值,region就需要拆分,其会创建一个对于的split目录,它被用来临时存放两个子region相关的数据。如果拆分成功,之后它们会被移动到表目录中,并形成两个新的region,每个region代表原始region的一半。
实际的存储文件功能是由HFile类实现的,它被专门创建以达到一个目的:有效地存储HBase的数据。它们基于Hadoop的TFile类,并模仿Google的BigTable架构使用的SStable格式。
Region服务器会将数据保存到内存中,直到攒足足够多的数据再将其刷写到硬盘上,这样可以避免创建很多小文件。存储在内存中的数据是不稳定的,例如,在服务器断电的情况下数据就可能会丢失。
一个比较常见的解决这个问题的方法是预写日志(WAL);每次更新都会写入日志,只有写入成功才会通知客户端操作成功,然后服务器可以按需自由地批量处理或聚合内存中的数据。
当灾难发生的时候,WAL就是所需的生命线。类似于MySQL的binary log,WAL存储了对数据的所有更改。这在主存储器出现意外的情况下非常重要。如果服务器崩溃,它可以有效地回放日志,使得服务器恢复到服务器崩溃以前。这也就意味着如果将记录写入到WAL失败时,整个操作也会被认为是失败的。
WAL是被多个region实例共享的。
大致流程是客户端向region服务器发送put或delete操作请求,到达region服务器端后,会被分配给与行相关的HRegion实例处理,首先会被写到WAL中,然后再写入到memstore中。整个过程看起来就是HBase的写路径。
处理过程如下:
首先客户端启动一个操作来修改数据。例如,可以对put(),delete()和increment()进行调用。每一个修改都封装到一个KeyValue对象实例中,并通过RPC调用发送过去。这些调用(理想情况下)成批地发送给含有匹配region的HRegionServer。
一旦KeyValue实例到达,它们会被发送到管理相应行的Region实例。数据被写入到WAL,然后被放入到实际拥有记录的存储文件的memstore中。实质上,这就是HBase大体的写路径。
最后,当memstore达到一定的大小或是经历一个特定的时间之后,数据就会异步地连续写入到文件系统中。在写入的过程中,数据以一种不稳定的状态存放在内存中,即使在服务器完全崩溃的情况下,WAL也能保证数据不会丢失,因为实际的日志存储在HDFS上。其他服务器可以打开日志文件然后回放这些修改—恢复操作并不在这些崩溃的物理服务器上进行。
实现了WAL的类叫做HLog。当HRegion被实例化时,HLog实例会被当做一个参数传入到HRegion的构造器中。当一个region接收到一个更新操作时,它可以直接将数据保存到一个共享的WAL实例中去。
HLog类的核心功能是append()方法。
由于所有的存储文件都是不可变的,从这些文件中删除一个特定的值是做不到的,通过重写存储文件将已经被删除的单元格移除也是毫无意义的。墓碑标记就是用于此类情况的,它标记着“已删除”信息,这个标记可以是单独一个单元格,多个单元格或一整行。