分享

HBASE原理简述

本帖最后由 pig2 于 2014-3-30 13:48 编辑
前提是大家至少了解HBase的基本需求和组件。

从大家最熟悉的客户端发起请求开始讲起吧,这样大家能够深有体会的逐步了解原理。比如我们发起了一条PUT请求,客户端首先需要查找到需要响应请求的REGIONSERVER。 记录region->regionserver映射是由HBASE系统表.META.记录的。所以我们只要知道. META.表的位置就能知道每个region响应的key的范围 和region所在机器。但是.META.表又保存在哪些机器上呢?这又是由-ROOT-表记录的 master在分配完-ROOT-表后 会将-ROOT-表的位置放到ZOOKEEPER中。所以我们在配置客户端的时候配置的是ZOOKEEPER的位置,而不是MASTER位置。

为什么要分为-ROOT-和.META.呢?这是因为region信息本身很多 一个集群中可能会出现成千上万的region 因此.META.表本身也无法在一个region中保存所有用户region的信息,所以本身也会分裂。而.META.表的region数就比较有限了所以-ROOT-是不会分裂的.

综上,客户端首次请求时,先拿-ROOT-然后通过请求范围找对应的.META.,在.META.中找打具体的region server 然后发送请求。-ROOT-和.META.是可以缓存的。

现在,我们解决了 客户端应当把PUT发送到哪个rs的问题,接下来就要发送请求了。region server收到请求后会保存PUT数据。这就不得不说HBASE的数据模型了,HBASE使用的列式存储,基本数据结构为LSMT log structure merge tree。简略的思路描述是,将操作记录在树中的节点上然后适时的将节点合并从而使key的删除修改能够最终体现在一个节点上,读取的时候会读取带有key相应操作的节点,返回最终key的值。可以看到lsmt是将随机读写转化为顺序读写的数据结构,读方面更适合扫库那样的顺序读取,不太适合随机读取。

那么一个PUT请求时怎么和LSMT搭上关系的呢?首先region server接到请求时,先将操作(keyvalue 时间戳 操作类型)保存为HLog,然后在保存到memstore中,然后即可返回写入成功的请求。其中memstore保存在内存中,写满后flush为hdfs文件。hlog是为了防止rs故障时,memstore数据必然丢失导致的数据丢失,在客户端可以禁用hblog来加快写入速度,但这是用数据不安全换来的。只要每次memstore刷入hdfs后,会判断hdfs刷入的中最早的操作 然后由另外的线程根据此记录删除旧的HLog文件。

接下来说说memstore写满时的处理。memstore写满(每个region的列族都有单独的memstore对象但实际上共用一块内存池)时,会将其中的操作分发到对应region的每个列族(store)做处理。然后store将这些操作序列保存为存储文件(storefile)。

从大体上粗略的看 region server这边重要的实体结构是这样:regionserver : region = 1 : n;region : store= 1 : n;store : storefile = 1 : n。对于每个列族的数据文件,实机上是一个LSMT的叶子节点,每个文件中保存的是最近的对于列族中key的操作。

当一个列族中文件过多的时候,会触发compact,也就是说的文件合并。HBase的compact分为两种 minor和major:minor是小范围内的合并文件,只合并部分。目的在于把小文件积累成大文件。因为没有全量数据,所以对于一个key的删除操作还是需要保留标记,无法物理删除。majorcompact把列族中的所有文件合并为一个,目的在于使key的修改和删除,最终在物理上生效。因为major compact操作的是此列族的全量数据,所以可以做物理删除。但是也由于是全量数据,执行起来耗费时间也会比价长,所以hbase对major compact做了时间间隔限制。

当store的store file集合中总文件长度太大时(超过配置的阈值),这个region会一分为二,也就是split。由于split是以region为单位的,所以有些列族因为其他列族过大也被连坐般的split。所以从这个流程粗略的看来 put会触发flush,flush会触发compact,compact会触发split。当然这都是在多个线程中执行的,不会明显的阻塞住客户端请求。

store file的大小和memstore大小有关系,一次flush会在一个列族里生成一个store file。所以memstore越大,产生大store file的机会也就越多。put不均匀时,有的列族里会有比较多的长度较小的store file,但是文件多了会触发compact。小文件compact很快,所以不用担心。
store file
------------------------------------------------
|block                         |
|----------------------------------------------|
|block                         |
...
|  meta                      |
|---------------------------------------------|
|block索引,以及一些key范围信息|
|---------------------------------------------|
|布隆过滤                   |
-----------------------------------------------
可以粗略的认为 一个storefile的结构是这样的,尾部的顺序和细节记不太清楚了。一个block包括多个key value,key在文件内是有序的。一条key value记录如下图:
aa.jpg
                              
读数据的时候我会发送一个get请求,在region server内部会转为一个scan。他会到相关列族中去scan storefile。storefile的尾部包含block索引、布隆过滤器、更新时间等所以这可以加快需要scan的文件过滤。所以针对一个store file读是这样的:判断get请求中的row key是否在文件保存的数据范围内;判断get请求中的row key是否能从布龙过滤器中找到(如果过滤器为row-col过滤器还可以判断是否包括需要get的col);判断get请求中的时间范围 是否在文件保存的数据的时间范围中;获取对应的block index;把block加载到block cache中;然后scan block;从多个store file的结果中 get请求中需要包含的version个数,取前几个从而满足get请求中需要包含的version个数。get可以看做特殊的scan操作。

