【转】HBase原理和设计

简介

HBase —— Hadoop Database的简称,Google BigTable的另外一种开源实现方式,从问世之初,就为了解决用大量廉价的机器高速存取海量数据、实现数据分布式存储提供可靠的方案。从功能上来说,HBase彻彻底底是一个数据库,与咱们熟悉的Oracle、MySQL、MSSQL等同样,对外提供数据的存储和读取服务。而从应用的角度来讲,HBase与通常的数据库又有所区别,HBase自己的存取接口至关简单,不支持复杂的数据存取,更不支持SQL等结构化的查询语言;HBase也没有除了rowkey之外的索引,全部的数据分布和查询都依赖rowkey。因此,HBase在表的设计上会有很严格的要求。架构上,HBase是分布式数据库的典范,这点比较像MongoDB的sharding模式,能根据键值的大小,把数据分布到不一样的存储节点上,MongoDB根据configserver来定位数据落在哪一个分区上,HBase经过访问Zookeeper来获取-ROOT-表所在地址,经过-ROOT-表获得相应.META.表信息,从而获取数据存储的region位置。数据库

 

架构

上面提到,HBase是一个分布式的架构,除去底层存储的HDFS外,HBase自己从功能上能够分为三块:Zookeeper群、Master群和RegionServer群。缓存

  • Zookeeper群:HBase集群中不可缺乏的重要部分,主要用于存储Master地址、协调Master和RegionServer等上下线、存储临时数据等等。
  • Master群:Master主要是作一些管理操做,如:region的分配,手动管理操做下发等等,通常数据的读写操做并不须要通过Master集群,因此Master通常不须要很高的配置便可。
  • RegionServer群:RegionServer群是真正数据存储的地方,每一个RegionServer由若干个region组成,而一个region维护了必定区间rowkey值的数据,整个结构以下图:
hbase

HBase结构图安全

上图中,Zookeeper(简称ZK)是一个集群,一般有奇数个ZK服务组成。Master为了服务可用性,也建议部署成集群方式,由于Master是整个管理操做的发起者,若是Master一旦发生意外停机,整个集群将会没法进行管理操做,因此Master也必须有多个,固然多个Master也有主从之分,如何区分哪一个是主,哪一个是从?关键看哪一个Master能竞争到ZK上对应Master目录下的锁,持有该目录锁的Master为主Master,其余从Master轮询竞争该锁,因此一旦主Master发生意外停机,从Master很快会由于竞争到Master文件夹上的锁而接管服务。
RegionServer(简称RS)在非Replication模式下,整个系统中都是惟一的,也就是说,在整个非Replication的HBase集群中,每台RS上保存的数据都不同,因此相对于前面二者,该模式下的RS并非高可用的,至少RS可能存在单点故障的问题,可是因为HBase内部数据分region存储和region能够迁移的机制,RS服务的单点故障可能会在极小代价下很快恢复,可是一旦停掉的RS上有-ROOT-或者.META.表的region,那后果仍是比较严重,由于数据节点的RS停机,只会在短期内影响该台RS上的region不可访问,等到region迁移完成后便可恢复,若是是-ROOT-、.META.所在的RS停机,整个HBase的新的求情都将受到影响,由于须要经过.META.表来路由,从而寻找到region所在RS的地址。架构

数据组织

整个架构中,ZK用于服务协调和整个集群运行过程当中部分信息的保存和-ROOT-表地址定位,Master用于集群内部管理,因此剩下的RS主要用于处理数据。
RS是处理数据的主要场所,那么在RS内部的数据是怎么分布的?其实RS自己只是一个容器,其定义了一些功能线程,好比:数据合并线程(compact thread)、storeFile分割线程(split thread)等等。容器中的主要对象就是region,region是一个表根据自身rowkey范围划分的一部分,一个表能够被划分红若干部分,也就是若干个region,region能够根据rowkey范围不一样而被分布在不一样的RS上(固然也能够在同一个RS上,但不建议这么作)。一个RS上能够包含多个表的region,也能够只包含一个表的部分region,RS和表是两个不一样的概念。
这里还有一个概念——列簇。对HBase有一些了解的人,或多或少据说过:HBase是一个列式存储的数据库,而这个列式存储中的列,实际上是区别于通常数据库的列,这里的列的概念,就是列簇,列簇,顾名思义就是不少列的集合,而在数据存储上来说,不一样列簇的数据,必定是分开存储的,即便是在同一个region内部,不一样的列簇也存储在不一样的文件夹中,这样作的好处是,通常咱们定义列簇的时候,一般会把相似的数据放入同一个列簇,不一样的列簇分开存储,有利于数据的压缩,而且HBase自己支持多种压缩方式。app

