HBase存储原理(架构)

HBase读写流程、flush、文件合并、region拆分 - 图1
HBase依赖于ZookeeperHadoop的,所以在启动HBase前需要启动Zookeeper和Hadoop。
HMaster用于管理整个HBase集群,即管理每个HRegionServer,它掌握着整个集群的元数据信息,同时会将相应的数据存储到Zookeeper(元数据信息、高可用信息等)。
HMaster的职责:
1)管理用户对Table的增、删、改、查操作;
2)记录region在哪台 Hregion server上;
3)在Region Split 后,负责新Region的分配;
4)新机器加入时,管理 HRegion Server的负载均衡,调整Region分布;
5)在 HRegion Server 宕机后,负责失效 HRegion Server 上的Regions 迁移。
HRegionServer是每台机器上的一个Java进程(一台机器只有一个HRegionServer进程),用来处理客户端的读写请求(和Hadoop的DataNode类似),并维护着元数据信息。
HRegionServer的职责:
1) HRegion Server 主要负责响应用户I/O请求,向HDFS文件系统中读写数据,是 HBASE中最核心的模块。
2) HRegion Server 管理了很多 table 的分区,也就是region
每个HRegionServer有一个HLog(有且仅有一个)HLog是操作日志,用来做灾难恢复的,当客户端发起一个写请求时,会先往HLog中写再往Memory Store中写。假设没有HLog,我们进行一个写请求,会首先写到Memory Store上,等到Memory Store到达一定容量后,才会flushStoreFile中。但是如果在这之前主机断电了呢?那这部分操作的数据全丢失了。这显然不是我们想到的结果,于是就有了HLog
每个HRegionServer里面有多个HRegion,一个HRegion对应于HBase的一张表(也可能是表的一部分,因为表太大了会切分,表和HRegion的对应关系是一对多),当这张表到一定大小的时候会进行切分,切分成两个HRegion,切分出来的新的HRegion会保存到另一台机器上。每个HRegionServer里面有多个HRegion,可以理解为有多张表。
每个HRegion里面有多个Store(一张表中有多个列族),一个Store对应于HBase一张表的一个列族,。按照这个原理,我们在设计列族的时候,可以把经常查询的列放在同一个列族,这样可以提高效率,因为同一个列族在同一个文件里面(不考虑切分)。
每个Store有一个内存级别的存储Memory Store(有且仅有一个)。当Memory Store达到一定大小或一定时间后会进行数据刷写(flush),写到磁盘中(即HFile)。
每个Store有多个磁盘级别的存储StoreFileMemory Store每刷写一次就形成一个StoreFileHFile是StoreFile在HDFS上的存储格式

HBase读原理

HBase读写流程、flush、文件合并、region拆分 - 图2
在上图中,我们模拟一下客户端读取数据过程,假设Zookeeper存放的meta表在RS1机器上,meta表存放的内容如下,Student表行键范围在1-100的存放在RS4上,在101~200的存放在RS3上,等等。

  1. meta表:
  2. _________________________________________________
  3. | 表名 | rowkey范围 | 所在位置 |
  4. |____________|________________|_________________|
  5. | Student | 1 ~ 100 | RS4 |
  6. |____________|________________|_________________|
  7. | Student | 101 ~ 200 | RS3 |
  8. |____________|________________|_________________|
  9. | Teacher | 1 ~ 500 | RS1 |
  10. |____________|________________|_________________|
  11. | ··· |
  12. |_______________________________________________|

客户端现在要读取Student表的第100行,具体步骤如下:

  1. 客户端向Zookeeper发起请求,请求元数据所在RegionServer,Zookeeper集群存放的是HBase的meta表所在位置。
  2. Zookeeper返回给客户端元数据所在RegionServer(即RS1)
  3. 客户端收到应答后去请求RS1,请求查询Student表的rowkey=100数据所在位置。
  4. 在RS1上查询meta表可知该数据在RS4机器上,即返回给客户端rowkey所在位置(RS4)。
  5. 客户端收到应答后去请求RS4读数据。
  6. RS4查询数据返回给客户端。查询时先去内存(MemStore)查找,因为内存是最新的数据,如果找到了就返回结果,如果没找到则去缓存(blockcache(每个HRegionServer只有一个))找,如果找到了就返回结果,如果还没找到就去磁盘(StoreFile)找,如果在磁盘找到了,则先将结果写入缓存(blockcache),再返回给客户端,写入缓存是为了下次查询提高效率。

    注:blockcache中存储数据来源即Hfile的标记,在读blockcache后,再进行读取未标记的Hfile与MemStore进行数据合并取对应版本数据,返回并保存至blockcache,再返回至client

