HBase region的split过程浅析

时间:2024-03-26 12:17:06

作为一个hbase的使用者来说并不会感知到region的存在。不过作为一个使用者,对于region这个词也不应该感到陌生,因为hbase在部署的时候有很多的region server。region是hbase实现上的一个存储单元。其实,我们所有的hbase数据都被存放在不同的region里。

从10000米的高空,我们可以这样理解region和hbase的架构:
1、Table在行的方向上分割为多个Region;
2、Region按大小分割的,每个表开始只有一个region,随着数据增多,region不断增大,当增大到一个阀值的时候,region就会等分会两个新的region,之后会有越来越多的region;
3、Region是Hbase中分布式存储和负载均衡的最小单元,不同Region分布到不同RegionServer上。

HBase region的split过程浅析

深入到region的内部,我们可以看到这样的region的解剖图。

HBase region的split过程浅析

region内部分成多个store。每个store负责管理和存储一个column family的数据。也就是说,一张表有多少个column family,一个region内部会生成多少个store与之对应。一个store里会有一个memstore和n个storefile。其中memstore是在regionserver的内存中。而storefile会保存持久化的保存在HDFS上。

region的设计让我想起了level db的设计。事实上region在实现过程中也在很大程度上参考了bit table的tablet的设计,也就是level db的设计。

memstore带来了两个好处:

1 所有的写入操作只写入wal日志文件和memstore中。wal日志文件是追加写, 而且磁盘的顺序IO速度很快, 因此写操作是极快完成的。

2 数据有了一个高速缓存。部分读请求可以直接命中缓存而省去了读磁盘之苦。

wal日志文件,hbase中的叫法是HLog,也是放在HDFS中。因为HDFS这一层的存在,使得一个数据库服务的high available的问题变得简单了。如果一个region server发生了故障,那么它所管理的memstore肯定就丢失了。但由于wal日志文件的存在,接管的region server可以轻易的将原来的region接管过来。它只需要找到wal日志文件中还没有落地成HFile的部分,重放请求并写入memstore就可以了。

如果数据不断的写入同一个region,会发生什么事?

首先随着数据的写入, memstore肯定是不够用了,需要flush到磁盘上形成Hfile。

随着HFile越来越多,可能触发compact过程。将多个HFile合并成一个HFile。

然后数据越来越多,HFile也越来越大。虽然HFile当中有索引,但索引的查询也是需要时间的。

另外大量的读写请求会汇聚到同一台region server上,服务器也会越来越繁忙。当达到它性能的极限后。。。天知道会发生些什么。

因此,当一个region过大之后,是需要进行split的。split的目的,是将一个region尽可能均匀的分成两个region。因为所有的HFile数据文件都是不可修改的, 所以新创建的子region不会重写数据到新文件中。在子region中,首先会使用的是父region的数据文件的reference。当子region逐渐通过compact,不再使用父region的数据文件的reference,就可以清理掉这些reference和父region的文件。

split的过程,由单个region的region server所发起。整个过程牵涉很多服务,zookeeper/master都会涉及到。region server需要在split开始前和结束后通知master, 更新meta表使client可以发现新的子region, 此外还需更新hdfs中的目录和文件。为了在错误发生时回滚, region server在内存中保存着split的执行journal。

HBase region的split过程浅析

 

当split发生时有这些步骤:

1 region server发起split。它会在zookeeper上创建一个节点,名称叫做/hbase/region-in-transition/region-name。状态为splitting。

2 因为master会监听/hbase/region-in-transition目录,所以它会得知split的发生。

3 region server在父region的hdfs目录下,创建一个叫做.splits的子目录。

4 region server在服务内部将父region关闭,并flush memstore。region在本地数据结构中的状态标记为offline也就是离线。此时,所有客户端访问父region的请求都会失败,并抛出NotServingRegionException异常。客户端会在等待一定时间后重试这个请求。

5 region server在.splits目录下创建子region的目录以及必要的数据结构。对于一个store file会创建两个reference file,并将reference file指向父region的文件。

6 region server创建真正的region目录,并移动这些reference文件。

7 region server 发送一个PUT请求到meta表,请求将父region在META中的状态设置成offline,并增加子region的信息。注意,此时并没有在meta表中增加子region的行,而只是修改父region的信息。在这时,客户端请求读、写父region的数据,都依然是返回出错。如果此时region server挂掉,那么接手它工作的region server依然能将split的工作做完。

