深入解读 HBase2.0 新功能之高可用读 Region Replica

1.前言

基于时间线一致的高可用读(Timeline-consistent High Available Reads),又称 Region replica。其实早在 HBase-1.2 版本的时候,这个功能就已经开发完毕了, 但是还是不太稳定,离生产可用级别还有一段距离,后来社区又陆陆续续修复了 一些 bug,比如说 HBASE-18223。这些 bug 很多在 HBase-1.4 之后的版本才修 复,也就是说 region replica 功能基本上在 HBase-1.4 之后才稳定下来。介于 HBase-1.4 版本目前实际生产中使用的还比较少,把 region replica 功能说成是 HBase2.0 中的新功能也不为过。

2.为什么需要 Region Replica

在 CAP 理论中,HBase 一直是一个 CP(Consistency&Partition tolerance)系统。 HBase 一直以来都在遵循着读写强一致的语义。所以说虽然在存储层,HBase 依 赖 HDFS 实现了数据的多副本,但是在计算层,HBase 的 region 只能在一台 RegionServer 上线提供读写服务,来保持强一致。如果这台服务器发生宕机时, Region 需要从 WAL 中恢复还缓存在 memstore 中未刷写成文件的数据,才能重新上线服务。

_2019_01_10_3_20_46

由于 HBase 的 RegionServer 是使用 Zookeeper 与 Master 保持 lease。而为了不让 JVM GC 停顿导致 RegionServer 被 master“误判”死亡,这个 lease 时间通常都 会设置为 20~30s,如果 RegionServer 使用的 Heap 比较大时,这个 lease 可能还 会设的更长。加上宕机后,region 需要 re-assign,WAL 可能需要 recoverlease 和被 replay 操作,一个典型的 region 宕机恢复时间可能长达一分钟!这就意味着在这一分钟内,这个 region 都无法被读写。

由于 HBase 是一个分布式系统, 同一张表的数据可能分布在非常多的 RegionServer 和 region 里。如果这是一个大 HBase 集群,有 100 台 RegionServer 机器,那么宕机一台的话,可能只有 1% 的用户数据被影响了。但是如果这是小用户的 HBase 集群,一共就只有 2 台
RegionServer,宕机一台意味着 50%的用户数据都在 1~2 分钟之内无法服务,这是很多用户都无法忍受的。

其实,很大一部分用户对读可用性的需求,可能比读强一致的需求还要高。在故 障场景下,只要保证读继续可用,“stale read”,即读到之前的数据也可以接受。 这就是为什么我们需要 read replica 这个功能。

3.Region Replica 技术细节

Region replica 的本质,就是让同一个 region host 在多个 regionserver 上。原来的 region,称为 Default Replica(主 region),提供了与之前类似的强一致读写体验。而与此同时,根据配置的多少,会有一个或者多个 region 的副本,统称为 region replica,在另外的 RegionServer 上被打开。并且由 Master 中的 LoadBalancer 来保证 region 和他们的副本,不会在同一个 RegionServer 打开,防止一台服务器的宕机导致多个副本同时挂掉。

_2019_01_10_3_22_43

Region Replica 的设计巧妙之处在于,额外的 region 副本并不意味着数据又会多出几个副本。这些 region replica 在 RegionServer 上 open 时,使用的是和主 region 相同的 HDFS 目录。也就是说主 region 里有多少 HFile,那么在 region replica 中,这些数据都是可见的,都是可以读出来的。region replica 相对于主 region,有一些明显的不同。

首先,region replica 是不可写的。这其实很容易理解,如果 region replica 也可以写的话,那么同一个 region 会在多个 regionserver 上被写入,连主 region 上的强一致读写都没法保证了。

再次,region replica 是不能被 split 和 merge 的。region replica 是主 region 的附 属品,任何发向 region replica 的 split 和 merge 请求都会被拒绝掉。只有当主 region split/merge 时,才会把这些 region replica 从 meta 表中删掉,建立新生 成 region 的 region 的 replica。

4.replica 之间的数据同步

那么,既然 region replica 不能接受写,它打开之后,怎么让新写入的数据变的 可见呢?这里,region replica 有两种更新数据的方案:

4.1. 定期的 StoreFile Refresher

这个方案非常好理解,region replica 定期检查一下它自己对应的 HDFS 目录,如果发现文件有变动,比如说 flush 下来新的文件,文件被 compaction 掉,它就刷 新一下自己的文件列表,这个过程非常像 compaction 完成之后删除被 compact 掉的文件和加入新的文件的流程。StoreFile Refresher 方案非常简单,只需要在 RegionServer 中起一个定时执行的 Chroe,定期去检查一下它上面的 region 哪
些是 region replica,哪些到了设置好的刷新周期,然后刷新就可以了。但这个方案缺点也十分明显,主 region 写入的数据,只有当 flush 下来后,才能被 region replica 看到。而且 storeFile Refresher 本身还有一个刷新的周期,设的太短了, list 文件列表对 NN 的冲击太频繁,设的太长,就会造成数据长时间在 region replica 中都不可见

4.2. Internal Replication

我们知道,HBase 是有 replication 链路的,支持把一个 HBase 集群的数据通过 replication 复制到另外一个集群。那么,同样的原理,可以在 HBase 集群内部建立一条 replication 通道,把一个 Server 上的主 region 的数据,复制到另一个 Server 的 region replica 上。那么 region replica 接收到这些数据之后,会把他们写入 memstore 中。对,你没看错,刚才我说了 region replica 是不接受写的,这是指 replica 不接受来自客户端的写,如果来自主 region 的 replication 的数据, 它还是会写入 memstore 的。但是,这个写和普通的写有很明显的区别。第一个,replica region 在写入来自主 region 的时候,是不写 WAL 的,因为这些数据已经 在主 region 所在的 WAL 中持久化了,replica 中无需再次落盘。第二个,replica region 的 memstore 中的数据是不会被 flush 成 HFile。我们知道,HBase 的 replication 是基于复制 WAL 文件实现的,那么在主 region 进行 flush 时,也会写入特殊的标记 Flush Marker。当 region replica 收到这样的标记时,就直接会把所 有 memstore 里的数据丢掉,再做一次 HDFS 目录的刷新,把主 region 刚刚刷下 去的那个 HFile include 进来。同样,如果主 region 发生了 compaction,也会写入相应的 Compaction Marker。读到这样的标记后,replica region 也会做类似的动作。
Internal replication 加快了数据在 region replica 中的可见速度。通过 replication 方案,只要 replication 本身不发生阻塞和延迟,region replica 中的数据可以做到 和主 region 只差几百 ms。但是,replication 方案本身也存在几个问题:

  1. META 表 无法通过 replication 来同步数据。如果给 meta 表开了 region replica 功能,meta 表主 region 和 replica 之间的数据同步,只能通过定 期的 StoreFile Refresher 机制。因为 HBase 的 replication 机制中会过滤 掉 meta 表的数据。
  2. 需要消耗额外的 CPU 和网络带宽来做 Replication。由于 region replica 的数据同步需要,需要在 HBase 集群内部建立 replication 通道,而且有几个 replica,就意味着需要从主 region 发送几份数据。这会增加 RegionServer 的 CPU 使用,同时在 server 之间复制数据还需要占用带宽
  3. 写 memstore 需要额外的内存开销。为了让 replica region 的数据缺失的内容尽量的少,主 region 的数据会通过 replication 发送到 replica 中,这些数据都会保存在 memstore 中。也就是说同样的一份数据,会同时存在主region 的 memstore 中,也会存在 replica region 的 memstore 中。replica 的数量是几,那么 memstore 的内存使用量就是几倍。