原理

前面介绍了HBase的通常架构,咱们知道了HBase有ZK、Master和RS等组成,本节咱们来介绍下HBase的基本原理,从数据访问、RS路由到RS内部缓存、数据存储和刷写再到region的合并和拆分等等功能。异步

RegionServer定位

访问HBase经过HBase客户端(或API)进行,整个HBase提供给外部的地址,实际上是ZK的入口,前面也介绍了,ZK中有保存-ROOT-所在的RS地址,从-ROOT-表能够获取.META.表信息,根据.META.表能够获取region在RS上的分布,整个region寻址过程大体以下:分布式

direct

RS定位过程oop

  1. 首先,Client经过访问ZK来请求目标数据的地址。
  2. ZK中保存了-ROOT-表的地址,因此ZK经过访问-ROOT-表来请求数据地址。
  3. 一样,-ROOT-表中保存的是.META.的信息,经过访问.META.表来获取具体的RS。
  4. .META.表查询到具体RS信息后返回具体RS地址给Client。
  5. Client端获取到目标地址后,而后直接向该地址发送数据请求。

上述过程实际上是一个三层索引结构,从ZK获取-ROOT-信息,再从-ROOT-获取.META.表信息,最后从.META.表中查到RS地址后缓存。这里有几个问题:性能

  • 既然ZK中能保存-ROOT-信息,那么为何不把.META.信息直接保存在ZK中,而须要经过-ROOT-表来定位?
  • Client查找到目标地址后,下一次请求还须要走ZK  —> -ROOT- —> .META.这个流程么?

先来回答第一个问题:为何不直接把.META.表信息直接保存到ZK中?主要是为了保存的数据量考虑,ZK中不宜保存大量数据,而.META.表主要是保存Region和RS的映射信息,region的数量没有具体约束,只要在内存容许的范围内,region数量能够有不少,若是保存在ZK中,ZK的压力会很大。因此,经过一个-ROOT-表来转存到RS中是一个比较理想的方案,相比直接保存在ZK中,也就多了一层-ROOT-表的查询,对性能来讲影响不大。
第二个问题:每次访问都须要走ZK –> -ROOT- —> .META.的流程么?固然不须要,Client端有缓存,第一次查询到相应region所在RS后,这个信息将被缓存到Client端,之后每次访问都直接从缓存中获取RS地址便可。固然这里有个意外:访问的region若果在RS上发生了改变,好比被balancer迁移到其余RS上了,这个时候,经过缓存的地址访问会出现异常,在出现异常的状况下,Client须要从新走一遍上面的流程来获取新的RS地址。整体来讲,region的变更只会在极少数状况下发生,通常变更不会很大,因此在整个集群访问过程当中,影响能够忽略。操作系统

Region数据写入

HBase经过ZK —> -ROOT-  —> .META.的访问获取RS地址后,直接向该RS上进行数据写入操做,整个过程以下图:

data_write

RegionServer数据操做过程

Client经过三层索引得到RS的地址后,便可向指定RS的对应region进行数据写入,HBase的数据写入采用WAL(write ahead log)的形式,先写log,后写数据。HBase是一个append类型的数据库,没有关系型数据库那么复杂的操做,因此记录HLog的操做都是简单的put操做(delete/update操做都被转化为put进行)

HLog

HLog写入

HLog是HBase实现WAL方式产生的日志信息,其内部是一个简单的顺序日志,每一个RS上的region都共享一个HLog,全部对于该RS上的region数据写入都被记录到该HLog中。HLog的主要做用就是在RS出现意外崩溃的时候,能够尽可能多的恢复数据,这里说是尽可能多,由于在通常状况下,客户端为了提升性能,会把HLog的auto flush关掉,这样HLog日志的落盘全靠操做系统保证,若是出现意外崩溃,短期内没有被fsync的日志会被丢失。

HLog过时

HLog的大量写入会形成HLog占用存储空间会愈来愈大,HBase经过HLog过时的方式进行HLog的清理,每一个RS内部都有一个HLog监控线程在运行,其周期能够经过hbase.master.cleaner.interval进行配置。
HLog在数据从memstore flush到底层存储上后,说明该段HLog已经再也不被须要,就会被移动到.oldlogs这个目录下,HLog监控线程监控该目录下的HLog,当该文件夹下的HLog达到hbase.master.logcleaner.ttl设置的过时条件后,监控线程当即删除过时的HLog。

Memstore

数据存储