在整个读过程中HMaster并没有参与,即读流程与HMaster无关,所以如果HMaster挂了,也是可以读数据的。

HBase写原理

HBase读写流程、flush、文件合并、region拆分 - 图3
HBase的写是比读快的,为什么呢,看下面的写过程,同样假设Zookeeper存放的meta表在RS1机器上,meta表存放的内容如下,Student表行键范围在1200的存放在RS3上,等等。
meta表:

meta表:
_________________________________________________
|    表名    |   rowkey范围    |     所在位置     |
|____________|________________|_________________|
|   Student  |    1 ~ 100     |       RS2       |
|____________|________________|_________________|
|   Student  |  101 ~ 200     |       RS1       |
|____________|________________|_________________|
|   Teacher  |    1 ~ 500     |       RS1       |
|____________|________________|_________________|
|                    ···                        |
|_______________________________________________|

客户端现在要插入数据给Student表,其中rowkey=100,具体步骤如下:

  1. 客户端向Zookeeper发起请求,请求元数据所在RegionServer,Zookeeper集群存放的是HBase的meta表所在位置。
  2. Zookeeper返回给客户端元数据所在RegionServer(即RS1)。
  3. 客户端收到应答后去请求RS1,请求查询Student表的rowkey=100数据所在位置。
  4. 在RS1上查询meta表可知该数据在RS2机器上,即返回给客户端rowkey所在位置(RS2)。
  5. 客户端收到应答后去请求RS4写入数据。
  6. RS2收到请求,先将数据写入HLog,再将数据写入MemStore,写入MemStore后就返回给客户端写入成功信息,此时,客户端的写流程完成了。

因为写入内存就结束了写流程,不用访问磁盘,所以总体比读流程是快一点的。
同样,在整个写流程中HMaster也没有参与,所以如果HMaster挂了,也是可以进行写数据的。但是,如果时间长了,表的大小一直变大,而HMaster却挂了,即不会触发Region切分,这样就会导致数据倾斜,系统就变得不安全了。

HBase数据flush刷写过程

hbase-default.xml配置文件中有这么几项配置(见下面)

1.hbase.hregion.memstore.flush.size 默认值:128M MemStore 级别限制,当 Region 中任意一个 MemStore 的大小(压缩后的大小)达到了设定值,会触发 MemStore flush。 2.hbase.regionserver.optionalcacheflushinterval 默认值:3600000 HBase 定期刷新 MemStore,默认周期为1小时,确保 MemStore 不会长时间没有持久化。为避免所有的 MemStore 在同一时间都进行 flush,定期的 flush 操作有 20000 左右的随机延时。 3.hbase.hregion.memstore.block.multiplier 默认值:2 (3.0版本是4) Region 级别限制,当 Region 中所有 MemStore 的大小总和达到了设定值(hbase.hregion.memstore.block.multiplier hbase.hregion.memstore.flush.size,默认 2 128M = 256M),会触发 MemStore flush,禁止当前Region读写 4.hbase.regionserver.global.memstore.upperLimit 默认值:0.4 Region Server 级别限制,当一个 Region Server 中所有 MemStore 的大小总和达到了设定值(hbase.regionserver.global.memstore.upperLimit hbase_heapsize,默认 0.4 RegionServer堆内存大小),会触发全部 MemStore flush,不管MemStore有多小。而且regionserver级别的flush会阻塞客户端读写。 5.hbase.regionserver.global.memstore.lowerLimit 默认值:0.38 与 hbase.regionserver.global.memstore.upperLimit 类似,区别是:当一个 Region Server 中所有 MemStore 的大小总和达到了设定值(hbase.regionserver.global.memstore.lowerLimit hbase_heapsize,默认 0.38 RS堆内存大小),会触发部分 MemStore flush。 Flush 顺序是按照 Region 的总 MemStore 大小,由大到小执行,先操作 MemStore 最大的 Region,再操作剩余中最大的 Region,直至总体 MemStore 的内存使用量低于设定值(hbase.regionserver.global.memstore.lowerLimit * hbase_heapsize)。 6.hbase.regionserver.maxlogs 默认值:32 当一个 Region Server 中 HLog 数量达到设定值,系统会选取最早的一个 HLog 对应的一个或多个 Region 进行 flush。 当增加 MemStore 的大小以及调整其他的 MemStore 的设置项时,也需要去调整 HLog 的配置项。否则,WAL的大小限制可能会首先被触发。因而,将利用不到其他专门为Memstore而设计的优化。 需要关注的 HLog 配置是 HLog 文件大小,由参数 hbase.regionserver.hlog.blocksize 设置(默认512M),HLog 大小达到上限,或生成一个新的 HLog 通过WAL限制来触发Memstore的flush并非最佳方式,这样做可能会会一次flush很多Region,尽管“写数据”是很好的分布于整个集群,进而很有可能会引发flush“大风暴”。 7.手动触发 用户可以通过shell命令一下分别对一个 Table 或者一个 Region 进行 flush: hbase> flush ‘TABLENAME’ hbase> flush ‘REGIONNAME’ 8.其他 执行 Compact 和 Split 之前,会进行一次 flush。

