Hbase之-Region是什么?如何split?(region数量,region拆分策略,region)

拆分流程)

在Hbase中,Region是分布式Table中的分区存储单元,每一个Region代表特定rowkey区间内的数据片段,每个Region中存储着1到多个存储Store,每个Store对应Table中的一个ColumnFamily,每个Store中包含一个MemStore的写缓存。一个RegionServer下的所有的Region共享一个Hlog目录。

本文主要讲述Region的相关的点

  • 如何设计region的数量
  • HMaster如何分配Region
  • region的状态
  • region在HDFS上的副本分配
  • region split划分
  • region merge

1 如何设计Region的数量

通常Hbase被设计为在每个RegionServer上托管少量(20-200)个大型Region(5-20GB)。

1.1 为什么需要保证少量的Region数量?

为什么需要保证region的数量较少?这里其实有几个方面的原因;通常加入你无法确认从20~200中选择一个合适的region数量,那么官方建议每个RegionServer上存100个Region数量,这样的集群的性能表现是很好的!~

一、MSLAB(MemStore-local Allocation Buffer),这是每个MemStore固定占用的缓存大小,就算Region不存数据,但是Region中的Store的数量是固定的,那么MSLAB的总消耗也是固定的,假如有1000个Region,每个Region中有2个Store(Family),那么这个固定占用的内存就为:1000 * 2 * 2MB = 4000MB,这样轻松占用3.XGB的内存消耗;

二、HMaster实际上对过多的Region是比较敏感的,因为HMaster需要花掉大量的时间分批分配、移动这些Region,这样实际上占用ZK资源,而且HMaster与ZK之间的信息同步做的不是特别好,但是Hbase从Hbase-0.9.6就开始进行优化了;

三、在Hbase-2.0.0之前,Region越多,Store总数越多,StoreFile数量越多,HFile数量越多,INDEX数量越多,由于INDEX都存储在L1的on heap内存中,Region过多会有OOME的风险;

四、最重要的一点,也是限制Region数量的主要因素

2 Region的分配

Region的分配主要分2种情况

  • Start-up启动时分配
  • Failover故障转移时重新分配

2.1 start-up时分配

1、启动时HMaster调用后台线程AssignMentManager;
2、AssignMentManager从ZK获取hbase:meta的位置,并且从中找到Region所属的Server信息;
3、如果region对应的RegionServer元数据依旧生效,那么继续按照元数据进行分配;
4、如何region对应的RegionServer此时失效,然后调用LoadBalancerFactory后台线程重新负载均衡分配这些Region,默认的LoadBalancerFactory的实例对象为StochasticLoadBalancer in hbase-1.0,由该实例将Region分配并移动到其它生效的RegionServer;
5、在重新分配后,RegionServer重新open这些Region,然后Master通过最新的(regionid,table,server start code)来更新hbase:meta表。

2.2 failover时分配

1、当一个RegionServer故障之后,该RegionServer上的所有Region变成不可用的状态;
2、HMaster将会发现该RegionServer不可用了;
3、HMaster此时对这些Region的分配失效了,此时需要重新分配,分配流程就像start-up时一样;
4、假如客户端此时来了请求,刚好请求这些Region上的数据,此时是请求不到的,request不会lost,但是会进行重试操作;
5、最后等Region重新分配之后,请求会指向重新分配后Region所属的RegionServer。

3 Region的状态过渡 state-transition

Hbase的每个Region都包含一个状态信息,并且将状态信息存储在hbase:meta表中

4 Region的副本本地性

因为Hbase的底层存储是基于HDFS的,所以尽量将HRegionServer与DataNode装在同一节点,减少RPC的网络IO消耗;

HBase在HDFS上的副本默认是3个

1、第一个副本,默认是在RegionServer的本地;
2、第二个副本,是被随机分配到其它rack(机架)上的任一节点;
3、第三个副本,是被随机分配到相同rack(机架)上的其它节点;

Hbase的Region在Flush和Compaction之后,实现了本地化,但是在RegionServer故障转移的过程中,由于所有的副本都不在本地节点上,所以需要重新分配Region,分配后的Region在Flush与Compaction之后,其对应的StoreFiles也实现了本地化。

5 Region split

5.1 pre-splitting(一般在设计hbase schema的时候使用)

具体的pre-splitting操作可以参考:pre-splitting