下面的两个问题虽然可以通过配置一些参数解决,但是列在这里,仍然需要注意, 因为一旦参数没有配对,就会产生这样的问题。

  1. 在 replica region failover 后,读到的数据可能会回退。我们假设一个情 况。客户端写入 X=1,主 region 发生 flush,X=1 刷在了 HFile 中,然后客 户端继续写入 X=2,X=3,那么在主 region 的 memstore 中 X=3。同时,通过 replication,X=2,X=3 也被复制到了 replica region 的 memstore 中。如 果客户端去 replica 中去读取 X 的数据,也能读到 3。但是由于 replica region memstore 中的数据是不写 WAL 的,也不刷盘。那么当 replica 所在 的机器宕机后,它是没有任何数据恢复流程的,他会直接在其他 RegionServer 上线。上线后它只能读取 HFile,无法感知主 region memstore 里的数据。这时如果客户端来 replica 上读取数据,那么他只会读到 HFile 中的 X=1。也就是说之前客户端可以读到 X=3,但后来却只能读到 X=1 了, 数据出现了回退。为了避免出现这样的问题,可以配置一个 hbase.region.replica.wait.for.primary.flush=true 的参数,配置之后, replica region 上线后,会被标记为不可读,同时它会去触发一次主 region 的 flush 操作。只有收到主 region 的 flush marker 之后,replica 才把自 己标记为可读,防止读回退
  2. replica memstore 过大导致写阻塞。上面说过,replica 的 region 中 memstore 是不会主动 flush 的,只有收到主 region 的 flush 操作,才会去 flush。同一台 RegionServer 上可能有一些 region replica 和其他的主 region 同时存在。这些 replica 可能由于复制延迟(没有收到 flush marker), 或者主 region 没有发生 flush,导致一直占用内存不释放。这会造成整体 的内存超过水位线,导致正常的写入被阻塞。为了防止这个问题的出现, HBase 中 有 一 个 参 数 叫 做
    hbase.region.replica.storefile.refresh.memstore.multiplier,默认值是 4。这个参数的意思是说,如果最大的 replica region 的 memstore 已经 超过了最大的主region memstore的内存的4倍,就主动触发一次StoreFile Refresher 去更新文件列表,如果确实发生了 flush,那么 replica 内存里 的数据就能被释放掉。但是,这只是解决了 replication 延迟导致的未 flush 问题,如果这个 replica 的主 region 确实没有 flush 过,内存还是不能被释放。写入阻塞还是会存在。

5.Timeline Consistency Read

无论是 StoreFile Refresher 还是 Internal replication,主 region 和 replica 之间的数据更新都是异步的,这就导致在 replica region 中读取数据时,都不是强一致的。read replica 的作者把从 region replica 中读数据的一致性等级定为 Timeline Consistency。只有用户明确表示能够接受 Timeline consistency,客户端的请求才会发往 replica 中。

_2019_01_10_6_11_42

比如说上图中,如果客户端是需要强一致读,那么客户端的请求只会发往主 region,即 replica_id=0 的 region,他就会读到 X=3.如果他选择了 Timeline consistency 读,那么根据配置,他的读可能落在主上,那么他仍然会读到 X=3, 如果他的读落在了 replica_id=1 的 region 上,因为复制延迟的存在,他就只能读 到 X=2.如果落在了 replica_id=2 上,由于 replication 链路出现了问题,他就只能 读到 X=1。

6.Region replica 的使用方法

6.1 服务端配置

  hbase.regionserver.storefile.refresh.period
  

如果要使用 StoreFile Refresher 来做为 Region replica 之间同步数据的策略,就 必须把这个值设置为一个大于 0 的数,即刷新 storefile 的间隔周期(单位为 ms) 上面的章节讲过,这个值要不能太大,也不能太小。

 hbase.regionserver.meta.storefile.refresh.period

由于 Meta 表的 region replica 不能通过 replication 来同步,所以如果要开启 meta 表的 region replica,必须把这个参数设成一个不为 0 的值,具体作用参见上一个参数,这个参数只对 meta 表生效。

 hbase.region.replica.replication.enabled 
 hbase.region.replica.replication.memstore.enabled

如果要使用 Internal replication 的方式在 Region replica 之间同步数据的策略, 必须把这两个参数都设置为 true

 hbase.master.hfilecleaner.ttl

在主 region 发生 compaction 之后,被 compact 掉的文件会放入 Achieve 文件夹内,超过 hbase.master.hfilecleaner.ttl 时间后,文件就会被从 HDFS 删除掉。而此时,可能 replica region 正在读取这个文件,这会造成用户的读取抛错返回。 如果不想要这种情况发生,就可以把这个参数设为一个很大的值,比如说 3600000(一小时),总没有读操作需要读一个小时了吧?

 hbase.meta.replica.count