需要注意的是HBase的最小flush单元是HRegion而不是单个MemStore
Flush是由HMaster触发的,Flush顺序是按照Memstore由大到小执行,先Flush Memstore最大的Region,再执行次大的,直至总体Memstore内存使用量低于阈值(hbase.regionserver.global.memstore.lowerLimit)。

Flush 阻止更新的情况

1.出现上述(2)的情况,Region 下所有 Memstore 的总大小超过了 MemStore 默认大小的倍数,该 Region 在 flush 完成前会 block 新的更新请求。
2.出现上述(3)的情况,RegionServer 所有 MemStore 占整个堆的最大比例超过 hbase.regionserver.global.memstore.upperLimit 设置值,该 RegionServer 的更新请求会被 block,一直到 MemStore 恢复阈值一下。
更新被阻塞对单个节点和整个集群的影响都很大,需要关注 MemStore 的大小和 Memstore Flush Queue 的长度。

Memstore Flush 流程

为了减少 flush 过程对读写的影响,HBase 采用了类似于两阶段提交的方式,将整个 flush 过程分为三个阶段:

  1. prepare 阶段:遍历当前 Region 中的所有 MemStore,将 MemStore 中当前数据集 kvset 做一个快照 snapshot,然后再新建一个新的 kvset,后期的所有写入操作都会写入新的 kvset 中。整个 flush 阶段读操作读 MemStore 的部分,会分别遍历新的 kvset 和 snapshot。prepare 阶段需要加一把 updateLock 对写请求阻塞,结束之后会释放该锁。因为此阶段没有任何费时操作,因此持锁时间很短。
  2. flush 阶段:遍历所有 MemStore,将 prepare 阶段生成的 snapshot 持久化为临时文件,临时文件会统一放到目录.tmp下。这个过程因为涉及到磁盘IO操作,因此相对比较耗时。
  3. commit 阶段:遍历所有的 MemStore,将 flush 阶段生成的临时文件移到指定的 Column family 目录下,生成对应的 Storefile(HFile) 和 Reader,把 Storefile 添加到 HStore 的 Storefiles 列表中,最后再清空 prepare 阶段生成的 snapshot。

    HBase数据compaction合并过程

    由于在flush过程中,可能会产生很多小文件(这很好理解,比如有两个MemStore,一个很大,一个很小,然后就触发了flush操作,那么那个小的就形成了小文件),我们都知道,HDFS不适合存储小文件,所以在写入HDFS之前会进行合并操作。
    hbase-default.xml配置文件中有这么几项配置:

    hbase.hregion.majorcompaction:一个region进行 major compaction合并的周期,在这个点的时候, 这个region下的所有hfile会进行合并,默认是7天,major compaction非常耗资源,建议生产关闭(设置为0),在应用空闲时间手动触发。 hbase.hstore.compactionThreshold:一个store里面允许存的hfile的个数,超过这个个数会被写到新的一个hfile里面 也即是每个region的每个列族对应的memstore在fulsh为hfile的时候,默认情况下当超过3个hfile的时候就会对这些文件进行合并重写为一个新文件,设置个数越大可以减少触发合并的时间,但是每次合并的时间就会越长。