Hbase的Region Split是自动的,这里无法为置顶的数据荷载指定一个确定的Region数量,通常需要很好开始调试,从RegionServer数量的3~4倍pre-split开始调试,然后观察接下来自动region-split的频率。

通常pre-split的预分区需要面临的一个问题是就是找到region split的那个split point,也就是切分点,在Java API中,可以使用RegionSplitter实例来创建split-points,具体方法是通过一套可拔插的SplitAlgorithm,Hbase内部提供2中已经定义好的切分算法, HexStringSplit and UniformSplit

  • HexStringSplit可以在rowkey有一个16进制的前缀的时候使用(例如使用hashes作为rowkey的前缀)
  • UniformSplit可以将rowkey都当作随机的字节数组进行rowkey space的划分,这里的rowkey space就可以理解成region

同时Hbase支持用户自定义SplitAlgorithm,同时将它用于RegionSplitter实例,进行region的划分。

# 按照HexStringSplit算法,创建一个表 test_table,-c 10代表请求的分区数量为10,-f f1 代表创建一个columnfamily f1,如果创建多个,使用:进行分割
$ hbase org.apache.hadoop.hbase.util.RegionSplitter test_table HexStringSplit -c 10 -f f1

执行以上命令的日志片段如下:

13/01/18 18:49:32 DEBUG hbase.HRegionInfo: Current INFO from scan results = {NAME => 'test_table,,1358563771069.acc1ad1b7962564fc3a43e5907e8db33.', STARTKEY => '', ENDKEY => '19999999', ENCODED => acc1ad1b7962564fc3a43e5907e8db33,}
13/01/18 18:49:32 DEBUG hbase.HRegionInfo: Current INFO from scan results = {NAME => 'test_table,19999999,1358563771096.37ec12df6bd0078f5573565af415c91b.', STARTKEY => '19999999', ENDKEY => '33333332', ENCODED => 37ec12df6bd0078f5573565af415c91b,}
.....

如果你需要自动创建split point,Hbase也是支持的,通过Hbase shell

#直接指定split points
hbase(main):015:0> create 'test_table', 'f1', SPLITS=> ['a', 'b', 'c']

or
# 通过文件制定split points
$ echo -e  "anbnc" >/tmp/splits
hbase(main):015:0> create 'test_table', 'f1', SPLITSFILE=>'/tmp/splits'

为了达到最优的分布式负载,我们需要考虑我们的 data model,以及key的分布,从而决定使用哪种划分算法以及使用什么split points,以一个固定的预分区开始,然后持续往Hbase的表中注入数据,任由Region的自动划分,查看table的Region的数量变化情况。

5.2 auto-splitting(按照默认的split策略进行region的自动划分)

假如不考虑pre-splitting,在Hbase中创建一个table,此时该table的region数量为1,但是无论是否采用pre-split,一旦该Region的数据量达到一定的限制,那么此时该Region就会自动划分成2个新的Region,然后通过RegionServer开启新的Region,然后删除旧的Region,同时更新hbase:meta的信息。

Hbase支持的拆分策略有3种Region split的policy策略

  • ConstantSizeRegionSplitPolicy
  • ConstantSizeRegionSplitPolicy作为Hbase-0.94.0之前默认的的Region自动划分策略;
  • 当一个Region中的Store(对应ColumnFamily)对应的store files总数据大小达到hbase.hregion.max.filesize(default = 10GB)时,该Region就会进行Region的划分;
  • 如果你已经完成了pre-split,而且你对每个RegionServer的Region数量感兴趣,那么这个划分策略是最理想的选择。
  • IncreasingToUpperBoundRegionSplitPolicy
  • 从Hbase-0.94.0开始,默认的划分策略从上者ConstantSizeRegionSplitPolicy变成了IncreasingToUpperBoundRegionSplitPolicy;
  • 该策略通过一个RegionServer中托管的Region的数量进行更主动的划分,进行划分的临界阈值是min(R^2* hbase.hregion.memstore.flush.size,hbase.hregion.max.filesize),其中R代表一个Table被一个RegionServer托管的Region的数量,"hbase.hregion.memstore.flush.size"为配置的flush的阈值大小默认128M,hbase.hregion.max.filesize为每个Region下默认的最大数据大小10GB;
  • 假如一个Table在 同一个RegionServer下托管的Region数最开始为1,假如采用该策略,那么第一次拆分的Region大小为128M,第二次为22*128M=512,第三次为32 *128M…到第十次切分的临界阈值为min(10^2*128M,10G) = 10G ,往后所有切分的阈值都会是10个G。