总得blockcache大小是有限的,会有淘汰的.实际上blockcache对于scan来说更合适,因为scan一般是一个范围的扫,block中的row key又是有序的,所以说顺序读会比随机读快。一般hbase比较难适应高并发的随机读,因为blockcache这个设计的本身,就不适合缓存随机的row key:随机读的特点就是读的key均匀散列,这样会使读操作,落在每个block上,导致读的时候每个block先被加载到内存,然后很快因为其他的block持续加载进来而被淘汰出去,然后就这样换来换去,反而更浪费时间。

最后两个比较重要的操作是open和close region。这两个在容灾和均衡中常用。

先说close吧 正常close时会先flush memstore 然后通知master close结束。非正常关闭时,就来不及flush了。master会通过zk和region server之间的心跳这两种途径得知regionsever挂掉的情况。

open 一般由master发起。master先找到包含region操作对应的HLog文件,然后挑选出region对应的操作放到region目录中,然后命令某个region server open之。open时先重演HLog中记录的操作,然后再加载region对应的store和store file。

比较重要的原理就是这样的了。原理清楚了的话,再分析起来代码,就能有一个宏观的了解了。


已有(18)人评论

跳转到指定楼层
break-spark 发表于 2014-10-14 15:35:07
很感谢分享,说的挺清晰的
回复

使用道具 举报

Joker 发表于 2015-1-4 17:28:24
顺带补充一些内容

【HBase LSM树 VS B+ 树】
  本人对这些树都不是太了解,也是从学大数据才开始了解的,所以内容基本与互联网上的内容没有两样~哈哈哈

B+树,如Oracle的普通索引就是采用B+树的方式,来一个B+数的例子

根节点和枝节点很简单,分别记录了每个叶子节点的最小值,并用一个指针指向叶子节点。
叶子节点里每个键值都指向真正的数据库(如Oracle里面的RowID),每个叶子节点都有前指针和后指针,这是为了做范围查询时,叶子节点间可以直接跳转,从而避免在回溯至枝和根节点。

B+树最大的性能问题是会产生大量的随机IO,随着新数据的插入,叶子节点会慢慢分裂
逻辑上连续的叶子节点在物理上往往不连续,甚至分离的很远,但做范围查询时,会产生
大量读随机I/O

对于大量的随机写也一样,举一个插入key跨度很大的例子,如 7->1000->3->2000...新插入的数据存储在磁盘上相隔很远,会产生大量的随机写I/O。

从上面可以看出,低下的磁盘寻道速度严重影响性能

LSM树
为了克服B+树的弱点,HBase引入了LSM树的概念,即Log-Structured Merger-Trees
为了更好的说明LSM树的原理,举个极端的例子

假设现在有1000个节点的随机key,对于磁盘来说,肯定把这1000个节点顺序写入磁盘最快,但是这样一来,读就悲剧了,因为key在磁盘中完全无序,每次读取都要扫描全盘;

那么,为了让读性能尽量提高,数据在磁盘中必须得有序,这就是LSM树的原理,但是写就悲剧了,因为会产生大量的随机I/O,磁盘寻道速度更不上。

LSM树本质上就是在读写之间取得平衡,和B+树相比,
它牺牲了部分读性能,用来大幅提高写性能

它的原理是把一颗大树拆成N颗小树,它首先写入到内存中(内存没有寻道速度的问题,随机写的性能得到大幅提升),在内存中构建一颗有序小树,随着小树越来越大,内存的小树
会flush到磁盘上。当读时,由于不知道数据在哪颗小树上,因此必须遍历所有的小树,
但在每颗小树内部数据是有序的。

好的LSM树的原理就是上面的内容了,在来增加一些附加姿势
1)、首先说说为什么要有WAL(Write Ahead Log),很简单,因为数据是先写到内存中的
如果中途断电,内存中的数据全部丢失,因此为了保护内存中的数据,需要在磁盘上先记录
Log file,当内存中的数据flush到磁盘上,就可以抛弃相应的Log file

2)、什么是memstore,storefile? 很简单,上面说过,LSM树就是一堆小树,在内存中的小树即memstore,每次flush,内存中的memstore变成磁盘上一个新的storefile

3)、为什么会有compact? 很简单,随着小树越来越多,读的性能会越来越差,因此需要在适当的时候,对磁盘中的小树进行merge,多颗小树变成一颗大树
回复

使用道具 举报

Tiny 发表于 2015-2-26 17:29:00
很不错,醍醐灌顶
回复

使用道具 举报

Liaq325 发表于 2015-3-1 19:48:04
解析的很透彻,学习了!
回复

使用道具 举报

tang 发表于 2015-3-9 21:22:38
回复

使用道具 举报

admln 发表于 2015-3-11 14:11:18
概括的很全面,学习了,一时消化不完
回复

使用道具 举报

tang 发表于 2015-6-9 20:32:32
回复

使用道具 举报

12下一页
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关闭

推荐上一条 /2 下一条