在我们利用shell命令或者API删除数据的时候,数据并没有被删除,而是被打上标记,而是在这里的compaction合并过程中才会被完全删除。
转自:https://blog.csdn.net/hzj1998/article/details/99116931

region拆分

Region自动切分是HBase能够拥有良好扩张性的最重要因素之一,也必然是所有分布式系统追求无限扩展性的一副良药。
Region切分触发策略 在最新稳定版(1.2.6)中,HBase已经有多达6种切分触发策略。当然,每种触发策略都有各自的适用场景,用户可以根据业务在表级别选择不同的切分触发策略。常见的切分策略如下图:
HBase读写流程、flush、文件合并、region拆分 - 图4

ConstantSizeRegionSplitPolicy0.94版本前默认切分策略。这是最容易理解但也最容易产生误解的切分策略,从字面意思来看,当region大小大于某个阈值(hbase.hregion.max.filesize)之后就会触发切分,实际上并不是这样,真正实现中这个阈值是对于某个store来说的,即一个region中最大store的大小大于设置阈值之后才会触发切分。store大小为压缩后的文件大小(采用压缩的场景)。

弊 端: 切分策略对于大表和小表没有明显的区分。阈值(hbase.hregion.max.filesize)设置较大对大表比较友好,但是小表就有可能不会触发分裂,极端情况下可能就1个,这对业务来说并不是什么好事。如果设置较小则对小表友好,但一个大表就会在整个集群产生大量的region,这对于集群的管理、资源使用、failover来说都不是一件好事。

