1、HBase概述
HBase是一个分布式的、面向列的开源数据库,该技术来源于Fay Chang所撰写的Google论文——“Bigtable:一个结构化数据的分布式存储系统”。就像Bigtable利用了Google文件系统(File System)所提供的分布式数据存储一样,HBase在Hadoop之上提供了类似于Bigtable的能力。
HBase是Hadoop Database的简称,它是Apache的Hadoop项目中的一个子项目,HBase依托于Hadoop的HDFS作为其最基本的存储单元,通过使用Hadoop的HDFS工具就可以看到这些数据存储文件夹的结构,还可以通过MapReduce的框架对HBase进行操作。HBase不同于一般的关系型数据库,它是一个适合于非结构化数据存储的数据库,另一个不同点是HBase采用基于列(更准确地说是基于列族)而不是基于行的模式。
HBase是一个数据模型,可以提供快速随机访问海量结构化数据的优势,它还充分利用了Hadoop的文件系统提供的容错能力。使用HBase在HDFS上读取消费/随机访问数据,并提供读写访问,如下图所示:
2、HBase的体系结构
在HBase中,表被分割成区域(Region,对应于列族),并由区域服务器(RegionServer,对应于一个机器节点)提供服务,区域被列族垂直分为“Store_File”,Store_File又以HFile文件的形式被保存在HDFS文件系统中,下图展示了HBase的体系结构。
Region内部解析:刚开始时数据都直接保存到mem_store内存中,待到mem_store满时,便会flush成一个Store_File文件,直到增长到一定阈值,便会触发Compact合并操作,将多个Store_File文件合并成一个大的Store_File文件(对应于HFile),同时进行版本合并和数据删除;由于HDFS数据块大小默认是128M,为了节省空间及提高效率,这个大的Store_File文件(也就是HFile),将以一个数据块的大小128M保存到HDFS文件系统中。
HBase主要由三部分构成,即客户端Client、主服务器HMaster以及区域服务器Region Server,区域服务器可按照应用需求进行添加和删除。
A、客户端Client
包含访问HBase的接口,并维护cache来加快对HBase的访问,比如Region的位置信息等。
B、主服务器HMaster
为Region Server分配Region;
负责Region Server的负载均衡;
发现失效的Region Server并重新分配其上的Region;
管理用户对table的增删改查操作。
C、区域服务器RegionServer
Region Server维护Region,处理对这些Region的IO请求;
Region Server负责切分在运行过程中变得过大的Region。
D、Zookeeper
通过选举,保证在任何时候,集群中只有一个主服务器HMaster,HMaster与RegionServer启动时会向Zookeeper注册;
存储所有Region的寻址入口;
实时监控RegionServer的上线和下线信息,并实时通知给HMaster;
存储HBase的schema和table元数据;
默认情况下,HBase管理Zookeeper实例,比如启动或者停止Zookeeper;
Zookeeper的引入,解决了HMaster单点故障的问题。
3、HBase的特点
A、HBase的存储机制
HBase是一个面向列的数据库,在表中它由行排序,表模式定义为列族,也就是键值对,一个表有多个列族(需要在创建表时就指定)以及每一个列族可以有任意数量的列,后续列的值连续地存储在磁盘上。表中的每个单元格值都具有时间戳(HBase默认添加)。总的来说,在一个HBase中:
表是行的集合;
行是列族的结合;
列族是列的集合;
列是键值对的集合。
举一个HBase表的实例,其表结构如下图所示,需要注意的是,rowkey(类似于关系型数据库中的主键)是可以重复的,相同的rowkey记录的是同一行数据;一个列族对应于一个Region。
B、HBase和HDFS
两者都具有良好的容错性和扩展性,都可以扩展成百上千个节点。HBase适用于大数据量存储,大数据量高并发操作,适用于需要对数据进行随机读写的简单操作;HDFS适用于批处理场景,不支持数据随机查找,不适合增量数据处理,不支持数据更新。
下面的表格简单地类比了HBase和HDFS。
C、面向行和面向列
面向列的数据库是将数据表作为数据列的部分进行存储,而不是作为数据行进行存储;面向列的数据库相对来说,对于大数据量的查询操作具有较高的效率,而面向行的数据库,对于增、删、改这些操作具有较高的效率。下面的表格对列式数据库和行式数据库进行了简单的类比。
D、HBase和RDBMS
下面的表格简单类比了基于列的数据库HBase与通用的关系型数据库RDBMS。
E、HBase容错性
HMaster容错:Zookeeper重新选举出一个新的HMaster,在没有HMaster的过程中,数据读取仍照常进行,但region切分、负载均衡等操作无法进行;
RegionServer容错:定时向Zookeeper汇报心跳,如果一定时间内未出现心跳,HMaster将该RegionServer上的Region重新分配到其他RegionServer上,失效服务器上“预写”日志由主服务器进行分割并派送给新的RegionServer;
在分布式系统环境中,无法避免系统出错或者宕机,一旦HRegionServer意外退出,mem_store中的内存数据就会丢失,引入HLog就是为了防止这种情况。其工作机制是:每个HRegionServer中都会有一个HLog对象,每次用户操作写入mem_store的同时,也会写一份数据到HLog文件,HLog文件定期会滚动出新,并删除旧的文件(对应已经持久化到Store_File中的数据)。当HRegionServer意外终止后,HMaster会通过Zookeeper感知,HMaster首先处理遗留的HLog文件,将不同Region的log数据拆分,分别放到相应Region目录下,然后再将失效的Region(带有刚刚拆分的log)重新分配,领取到这些Region的HRegionServer在Load Region的过程中,会发现有历史HLog需要处理,因此会Replay HLog中的数据到mem_store中,然后flush到Store_File,完成数据恢复。
Zookeeper容错:Zookeeper是一个可靠的服务,一般配置3到5个Zookeeper实例。
4、HBase数据读/写流程
A、HBase表数据的写入流程
1)client先去访问Zookeeper,从Zookeeper上获取meta表的位置信息;
以前的版本hbase的系统表除了meta表还有root表;
在root表中存储了meta表的位置信息;
新版本中将meta表的位置信息直接存入ZooKeeper中
2)client向meta表的region所在的regionserver发起访问,读取meta表的数据,获取HBase集群上所有的表的元数据;
3)根据meta表的元数据信息(如某张表有几个region及region如何分配及每个reigon的startkey和stopkey等),client找到当前要写入的表对应的Region及所在RegionServer;
4)client向对应的RegionServer发起写入请求;
5)RegionServer收到client请求并响应,client先把数据写入到Hlog防止数据丢失;
6)再把数据写入到mem_store内存缓存区(默认大小128M);
7)当数据写入到Hlog及mem_store内存缓存区都成功时,写入才算成功;
8)当mem_store达到128M或其他的因素触发,会将mem_store中的数据flush成Store_File;
9)当Store_File越来越多,会触发compact合并,将多个Store_File文件最终合并成一个文件;
合并分为minor compact和major compact;
在大合并期间打上‘删除’标签的cell或者过期的cell会被统一清理
10)当某个store下的某个storeFile文件的最终合并后的大小达到10G时,会触发整个region的split分割,一个region一分为二,由master进行分配;
B、HBase表数据的读取过程
1)client先去访问Zookeeper,从Zookeeper上获取meta表的位置信息;
2)client向meta表的Region所在的RegionServer发起访问,读取meta表的数据,获取HBase集群上所有表的元数据;
3)根据meta表的元数据信息(如某张表有几个Region及Region如何分配及每个Reigon的startkey和stopkey),client找到当前要写入的表对应的Region及所在RegionServer;
4)client向对应的RegionServer发起读请求;
5)RegionServer收到客户端的读请求,会先扫描mem_store,再扫描blockcache(读缓存),没有找到数据,再去读取Store_File文件;
6)RegionServer将数据返回给client。
5、Region分裂详解
A、Region分裂的产生
在最初时,HBase中数据量会比较小,仅需要保存到一个Region上(如保存于下图中的RegionServer1)。随着数据量的增大,当HBase通过自检查发现满足相应的条件,触发Region分裂(在HBase中不可避免会发生)使得Region数量变多并进行重新分布,如RegionServer1上先前的Region分裂成两个,并将其中一个Region中的数据拷贝到其他RegionServer上的Region中,如下图中的RegionServer2,该过程简单描述如下图。
因此,由于Region分裂的存在,需要采取其他必要的措施或进行合理规划,将Region分裂产生的影响尽可能降到最低,防止对网络造成过大压力,使整个环境崩溃。
B、寻找分裂点SplitPoint
Region分裂策略会触发Region分裂,分裂开始之后的第一件事是寻找分裂点-SplitPoint。所有默认分裂策略,无论是ConstantSizeRegionSplit Policy、IncreasingToUpperBoundRegionSplitPolicy 抑或是SteppingSplit Policy,对于分裂点的定义都是一致的。当然,用户手动执行分裂时是可以指定分裂点进行分裂的。
那分裂点是如何定位的呢? 整个Region中最大store中的最大文件中最中心的一个block的首个rowkey 。这是一句比较消耗脑力的语句,需要细细品味。另外,HBase还规定,如果定位到的rowkey是整个文件的首个rowkey或者最后一个rowkey的话,就认为没有分裂点。
什么情况下会出现没有分裂点的场景呢?最常见的就是一个文件只有一个block,执行split的时候就会发现无法分裂。很多朋友在测试split的时候往往都是新建一张新表,然后往新表中插入几条数据并执行一下flush,再执行split,奇迹般地发现数据表并没有真正执行分裂。
C、Region核心分裂流程
HBase将整个分裂过程包装成了一个事务,意图能够保证分裂事务的原子性。整个分裂事务过程分为如下三个阶段:prepare– execute – rollback,操作模板如下图所示:
prepare阶段:在内存中初始化两个子Region,具体是生成两个HRegionInfo对象,包含tableName、regionName、startkey、endkey等。同时会生成一个transactionjournal,这个对象用来记录分裂的进展,具体见rollback阶段。
execute阶段:分裂的核心操作。如下图所示:
具体过程如下:
1)、RegionServer更改ZK节点/region-in-transition中该Region的状态为SPLITING;
2)、Master通过watch节点/region-in-transition检测到Region状态改变,并修改内存中Region的状态,在Master页面RIT模块就可以看到Region执行split的状态信息;
3)、在父存储目录下新建临时文件夹.split保存split后的daughter region信息;
4)、关闭parent region:parent region关闭数据写入并触发flush操作,将写入Region的数据全部持久化到磁盘。此后,短时间内客户端落在父Region上的请求都会抛出异常NotServingRegionException;
5)、核心分裂步骤:在.split文件夹下新建两个子文件夹,称之为daughter A、daughter B,并在文件夹中生成reference文件,分别指向父Region中对应文件。这个步骤是所有操作步骤中最核心的一个环节;
6)、父Region分裂为两个子Region后,将daughter A、daughter B拷贝到HBase根目录下,形成两个新的Region;
7)、parent region通知修改hbase.meta表后下线,不再提供服务。下线后parent region在meta表中的信息并不会马上删除,而是标注split列、offline列为true,并记录两个子Region;
8)、开启daughter A、daughter B两个子Region。通知修改hbase.meta表,正式对外提供服务。
rollback阶段:如果execute阶段出现异常,则执行rollback操作。为了实现回滚,整个分裂过程被分为很多子阶段,回滚程序会根据当前进展到哪个子阶段清理对应的垃圾数据。代码中使用JournalEntryType来表征各个子阶段。
D、Region切分事务性保证
整个Region分裂是一个比较复杂的过程,涉及到父Region中HFile文件的切分、两个子Region的生成、系统meta元数据的更改等很多子步骤,因此必须保证整个分裂过程的事务性,即要么分裂完全成功,要么分裂完全未开始,在任何情况下也不能出现分裂只完成一半的情况。
为了实现分裂的事务性,hbase设计了使用状态机的方式保存分裂过程中的每个子步骤状态,这样一旦出现异常,系统可以根据当前所处的状态决定是否回滚,以及如何回滚。遗憾的是,目前实现中这些中间状态都只存储在内存中,因此一旦在分裂过程中出现RegionServer宕机的情况,有可能会出现分裂处于中间状态的情况,也就是RIT状态。这种情况下需要使用hbck工具进行具体查看并分析解决方案。在2.0版本之后,HBase实现了新的分布式事务框架Procedure V2(HBASE-12439),新框架将会使用HLog存储这种单机事务(DDL操作、Split操作、Move操作等)的中间状态,因此可以保证即使在事务执行过程中参与者发生了宕机,依然可以使用HLog作为协调者对事务进行回滚操作或者重试提交,大大减少甚至杜绝RIT现象。
E、Region分裂对其他模块的影响
通过Region分裂流程的了解,我们知道整个Region分裂过程并没有涉及数据的移动,所以分裂本身的成本并不是很高,可以很快完成。分裂后子Region的文件实际没有任何用户数据,文件中存储的仅是一些元数据信息,如分裂点rowkey等,那通过引用文件如何查找数据呢?子Region的数据实际在什么时候完成真正迁移?数据迁移完成之后父Region什么时候会被删掉?
1)通过reference文件如何查找数据?
这里就能看到reference文件名、文件内容的实际意义,整个操作流程如下所示:
i.根据reference文件名(Region名+真实文件名)定位到真实数据所在文件路径;
ii.定位到真实数据文件就可以在整个文件中扫描待查KV了么?非也。因为reference文件通常都只引用了数据文件的一半数据,以分裂点为界,要么上半部分文件数据,要么下半部分数据。那到底是哪部分数据?分裂点又是哪个点?还记得上文提到的reference文件的文件内容吧,没错,就记录在该文件中。
2)父Region的数据什么时候会迁移到子Region目录?
答案是子Region发生major_compaction时。我们知道compaction的执行实际上是将store中所有小文件一个KV一个KV从小到大读出来之后再顺序写入一个大文件,完成之后再将小文件删掉,因此compaction本身就需要读取并写入大量数据。子Region执行major_compaction后会将父目录中属于该子Region的所有数据读出来并写入子Region目录数据文件中。可见将数据迁移放到compaction这个阶段来做,是一件顺其自然的事情。
3)父Region什么时候会被删除?
实际上HMaster会启动一个线程定期遍历检查所有处于splitting状态的父Region,以确定父Region是否可以被清理。检测线程首先会在meta表中揪出所有split列为true的Region,并加载出其分裂后生成的两个子Region(meta表中splitA列和splitB列),只需要检查这两个子Region是否还存在引用文件,如果都不存在引用文件就可以认为该父Region对应的文件可以被删除。
领取专属 10元无门槛券
私享最新 技术干货