8 region server同时打开两个子region接受请求。

9 region server将两个子region的加入到meta表中。此时,客户端会发现新的region,并可以向新的region发送请求。如果客户端本地有缓存meta表信息,缓存会失效。

10 region server将zookeeper上的/hbase/region-in-transition/region-name的状态改成split。master可以感知到split的完成。在迟些时候,balancer可以将子region分配到其他的region server。

11 split已经完成。但还有些垃圾需要清理。meta和hdfs中还保存着父region的reference。子region在compact之后会删除父region的reference。而master的垃圾回收任务,会定期检查region是否还在使用reference, 如果不再使用就会全部删除父region的数据文件。

在使用HBase的过程中,我曾经遇到过一个问题。在压力测试写一张hbase的表的过程中,出现了一张表一直不能结束split过程,而一直处在splitting的状态。而客户端也一直抛出NotServerRegionException异常。

查询了一下对应的region server的日志发现,一直打印这样的错误,非常频繁,一秒钟之内有几百上千次。

hbase-gdata-regionserver-tdmtest2.log

2017-02-18 15:16:53,542 WARN  [B.DefaultRpcServer.handler=29,queue=2,port=60020] regionserver.HRegion: Failed getting lock in batch put, row=3eb3b773-5fac-4638-86d1-930d68604ae3
org.apache.hadoop.hbase.regionserver.WrongRegionException: Requested row out of range for row lock on HRegion login_event_20170217,8024051d-360b-4877-a9b9-ebef10810e65,1487322174053.c9be23e25c28188f0af105e9a79593ec., startKey='8024051d-360b-4877-a9b9-ebef10810e65', getEndKey()='', row='3eb3b773-5fac-4638-86d1-930d68604ae3'
    at org.apache.hadoop.hbase.regionserver.HRegion.checkRow(HRegion.java:3636)
        at org.apache.hadoop.hbase.regionserver.HRegion.getRowLockInternal(HRegion.java:3667)
        at org.apache.hadoop.hbase.regionserver.HRegion.doMiniBatchMutation(HRegion.java:2430)
        at org.apache.hadoop.hbase.regionserver.HRegion.batchMutate(HRegion.java:2297)
        at org.apache.hadoop.hbase.regionserver.HRegion.batchMutate(HRegion.java:2252)
        at org.apache.hadoop.hbase.regionserver.HRegion.batchMutate(HRegion.java:2256)
        at org.apache.hadoop.hbase.regionserver.HRegionServer.doBatchOp(HRegionServer.java:4538)
        at org.apache.hadoop.hbase.regionserver.HRegionServer.doNonAtomicRegionMutation(HRegionServer.java:3718)
        at org.apache.hadoop.hbase.regionserver.HRegionServer.multi(HRegionServer.java:3607)
        at org.apache.hadoop.hbase.protobuf.generated.ClientProtos$ClientService$2.callBlockingMethod(ClientProtos.java:30954)
        at org.apache.hadoop.hbase.ipc.RpcServer.call(RpcServer.java:2093)
        at org.apache.hadoop.hbase.ipc.CallRunner.run(CallRunner.java:101)
        at org.apache.hadoop.hbase.ipc.RpcExecutor.consumerLoop(RpcExecutor.java:130)
        at org.apache.hadoop.hbase.ipc.RpcExecutor$1.run(RpcExecutor.java:107)
        at java.lang.Thread.run(Thread.java:745)

通过仔细分析上面的步骤可以发现,这个region server的错误发生在第四步之后和第八步之前。因为客户端还没找到新的region,而且在对应的meta表里也没有发现新的region。因此我采取了重启对应region server的方式进行处理。因为此时如果错误发生在第7步之前,接手的region server不知道split的发生,依然可以正常处理请求。而错误发生在第7步之后,region server会完成split的过程。

重启region server的过程需要注意,千万不能使用 stop-hbase.sh进行关闭,因为这样会关闭整个集群。

而需要在对应的region server上用 bin/graceful_stop.sh regionservername的方式进行关闭。使用bin/hbase-daemon.sh start regionserver。启动之后,故障排除,region split完成。集群恢复了服务。

 

 

转载于:https://my.oschina.net/costaxu/blog/841493