IncreasingToUpperBoundRegionSplitPolicy: 0.94版本~2.0版本默认切分策略。这种切分策略微微有些复杂,总体来看和ConstantSizeRegionSplitPolicy思路相同,一个region中最大store大小大于设置阈值就会触发切分。但是这个阈值并不像ConstantSizeRegionSplitPolicy是一个固定的值,而是会在一定条件下不断调整,调整规则和region所属表在当前regionserver上的region个数有关系 :(#regions) * (#regions) * (#regions) * flush size * 2,当然阈值并不会无限增大, 最大值为用户设置的MaxRegionFileSize

优点: 这种切分策略很好的弥补了ConstantSizeRegionSplitPolicy的短板,能够自适应大表和小表。而且在大集群条件下对于很多大表来说表现很优秀,但并不完美。
弊 端: 这种策略下很多小表会在大集群中产生大量小region,分散在整个集群中。而且在发生region迁移时也可能会触发region分裂。

SteppingSplitPolicy : 2.0版本默认切分策略。这种切分策略的切分阈值又发生了变化,相比IncreasingToUpperBoundRegionSplitPolicy简单了一些,依然和待分裂region所属表在当前regionserver上的region个 数有关系,如果region个数等于1,切分阈值为flush size * 2,否则为MaxRegionFileSize。这种切分策略对于大集群中的大表、小表会比IncreasingToUpperBoundRegionSplitPolicy更加友好,小表不会再产生大量的小region,而是适可而止。 另外, 还有一些其他分裂策略, 比如使用DisableSplitPolicy: 可以禁止region 发生分裂; 而KeyPrefixRegionSplitPolicy ,DelimitedKeyPrefixRegionSplitPolicy 对 于 切 分 策 略 依 然 依 据 默 认 切 分 策 略 , 但 对 于 切 分 点 有 自 己 的 看 法 , 比 如KeyPrefixRegionSplitPolicy要求必须让相同的PrefixKey待在一个region中。 在用法上,一般情况下使用默认切分策略即可,也可以在cf级别设置region切分策略,命令为: create’table’{NAME=>‘cf’,SPLIT_POLICY=>‘org.apache.hadoop.hbase.regionserver. ConstantSizeRegionSpli

region切分策略会触发region切分,切分开始之后的第一件事就是寻找切分点-splitpoint。所有默认切分策略,无论是ConstantSizeRegionSplitPolicy、IncreasingToUpperBoundRegionSplitPolicy抑或是SteppingSplitPolicy,对于切分点的定义都是一致的。当然,用户手动执行切分时是可以指定切分点进行切分的,这里并不讨论这种情况。
那切分点是如何定位的呢?整个region中最大store中的最大文件中最中心的一个block的首个rowkey。这是一句比较消耗脑力的语句,需要细细品味。另外,HBase还规定,如果定位到的rowkey是整个文件的首个rowkey或者最后一个rowkey的话,就认为没有切分点。
什么情况下会出现没有切分点的场景呢?最常见的就是一个文件只有一个block,执行split的时候就会发现无法切分。很多新同学 在测试split的时候往往都是新建一张新表,然后往新表中插入几条数据并执行一下flush,再执行split,奇迹般地发现数据表并没有真正执行切分。原因就在这里,这个时候仔细的话你翻看debug日志是可以看到这样的日志滴:
HBase读写流程、flush、文件合并、region拆分 - 图5

Region 核 心 切 分 流 程

HBase将整个切分过程包装成了一个事务,意图能够保证切分事务的原子性。整个分裂事务过程分为三个阶段:prepare – execute– (rollback) ,操作模版如下:
HBase读写流程、flush、文件合并、region拆分 - 图6
prepare阶段: 在内存中初始化两个子region,具体是生成两个HRegionInfo对象,包含tableName、regionName、startkey、endkey等。同时会生成一个transaction journal,这个对象用来记录切分的进展,具体见rollback阶段。
execute阶段:切分的核心操作。见下图(来自Hortonworks):
HBase读写流程、flush、文件合并、region拆分 - 图7

  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中对应文件。这个步骤是所有步骤中最核心的一个环节,生成reference文件日志如下所示: 2017-08-12 11:53:38,158 DEBUG [StoreOpene-0155388346c3c919d3f05d7188e885e0-1] regionserver.StoreFileInfo: reference’hdfs://hdfscluster/hbase-rsgroup/data/default/music/0155388346c3c919d3f05d7188e885e0/cf/d24415c4fb44427b8f698143e5c4d9dc00 其中reference文件名为d24415c4fb44427b8f698143e5c4d9dc.00bb6239169411e4d0ecb6ddfdbacf66,格式看起来比较特殊,那这种文件名具体什么含义呢?那来看看该reference文件指向的父region文件,根据日志可以看到,切分的父region是00bb6239169411e4d0ecb6ddfdbacf66,对应的切分文件是d24415c4fb44427b8f698143e5c4d9dc,可见reference文件名是个信息量很大的命名方式,如下所示:
HBase读写流程、flush、文件合并、region拆分 - 图8
除此之外,还需要关注reference文件的文件内容,reference文件是一个引用文件(并非linux链接文件),文件内容很显然不是用户数据。文件内容其实非常简单, 主要有两部分构成: 其一是切分点splitkey, 其二是一个boolean类型的变量( true 或者false),true表示该reference文件引用的是父文件的上半部分(top),而false表示引用的是下半部分 (bottom)。为什么存储的是这两部分内容?且听下文分解。
看官可以使用hadoop命令亲自来查看reference文件的具体内容: hadoopdfs-cat/hbase-rsgroup/data/default/music/0155388346c3c919d3f05d7188e885e0/cf/d24415c4fb44427b8f698 6. 父region分裂为两个子region后,将daughter A、daughter B拷贝到HBase根目录下,形成两个新的region。

  1. parent region通知修改 hbase.meta 表后下线,不再提供服务。下线后parent region在meta表中的信息并不会马上删除, 而是标注split列、offline列为true,并记录两个子region。为什么不立马删除?且听下文分解。
    HBase读写流程、flush、文件合并、region拆分 - 图9
  2. 开启daughter A、daughter B两个子region。通知修改 hbase.meta 表,正式对外提供服务。
    HBase读写流程、flush、文件合并、region拆分 - 图10
    rollback阶段: 如果execute阶段出现异常,则执行rollback操作。为了实现回滚,整个切分过程被分为很多子阶段,回滚程序会根据当前进展到哪个子阶段清理对应的垃圾数据。代码中使用 JournalEntryType 来表征各个子阶段,具体见下图:
    HBase读写流程、flush、文件合并、region拆分 - 图11

    Region切分事务性保证

    整个region切分是一个比较复杂的过程,涉及到父region中HFile文件的切分、两个子region的生成、系统meta元数据的更改等很多子步骤,因此必须保证整个切分过程的事务性,即要么切分完全成功,要么切分完全未开始,在任何情况下也不能出现切分只完成一半的情况。
    为了实现事务性,hbase设计了使用状态机(见SplitTransaction类)的方式保存切分过程中的每个子步骤状态,这样一旦出现异常,系统可以根据当前所处的状态决定是否回滚,以及如何回滚。遗憾的是,目前实现中这些中间状态都只存储在内存中,因此一旦在切分过程中出现regionserver宕机的情况,有可能会出现切分处于中间状态的情况,也就是RIT状态。这种情况下需要使用hbck工具进行具体查看并分析解决方案。在2.0版本之后,HBase实现了新的分布式事务框架Procedure V2(HBASE-12439),新框架将会使用HLog存储这种单机事务(DDL操作、Split操作、Move操作等)的中间状态,因此可以保证即使在事务执行过程中参与者发生了宕机,依然可以使用HLog作为协调者对事务进行回滚操作或者重试提交,大大减少甚至杜绝RIT现象。这也是是2.0在可用性方面最值得期待的一个亮点!!!
    Region切分对其他模块的影响
    通过region切分流程的了解,我们知道整个region切分过程并没有涉及数据的移动,所以切分成本本身并不是很高,可以很快完成。切分后子region的文件实际没有任何用户数据,文件中存储的仅是一些元数据信息-切分点rowkey等,那通过引用文件如何查找数据呢?子region的数据实际在什么时候完成真正迁移?数据迁移完成之后父region什么时候会被删掉?

  3. 通过reference文件如何查找数据?

这里就会看到reference文件名、文件内容的实际意义啦。整个流程如下图所示:
HBase读写流程、flush、文件合并、region拆分 - 图12
(1)根据reference文件名(region名+真实文件名)定位到真实数据所在文件路径。
(2)定位到真实数据文件就可以在整个文件中扫描待查KV了么?非也。因为reference文件通常都只引用了数据文件的一半数据, 以切分点为界,要么上半部分文件数据,要么下半部分数据。那到底哪部分数据?切分点又是哪个点?还记得上文又提到reference 文件的文件内容吧,没错,就记录在文件中。

  1. 父region的数据什么时候会迁移到子region目录?

答案是子region发生major_compaction时,我们知道compaction的执行实际上是将store中所有小文件一个KV一个KV从小到大读出来之后再顺序写入一个大文件,完成之后再将小文件删掉,因此compaction本身就需要读取并写入大量数据。子region执行major_compaction后会将父目录中属于该子region的所有数据读出来并写入子region目录数据文件中。可见将数据迁移放到compaction这个阶段来做,是一件顺便的事。

  1. 父region什么时候会被删除?

实际上HMaster会启动一个线程定期遍历检查所有处于splitting状态的父region,确定检查父region是否可以被清理。检测线程首先会在meta表中揪出所有split列为true的region,并加载出其分裂后生成的两个子region(meta表中splitA列和splitB列),只需要检查此两个子region是否还存在引用文件,如果都不存在引用文件就可以认为该父region对应的文件可以被删除。现在再来看看上文中父目录在meta表中的信息,就大概可以理解为什么会存储这些信息了:
HBase读写流程、flush、文件合并、region拆分 - 图13
4. split模块在生产线的一些坑?
有些时候会有同学反馈说集群中部分region处于长时间RIT,region状态为spliting。通常情况下都会建议使用hbck看下什么报错, 然后再根据hbck提供的一些工具进行修复,hbck提供了部分命令对处于split状态的rit region进行修复,主要的命令如下: -fixSplitParents Try to force offline split parents to be online.-removeParents Try to offline and sideline lingering parents and keep daughter regions.-fixReferenceFiles Try to offline lingering reference store files
其中最常见的问题是 : ERROR:Foundlingeringreferencefilehdfs://mycluster/hbase/news_user_actions/3b3ae24c65fc5094bc2acfebaa7a56de/ 简单解释一下,这个错误是说reference文件所引用的父region文件不存在了,如果查看日志的话有可能看到如下异常:java.io.IOException: java.io.IOException: java.io.FileNotFoundException: File does not exist:/hbase/news_user_actions/b7
父region文件为什么会莫名其妙不存在?经过和朋友的讨论,确认有可能是因为官方bug导致,详见HBASE-13331。这个jira是说HMaster在确认父目录是否可以被删除时,如果检查引用文件(检查是否存在、检查是否可以正常打开)抛出IOException异常, 函数就会返回没有引用文件,导致父region被删掉。正常情况下应该保险起见返回存在引用文件,保留region,并打印日志手工介入查看。如果大家也遇到类似的问题,可以看看这个问题,也可以将修复patch打到线上版本或者升级版本。