Hbase:原理和设计

 转载自:http://www.sysdb.cn/index.php/2016/01/10/hbase_principle/ ,感谢原做者。php

 

简介

  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

 

  上图中,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主要用于处理数据。app

  RS是处理数据的主要场所,那么在RS内部的数据是怎么分布的?其实RS自己只是一个容器,其定义了一些功能线程,好比:数据合并线程 (compact thread)、storeFile分割线程(split thread)等等。容器中的主要对象就是region,region是一个表根据自身rowkey范围划分的一部分,一个表能够被划分红若干部分,也就 是若干个region,region能够根据rowkey范围不一样而被分布在不一样的RS上(固然也能够在同一个RS上,但不建议这么作)。一个RS上能够 包含多个表的region,也能够只包含一个表的部分region,RS和表是两个不一样的概念。异步

  这里还有一个概念——列簇。对HBase有一些了解的人,或多或少据说过:HBase是一个列式存储的数据库,而这个列式存储中的列,实际上是区别于 通常数据库的列,这里的列的概念,就是列簇,列簇,顾名思义就是不少列的集合,而在数据存储上来说,不一样列簇的数据,必定是分开存储的,即便是在同一个 region内部,不一样的列簇也存储在不一样的文件夹中,这样作的好处是,通常咱们定义列簇的时候,一般会把相似的数据放入同一个列簇,不一样的列簇分开存 储,有利于数据的压缩,而且HBase自己支持多种压缩方式。分布式

 

原理

 

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

RegionServer定位

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

 

hbase

 

  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上进行数据写入操做,整个过程以下图:

 

hbase

 

  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刷盘的操做有多种不一样的方式以下图:

 

hbase

 

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

 

  • 1经过全局内存控制,触发memstore刷盘操做。

    在该种状况下,RS中全部region的memstore内存占用都没达到刷盘条件,但总体的内存消耗已经到一个很是危险的范围,若是持续下去,颇有可能形成RS的OOM,这个时候,须要进行memstore的刷盘,从而释放内存。

  memstore总体内存占用上限经过参数hbase.regionserver.global.memstore.upperLimit进行设 置,固然在达到上限后,memstore的刷写也不是一直进行,在内存降低到 hbase.regionserver.global.memstore.lowerLimit配置的值后,即中止memstore的刷盘操做。这样作, 主要是为了防止长时间的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

 

  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现象。

 

参考

  http://blog.csdn.net/yangjinming24/article/details/51918132

  http://blog.csdn.net/woshiwanxin102213/article/details/17584043

  http://blog.csdn.net/FrankieWang008/article/details/41965543

  http://lxw1234.com/archives/2016/09/719.htm

相关文章
相关标签/搜索