HBase Split 简介

Hbase Split 是一个很重要的功能,HBase 通过把数据分配到一定数量的 Region 来达到负载均衡的。当 Region 管理的数据过多时,可以通过手动或自动的方式触发 HBase Split 将一个 Region 分裂成两个新的子 Region,并对父 Region 进行清除处理(不会立即清除)。

HBase 为什么需要 Split?
相比于传统 RDBMS 对大数据表的扩展方式,HBase 天然支持自动分库分表,实现的基础就是 Split 和 Rebalance。
Region 是管理一段连续的 Rowkey 的核心单元,当 Region 管理的 Rowkey 数量多时,或 HFile 文件较大时,都会影响到性能。

Pre-splitting

一个表刚被创建的时候,Hbase 默认分配一个 Region 给表,所有的读写请求都会访问到一个 RegionServer 上的唯一一个 Region。这样就达不到负载均衡的效果了,集群中的其他 RegionServer 就会处于空闲的状态(考虑只有一个表的情况)。解决这个问题可以有两种方法:

  • 如果能很好的预计业务的 Rowkey 分布和数据增长情况,可以在建表时分配好 Region 的数量和每个 Region Rowkey 范围,同时禁用表的自动分区,因为 Split 操作会有额外的开销和维护成本。
  • 另一种方法只需要指定分配的 Region 数量,利用 pre-splitting,生成每个 Region 的 Rowkey 范围

Hbase 自带了两种 pre-split 的算法,分别是 HexStringSplit 和 UniformSplit 。HexStringSplit 适用于十六进制字符的 Rowkey(MD5)。UniformSplit 适用于随机字节组成的 Rowkey(Hash)。

Split 触发时机

HBase 中共有3种情况会触发 HBase Split:

  1. 当 Memstore flush 操作后,HRegion 写入新的 HFile,有可能产生较大的 HFile,会判断是否需要执行 Split。
  2. HStore 执行完成 Compact 操作后可能产生较大的 HFile,会判断是否需要执行 Split。
  3. HBaseAdmin 手动执行 split 命令时,会触发 Split。

Split 触发策略