memstore是region内部缓存,其大小经过HBase参数hbase.hregion.memstore.flush.size进行配置。RS在写完HLog之后,数据写入的下一个目标就是region的memstore,memstore在HBase内部经过LSM-tree结构组织,因此可以合并大量对于相同rowkey上的更新操做。
正是因为memstore的存在,HBase的数据写入都是异步的,并且性能很是不错,写入到memstore后,该次写入请求就能够被返回,HBase即认为该次数据写入成功。这里有一点须要说明,写入到memstore中的数据都是预先按照rowkey的值进行排序的,这样有利于后续数据查找。

数据刷盘

memstore中的数据在必定条件下会进行刷写操做,使数据持久化到相应的存储设备上,触发memstore刷盘的操做有多种不一样的方式以下图:

flush

Memstore刷写流程

以上1,2,3均可以触发memstore的flush操做,可是实现的方式不一样:

  • 1经过全局内存控制,触发memstore刷盘操做。memstore总体内存占用上限经过参数hbase.regionserver.global.memstore.upperLimit进行设置,固然在达到上限后,memstore的刷写也不是一直进行,在内存降低到hbase.regionserver.global.memstore.lowerLimit配置的值后,即中止memstore的刷盘操做。这样作,主要是为了防止长时间的memstore刷盘,会影响总体的性能。
  • 在该种状况下,RS中全部region的memstore内存占用都没达到刷盘条件,但总体的内存消耗已经到一个很是危险的范围,若是持续下去,颇有可能形成RS的OOM,这个时候,须要进行memstore的刷盘,从而释放内存。
  • 2手动触发memstore刷盘操做
  • HBase提供API接口,运行经过外部调用进行memstore的刷盘
  • 3 memstore上限触发数据刷盘
  • 前面提到memstore的大小经过hbase.hregion.memstore.flush.size进行设置,当region中memstore的数据量达到该值时,会自动触发memstore的刷盘操做。

刷盘影响

memstore在不一样的条件下会触发数据刷盘,那么整个数据在刷盘过程当中,对region的数据写入等有什么影响?memstore的数据刷盘,对region的直接影响就是:在数据刷盘开始到结束这段时间内,该region上的访问都是被拒绝的,这里主要是由于在数据刷盘结束时,RS会对改region作一个snapshot,同时HLog作一个checkpoint操做,通知ZK哪些HLog能够被移到.oldlogs下。从前面图上也能够看到,在memstore写盘开始,相应region会被加上UpdateLock锁,写盘结束后该锁被释放。

StoreFile

memstore在触发刷盘操做后会被写入底层存储,每次memstore的刷盘就会相应生成一个存储文件HFile,storeFile即HFile在HBase层的轻量级分装。数据量的持续写入,形成memstore的频繁flush,每次flush都会产生一个HFile,这样底层存储设备上的HFile文件数量将会愈来愈多。无论是HDFS仍是Linux下经常使用的文件系统如Ext四、XFS等,对小而多的文件上的管理都没有大文件来的有效,好比小文件打开须要消耗更多的文件句柄;在大量小文件中进行指定rowkey数据的查询性能没有在少许大文件中查询来的快等等。

Compact

大量HFile的产生,会消耗更多的文件句柄,同时会形成RS在数据查询等的效率大幅度降低,HBase为解决这个问题,引入了compact操做,RS经过compact把大量小的HFile进行文件合并,生成大的HFile文件。
RS上的compact根据功能的不一样,能够分为两种不一样类型,即:minor compact和major compact。

  • Minor Compact

minor compact又叫small compact,在RS运行过程当中会频繁进行,主要经过参数hbase.hstore.compactionThreshold进行控制,该参数配置了HFile数量在知足该值时,进行minor compact,minor compact只选取region下部分HFile进行compact操做,而且选取的HFile大小不能超过hbase.hregion.max.filesize参数设置。

  • Major Compact

相反major compact也被称之为large compact,major compact会对整个region下相同列簇的全部HFile进行compact,也就是说major compact结束后,同一个列簇下的HFile会被合并成一个。major compact是一个比较长的过程,对底层I/O的压力相对较大。
major compact除了合并HFile外,另一个重要功能就是清理过时或者被删除的数据。前面提到过,HBase的delete操做也是经过append的方式写入,一旦某些数据在HBase内部被删除了,在内部只是被简单标记为删除,真正在存储层面没有进行数据清理,只有经过major compact对HFile进行重组时,被标记为删除的数据才能被真正的清理。
compact操做都有特定的线程进行,通常状况下不会影响RS上数据写入的性能,固然也有例外:在compact操做速度跟不上region中HFile增加速度时,为了安全考虑,RS会在HFile达到必定数量时,对写入进行锁定操做,直到HFile经过compact降到必定的范围内才释放锁。