对于以上2种算法,不管何时进行拆分,每次拆分的split point都是该Region中最大Store中的最大StoreFile的Block-Index的mid point对应的rowkey,虽然比较绕,但是还是请记住了,弟弟。

  • KeyPrefixRegionSplitPolicy
  • KeyPrefixRegionSplitPolicy是对Hbase库的特殊补充;
  • 我们可以在设计rowkey的时候对相同业务意义的rowkey加上相同的prefix,从而对数据进行分组;
  • 而且该拆分策略能够保证不会在具有相同prefix的rowkey的mid-point进行拆分,这样就保证不管怎么拆分,相同分组的数据始终被分配在同一个Region中,这种分组可称为row gourp或者entity group;
  • 该划分策略使用场景:如果需要在应用程序中设计本地事物(替代链接),可以考虑该策略

那么如何在Hbase程序中指定划分策略?

//1、我们可以在配置文件中指定该选项option
hbase.regionserver.region.split.policy

//2、我们也可以在TableDescriptor的实例中指定拆分策略,可以在创建table或者修改table的时候进行设置
HTableDescriptor tableDesc = new HTableDescriptor("example-table");
tableDesc.setValue(HTableDescriptor.SPLIT_POLICY, KeyPrefixRegionSplitPolicy.class.getName());
//add columns etc
admin.createTable(tableDesc);

如果你需要做预分区pre-splitting,并且不想让Region进行自定划分,而是手动进行region-split,那么你可以将hbase.hregion.max.filesize从默认的10GB设置到一个100GB,但是前提必须是100G属于不超过整个RegionServer容量的安全值,否则Region的容量会超过整个RegionServer的容器资源大小;

如果你决定禁用region的自动划分并且使用初始化时固定的pre-split的region数量,例如:你使用相同的hashes作为rowkey的前缀,从而对rowkey进行分组,这样就能保证每个Region的read/write负载以及数据大小在table的每个Region中都是一致的,因为均匀分布。

5.3 force split

Hbase除了支持pre-split,auto-split,同时Hbase还支持在客户端对online的Table进行强制split,hbase shell可以对一个Table中的所有的Region、或者单个Region通过提供split-point的方式进行强制拆分;

# 
hbase(main):024:0> split 'b07d0034cbe72cb040ae9cf66300a10c', 'b'
0 row(s) in 0.1620 seconds

仔细观察你的Hbase 分布式UI界面

如果你发现某些Region负载正在变得不平整(size与其它相差太大),你可能就应该考虑是否需要手动拆分这些Region从而实现平整的负载,从而提升整个集群的吞吐量,因为hbase可以轻易实现150W/s的吞吐量;

需要手动强制split的另一个原因是,当你pre-split进行初始化切分之后,随着时间的推移,发现Region数据分布还是不够均匀,那么此时你也可以进行手动split操作。

5.4 region是怎样被拆分的?

前面的内容描述了region的split方式与split策略,但是不管使用哪种拆分方式与策略,整个split流程其实是一样的,当client向Hbase发起写入请求时,经过元数据找到regionserver,然后找到指定的region,然后将数据写入到Region的MemStore,当MemStore的数据达到一定的阈值,然后会将内存中的数据flush到StoreFile,当storeFiles的达到一定数量,会将每个Store下的StoreFiles合并成一个更大的文件,每次Flush\compaction之后,RegionServer都会判断是否需要将Region进行split操作;

一旦需要进行RegionSplit,RegionServer就会将该Split Request添加到请求队列,由于Hbase底层数据是不可变(HDFS的文件数据也是不可变的),当Region进入到拆分阶段,新创建的子Region不会将父Region所有数据重新写为新的文件,而是创建2个引用文件reference file分别通过Index指向父Region的存储文件的[start_key,split-point]与[split-point,end_key]范围,reference file可以当作正常的StoreFile进行使用,只是这2个reference file分别代表原本父Region的每个StoreFile的一半,当父Region完成split操作,那么它就变成offline,而子Regions就会从splitting_new状态被RegionServer打开,最终变成open状态,但是这个过程可以回滚;同时因为带有reference file的Region是无法进行Split操作的,所以最终2个子Region的reference file都会被删除;