目前已经的支持触发策略多达6种,每种触发策略都有各自的适用场景,可以根据业务在表级别(Column family 级别)选择不同的切分触发策略。一般情况下使用默认切分策略即可。

  • ConstantSizeRegionSplitPolicy:0.94版本前默认切分策略。
    一个 Region 中最大 Store 的大小大于设置阈值之后才会触发切分,Store 大小为压缩后的文件大小(启用压缩的场景)
    切分策略对于大表和小表没有明显的区分
  • IncreasingToUpperBoundRegionSplitPolicy:0.94版本~2.0版本默认切分策略。
    和 ConstantSizeRegionSplitPolicy 思路相同,一个 Region 中最大 Store 大小大于设置阈值就会触发切分,区别是这个阈值并不像 ConstantSizeRegionSplitPolicy 是一个固定的值,而是会在不断调整。
    调整规则和 Region 所属表在当前 RegionServer 上的 Region 个数有关系 :(#regions) * (#regions) * (#regions) * flush_size * 2,最大值为用户设置的 MaxRegionFileSize
    能够自适应大表和小表,这种策略下很多小表会在大集群中产生大量小 Region,分散在整个集群中
  • SteppingSplitPolicy:2.0版本默认切分策略。
    相比 IncreasingToUpperBoundRegionSplitPolicy 简单了一些,依然和待分裂 Region 所属表在当前 RegionServer 上的 Region 个数有关系:如果 Region 个数等于1,切分阈值为 flush_size * 2,否则为 MaxRegionFileSize
  • DisableSplitPolicy:禁止 Region split
  • KeyPrefixRegionSplitPolicy:切分策略依然依据默认切分策略,根据 Rowkey 指定长度的前缀来切分 Region,保证相同的前缀的行保存在同一个 Region 中。由 KeyPrefixRegionSplitPolicy.prefix_length 属性指定 Rowkey 前缀长度。按此长度对splitPoint进行截取。
    此种策略比较适合有固定前缀的 Rowkey。当没有设置前缀长度,切分效果等同与 IncreasingToUpperBoundRegionSplitPolicy。
  • DelimitedKeyPrefixRegionSplitPolicy:切分策略依然依据默认切分策略,同样是保证相同 RowKey 前缀的数据在一个Region中,但是是以指定分隔符前面的前缀为来切分 Region。

Split 流程

Region split 的过程可以简单的理解为:在 Region 中找到一个合适的 split point,在这个 split point 上将该 Region 的数据划分为两个新的 Region。当然这个过程的实现起来很复杂,Split 发生时,新创建的子 Region 不会立即将所有数据重新写入新文件,而是会创建类似于符号链接文件的小文件,称为引用文件(reference files),根据 split point,指向父存储文件的顶部或底部(父 Region 的一半数据)。

下文引用官方博客对 HBase split 流程的解释:

HBase Split

HBase Split

HBase 将整个切分过程包装成了一个事务,为了保证切分事务的原子性。整个分裂事务过程分为三个阶段:prepare – execute – (rollback)

  • prepare阶段:在内存中初始化两个子 Region,具体是生成两个 HRegionInfo 对象,包含 TableName、RegionName、Startkey、Endkey等。同时会生成一个 transaction journal,这个对象用来记录切分的进展
  • execute阶段:切分的核心操作
    (1)在ZK节点 /hbase/region-in-transition/region-name 下创建一个 znode,并设置状态为SPLITTING
    (2)Master 通过监听ZK节点,检测到 Region 状态的变化
    (3)RegionServer 在父 Region 的数据目录(HDFS)下创建一个名称为 .splits 的子目录
    (4)RegionServer 关闭父 Region,强制将数据 flush 到磁盘,并这个 Region 标记为 offline 的状态。此时,落到这个 Region 的请求都会返回 NotServingRegionException 这个错误,客户端需要进行一些重试,直到新的 Region 上线。
    (5)RegionServer 在 .splits 目录(HDFS)下创建 daughterA 和 daughterB 子目录,并在文件夹中创建对应的 reference 文件,分别指向父 Region 的数据文件中的一部分。
    (6)RegionServer 创建子 Region 的目录(HDFS),并将 daughterA 和 daughterB 目录下的文件拷贝到 Region 目录。
    (7)在 .META. 表中设置父 Region 为 offline 状态,不再提供服务。并将子 Region 的信息添加到 .META. 表中父 Region 的信息中(splitA 和 splitB 两列)。这个时候如果扫描 hbase:meta 表,会发现父 Region 正在执行 split,并不能看到子 Region 的信息。如果 RegionServer 执行这个过程失败,Master 和下一个分配了这个 Region 的 Regionserver 会清除 split 相关的状态数据。
    (8)RegionServer 并行启用两个子 Region,并正式提供对外服务
    (9)RegionSever 将 daughterA 和 daughterB 添加到 .META. 表中,并设置为 online 状态,这样就可以从 .META. 找到子 Region,并可以对子 Region 进行访问了。
    (10)RegionServr 修改ZK节点 /hbase/region-in-transition/region-name 的状态为SPLIT,Master 就可以监听到 Region 状态的更新。Split 事务就此结束。

再次强调,为了减少对业务的影响,Region 的 Split 并不涉及到数据迁移的操作,而只是创建了对父Region的指向。只有在做大合并的时候,才会将数据进行迁移。

  • rollback阶段:如果 execute 阶段出现异常,则执行 rollback 操作。为了实现回滚,整个切分过程被分为很多子阶段,回滚程序会根据当前进展到哪个子阶段清理对应的垃圾数据。JournalEntryType 类定义了各个子阶段。

Region 事务性保证

整个region切分是一个比较复杂的过程,涉及子步骤,因此必须保证整个 Split 过程的事务性,即要么完全成功,要么完全未开始,在任何情况下也不能出现 Split 只完成一半的情况。为了实现事务性,Hbase 设计了使用状态机(见 SplitTransaction 类)的方式保存切分过程中的每个子步骤状态,这样一旦出现异常,系统可以根据当前所处的状态决定是否回滚,以及如何回滚。

目前实现中这些中间状态都只存储在内存中,因此一旦在切分过程中出现 RegionServer 宕机的情况,有可能会出现切分处于中间状态的情况,也就是RIT状态。这种情况下可使用 hbck 工具,根据实际情况查看并分析解决方案。

在2.0版本 HBase 实现了新的分布式事务框架 Procedure V2(HBASE-12439),使用 HLog 存储这种单机事务(DDL、Split、Move 等操作)的中间状态。保证即使在事务执行过程中参与者发生了宕机,依然可以使用 HLog 作为协调者对事务进行回滚操作或重新提交。

通过 reference 文件如何查找到对应的数据

根据文件名来判断是否是 reference 文件:

  1. reference 文件的命名规则为前半部分为父 Region 对应的 HFile 的文件名,后半部分是父 Region 的名称,因此读取的时候也根据前半部分和后半部分来定位文件。
  2. 根据 reference 文件的内容来确定扫描的范围,reference 的内容包含两部分:一部分是切分点 splitkey,另一部分是 boolean 类型的变量,如果为 true 则扫描文件的上半部分,反之则扫描文件的下半部分
  3. 接下来确定了扫描的文件,以及文件的扫描范围,那就按照正常的文件检索了

Split 对其他模块的影响

执行 Region Split 过程不涉及数据的移动,所以可以很快完成。新生成的子 Region 文件中没有任何用户数据,而是一个 reference 文件,文件中存储的是一些元数据信息,包括切分点的 Rowkey 等。引入了以下问题:

  1. 父 Region 的数据什么时候会迁移到子 Region 目录
    子 Region 发生 major_compaction 时。将父 Region 目录中属于该子 Region 的所有数据读出来并写入子 Region 数据文件目录中,这一操作符合 compaction 本身的处理逻辑,因此在 compaction 中操作。
  2. 父 Region 什么时候会被删除
    HMaster 会启动一个线程定期检查所有处于 splitting 状态的父 Region,确定其是否可以被清理。检测线程首先会在 .META. 表中找到 splitting region,并找出其生成的两个子 Region(.META. 表中 splitA 和 splitB 列)。然后检查两个子 Region 是否保留引用文件,如果都不存在就认为该 splitting region 可以被删除和下线。.META. 表中的信息参考下图:

注意事项

使用 Split 时有以下需要注意的:

  1. 不能对元数据表进行 Split
  2. 不能对正在恢复的 Region 进行 Split
  3. 如果某个 Region 存在引用文件的不能 Split
  4. 当 Split 执行到将父 Region 下线之后,子 Region还未创建之前,如果此刻正访问的是父 Region,在客户端没有更新 Region 地址缓存的情况下,会报 NotServingRegionException 异常,因此客户端需要做好重试机制。
  5. Split 的完整过程中有个关键的时间点是无法回退的点(PONR: point of no return),发生在将 .META. 表中的父 Region 下线之前,如果进入 PONR 之后,由于种种原因更新 .META. 失败,需要重启所在的 RegionServer。

Reference:
https://zh.hortonworks.com/blog/apache-hbase-region-splitting-and-merging/
http://hbase.apache.org/book.html

转载自:
https://jianshu.com/p/53459997c814

发表评论

电子邮件地址不会被公开。 必填项已用*标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据