Split

compact将多个HFile合并单个HFile文件,随着数据量的不断写入,单个HFile也会愈来愈大,大量小的HFile会影响数据查询性能,大的HFile也会,HFile越大,相对的在HFile中搜索的指定rowkey的数据花的时间也就越长,HBase一样提供了region的split方案来解决大的HFile形成数据查询时间过长问题。
一个较大的region经过split操做,会生成两个小的region,称之为Daughter,通常Daughter中的数据是根据rowkey的之间点进行切分的,region的split过程大体以下图:

hbase_split

region split流程

  1. region先更改ZK中该region的状态为SPLITING。
  2. Master检测到region状态改变。
  3. region会在存储目录下新建.split文件夹用于保存split后的daughter region信息。
  4. Parent region关闭数据写入并触发flush操做,保证全部写入Parent region的数据都能持久化。
  5. 在.split文件夹下新建两个region,称之为daughter A、daughter B。
  6. Daughter A、Daughter B拷贝到HBase根目录下,造成两个新的region。
  7. Parent region通知修改.META.表后下线,再也不提供服务。
  8. Daughter A、Daughter B上线,开始向外提供服务。
  9. 若是开启了balance_switch服务,split后的region将会被从新分布。

上面1 ~ 9就是region split的整个过程,split过程很是快,速度基本会在秒级内,那么在这么快的时间内,region中的数据怎么被从新组织的?
其实,split只是简单的把region从逻辑上划分红两个,并无涉及到底层数据的重组,split完成后,Parent region并无被销毁,只是被作下线处理,再也不对外部提供服务。而新产生的region Daughter A和Daughter B,内部的数据只是简单的到Parent region数据的索引,Parent region数据的清理在Daughter A和Daughter B进行major compact之后,发现已经没有到其内部数据的索引后,Parent region才会被真正的清理。

HBase设计

HBase是一个分布式数据库,其性能的好坏主要取决于内部表的设计和资源的分配是否合理。

Rowkey设计

rowkey是HBase实现分布式的基础,HBase经过rowkey范围划分不一样的region,分布式系统的基本要求就是在任什么时候候,系统的访问都不要出现明显的热点现象,因此rowkey的设计相当重要,通常咱们建议rowkey的开始部分以hash或者MD5进行散列,尽可能作到rowkey的头部是均匀分布的。禁止采用时间、用户id等明显有分段现象的标志直接看成rowkey来使用。

列簇设计

HBase的表设计时,根据不一样需求有不一样选择,须要作在线查询的数据表,尽可能不要设计多个列簇,咱们知道,不一样的列簇在存储上是被分开的,多列簇设计会形成在数据查询的时候读取更多的文件,从而消耗更多的I/O。

TTL设计

选择合适的数据过时时间也是表设计中须要注意的一点,HBase中容许列簇定义数据过时时间,数据一旦超过过时时间,能够被major compact进行清理。大量无用历史数据的残余,会形成region体积增大,影响查询效率。

Region设计

通常地,region不宜设计成很大,除非应用对阶段性性能要求不少,可是在未来运行一段时间能够接受停服处理。region过大会致使major compact调用的周期变长,而单次major compact的时间也相应变长。major compact对底层I/O会形成压力,长时间的compact操做可能会影响数据的flush,compact的周期变长会致使许多删除或者过时的数据不能被及时清理,对数据的读取速度等都有影响。
相反,小的region意味着major compact会相对频繁,可是因为region比较小,major compact的相对时间较快,并且相对较多的major compact操做,会加速过时数据的清理。
固然,小region的设计意味着更多的region split风险,region容量太小,在数据量达到上限后,region须要进行split来拆分,其实split操做在整个HBase运行过程当中,是被不怎么但愿出现的,由于一旦发生split,涉及到数据的重组,region的再分配等一系列问题。因此咱们在设计之初就须要考虑到这些问题,尽可能避免region的运行过程当中发生split。
HBase能够经过在表建立的时候进行region的预分配来解决运行过程当中region的split产生,在表设计的时候,预先分配足够多的region数,在region达到上限前,至少有部分数据会过时,经过major compact进行清理后, region的数据量始终维持在一个平衡状态。
region数量的设计还须要考虑内存上的限制,经过前面的介绍咱们知道每一个region都有memstore,memstore的数量与region数量和region下列簇的数量成正比,一个RS下memstore内存消耗:

Memory = memstore大小 * region数量 * 列簇数量

若是不进行前期数据量估算和region的预分配,经过不断的split产生新的region,容易致使由于内存不足而出现OOM现象。

相关文章
相关标签/搜索