mata 表的 replica 份数,默认为 1,即不开启 meta 表的 replica。如果想让 meta表有额外的一个 replica,就可以把这个值设为 2,依次类推。此参数只影响 meta 表的 replica 份数。用户表的 replica 份数是在表级别配置的,这个我后面会讲

 hbase.region.replica.storefile.refresh.memstore.multiplier

这个参数我在上面的章节里有讲,默认为 4

 hbase.region.replica.wait.for.primary.flush

这个参数我在上面的章节里有讲,默认为 true

需要注意的是,开启 region replica 之后,Master 的 balancer 一定要用默认的 StochasticLoadBalancer,只有这个 balancer 会尽量使主 region 和他的 replica 不在同一台机器上。其他的 balaner 会无区别对待所有的 region。

6.2 客户端配置

 hbase.ipc.client.specificThreadForWriting

因为当存在 region replica 时,当客户端发往主 region 的请求超时后,会发起一个请求到 replica region,当其中一个请求放回后,就无需再等待另一个请求的结果了,通常要中断这个请求,使用专门的的线程来发送请求,比较容易处理中断。 所以如果要使用 region replica,这个参数要配为 true。

 hbase.client.primaryCallTimeout.get 
 hbase.client.primaryCallTimeout.multiget 
 hbase.client.replicaCallTimeout.scan

分别对应着,get、multiget、scan 时等待主 region 返回结果的时间。如果把这 个值设为 1000ms,那么客户端的请求在发往主 region 超过 1000ms 还没返回后, 就会再发一个请求到 replica region(如果有多个 replica 的话,就会同时发往多个 replica)

 hbase.meta.replicas.use

如果服务端上开启了 meta 表的 replica 后,客户端可以使用这个参数来控制是否使用 meta 表的 replica 的 region。

6.3 建表

在 shell 建表时

 create 't1', 'f1', {REGION_REPLICATION => 2}

Replica 的份数支持动态修改,但修改之前必须 disable 表

 diable 't1'
 alter 't1', {REGION_REPLICATION => 1} 
 enable 't1'

6.4 访问有 replica 的表

如果可以按请求设置一致性级别,如果把请求的一致性级别设为 Consistency.TIMELINE,即有可能读到 replica 上

_2019_01_11_11_51_04

另外,用户可以通过 Result.isStale()方法来获得返回的 result 是否来自主 region,

如果为 isStale 为 false,则结果来自主 region。

_2019_01_11_11_51_37

7.总结和建议

Region Replica 功能给 HBase 用户带来了高可用的读能力,提高了 HBase 的可用 性,但同时也存在一定的缺点:

  1. 高可用的读基于 Timeline consistency,用户需要接受非强一致性读才能 开启这个功能
  2. 使用 Replication 来做数据同步意味着额外的 CPU,带宽消耗,同时根据 replica 的多少,可能会有数倍的 memstore 内存消耗
  3. 读取 replica region 中的 block 同样会进 block cache(如果表开启了 block cache 的话),这意味着数倍的 cache 开销
  4. 客户端 Timeline consistency 读可能会把请求发往多个 replica,可能带 来更多的网络开销

Region Replica 只带来了高可用的读,宕机情况下的写,仍然取决于主 region 的 恢复时间,因此 MTTR 时间并没有随着使用 Region replica 而改善。虽然说 region replica 的作者在规划中有写计划在宕机时把一个 replica 提升为主,来优化 MTTR 时间,但截至目前为止,还没有实现。

个人建议,region replica 功能适合于用户集群规模较小,对读可用性非常在意, 同时又可以接受非强一致性读的情况下开启。如果集群规模较大,或者读写流量 非常大的集群上开启此功能,需要留意内存使用和网络带宽。Memstore 占用内 存过高可能会导致 region 频繁刷盘,影响写性能,同时 cache 容量的翻倍会导 致一部分读请求击穿 cache 直接落盘,导致读性能的下降。

作者:杨文龙 阿里巴巴 技术专家 HBase Committer&HBase PMC

猜你喜欢

转载自yq.aliyun.com/articles/686234