即使splitting Region是RegionServer的本地决策,但是整个Split过程会有很多角色的协调参与,RegionServer在进行Split操作的前后都会通知HMaster,并且RegionServer会修改hbase:meta(.META.)表,保证Client能知道split之后的新生成的子Region,同时重新调节HDFS上的目录结构,Split过程是多个进程协调处理的过程,为了保证异常操作可以回滚,RegionServer在内存中记录一个执行操作的备忘录,具体的Split操作流程如下图(Master、RegionServer的操作标记为红色,Client的操作标记成绿色)。

hbase region数量分布 hbase中的region_hbase region数量分布

步骤解析如下:

0、在某一次flush and compaction操作之后,RegionServer本地决定将某个Region进行split操作;

1、首先RegionServer会在ZK的hbase-znode下创建一个子znode:/hbase/region-in-transition/parent-region-name:SPLITTING表示该parentRegion此时将进入SPLITTING状态;

2、同时HMaster会从Zookeeper中通过监听/hbase节点,获取到parentRegion的状态;

3、该RegionServer会在HDFS上该parent-region的目录下创建一个子目录:/habse/data/namespace/table/parent-region/.split;

4、准备好之后,RegionServer将该parentRegion关闭,往后的客户端针对该parentRegion的所有请求都回抛出throw NotServingRegionException;

5、RegionServer会在HDFS的创建的····/.split下创建对应的SubRegion的目录:

#为parentRegion的每个StoreFile创建一个reference_file,这是类似于符号链接的文件,通过索引数据链接到storeFile,可以当作正常的storeFile使用 /habse/data/namespace/table/parent-region/.split/daughterA/column_family/reference_files /habse/data/namespace/table/parent-region/.split/daughterB/column_family/reference_files

6、RegionServer在HDFS上创建真正的SubRegion目录,并将创建好的reference_files移动到真实目录下;

/habse/data/namespace/table/daughterA/column_family/reference_files /habse/data/namespace/table/daughterB/column_family/reference_files

7、在创建真实subRegion目录之后,RegionServer会将hbase:meta(.META)parentRegion的元数据row中的状态进行修改,好让Client可以发现parentRegion此时的状态是offline状态,同时添加SubRegion的信息;

8、然后将daugherA、daughterB打开,状态从SPLITTING_NEW=>OPEN;

9、RegionServer再次将online的2个SubRegion信息写入到元数据表(hbase:meta),Client将会发现这些Region的状态;

10、最终RegionServer会在ZK的hbase-znode下创建一个znode/hbase/region-in-transition/parent-region-name:SPLIT,表示parentRegion拆分完成;

11、此时METADATA和SubRegion都还对parentRegion留有引用,在下次SubRegion需要进行Compaction操作的时候,会清除掉这些reference_files,同时HMaster后台的GC task线程会定期查看parentRegion是否还被SubRegions引用,假如不被引用,那么就会在hbase:meta中干掉该parentRegion的信息,从此parentRegion就不存在了。

6 region merge

Hbase的merge先处于一个完善的阶段,目前只支持简单的手动merge,

7 Region使用总结

如你所见,Hbase后台进行大量的内部管理,可以管理Region进行自动splitting,同时还提供了工具支持手动管理RegionSplitting,我们还可以可拔插的使用不同的RegionSplitPolicy来控制Splitting的时间和方式;

Table中Region的数量以及如何划分这些Region是理解和优化Hbase集群负载的关键因素,如果你能评估rowkey的分布规律,可以配合合适的RegionSplitPolicy来进行的pre-splitting以达到最佳的初始化负载性能,你可以RegionServer的数量的较少整数倍开始进行预分区初始化,之后交给auto_splitting来接管;

如果你无法很准确的估算出rowkey的初始split-point,最好在初始化创建Table的时候不指定split-point,此时创建的table的region的数量是1,然后通过auto-splitting进行自动拆分,配合increasingToUpperBoundRegionSplitPolicy,但是,请记住⚠️,该策略Region的数量会随着时间的推移保持稳定的状态,而这些region的split-point由目前为止该Table接收的数据决定;

最后,官方建议您养成好习惯,定期观察Hbase集群的WebUI界面,了解集群的负载情况,假如Region的负载不太平整,你可以考虑手动进行split操作,或者设置更大的Region Split threshold。