关于HBase元数据cache的一个小坑

问题过程描述

      最近在做关于HBase相关的工作,刚接入了1个关于数字证书的表,业务在灰度的时候发现有大量的超时的请求(备注:默认3秒超时),如下图,基本所有的请求都出现失败:

这个问题有点奇怪,在测试环境测试的时候没有出现过这种超时的问题,通过hbase shell查询数据也正常。目前的架构如下:

从架构上看各个方面假设都可以排除:

假设1、HBase后端的问题

    如果是HBase有问题,那么应该通过HBase shell访问以及测试机器访问(测试机器直接连接线上HBase测试,只有查询的操作)也会有问题,但是通过HBase shell以及测试机器访问都没有出现超时的情况,因此HBase后端的问题基本可以排除。

假设2、Thrift Server的问题

    因为通过LVS转发,其他的业务和数字证书的表共用Thrift Server,其他业务访问HBase都正常,如果是Thrift Server有问题应该其他的表也会出现大量超时的情况,线上其他的表都没发现异常,因此这个基本也可以排除。

假设3、数据证书的表上线前没有初始化?

    数据证书的表在上线前已经全量进行major compaction操作,并且将数字证书的表移动到了重点业务组,这个问题也可以排除。

那到底是哪里的问题?为了找到问题的根源,必须找到超时的现场。于是使用Thriftclient模拟真实的业务访问,并持续在ThriftServer上观察日志。

通过上图发现,在访问不同的ID的时候,发现有部分请求会超过3秒,并且在出现超过3秒的请求的时候,会出现如下的日志:

很明显,上面显示数字证书元数据从本地缓存中移走,路由发生了变动后,导致访问超时了。因为HBase在路由发生变动后,Thrift Server缓存的元数据信息并不会主动的改变,而是在访问出错的时候,才会去重新获取对应region的元数据信息,因此导致了超时。路由变动是由于做优化的时候将数字证书的表从普通组迁移到重点组中,从而导致路由发生了变化。

解决办法:

既然访问超时是由于路由缓存是错误的,那么重启Thrift Server既可解决。

重启Thrift Server后,第一次访问是1秒多一点,然后对应region的第二次以后的访问都在50ms以内了。第一次访问耗时1秒多是因为集群中表的region太多,已经超过了30万个region。

region的寻址扫盲

为了让大家更了解此次的问题,把之前概括的region的寻址方式也描述一下:

HBase的region寻址有老的寻址方式和新的寻址方式,先来看老的寻址方式:

老的寻址方式

在Hbase 0.96版本以前,Hbase有两个特殊的表,分别是-ROOT-表和.META.表,其中-ROOT-的位置存储在ZooKeeper中,-ROOT-本身存储了 .META. Table的RegionInfo信息,并且-ROOT-不会分裂,只有一个region。而.META.表可以被切分成多个region。读取的流程如下图所示:

第1步:client请求ZK获得-ROOT-所在的RegionServer地址

第2步:client请求-ROOT-所在的RS地址,获取.META.表的地址,client会将-ROOT-的相关信息cache下来,以便下一次快速访问

第3步:client请求 .META.表的RS地址,获取访问数据所在RegionServer的地址,client会将.META.的相关信息cache下来,以便下一次快速访问

第4步:client请求访问数据所在RegionServer的地址,获取对应的数据

从上面的路径我们可以看出,用户需要3次请求才能直到用户Table真正的位置,这在一定程序带来了性能的下降。在0.96之前使用3层设计的主要原因是考虑到元数据可能需要很大。但是真正集群运行,元数据的大小其实很容易计算出来。在BigTable的论文中,每行METADATA数据存储大小为1KB左右,如果按照一个Region为128M的计算,3层设计可以支持的Region个数为2^34个,采用2层设计可以支持2^17(131072)。那么2层设计的情况下一个 集群可以存储4P的数据。这仅仅是一个Region只有128M的情况下。如果是10G呢? 因此,通过计算,其实2层设计就可以满足集群的需求。因此在0.96版本以后就去掉了-ROOT-表了。

新的Region寻址方式

如上面的计算,2层结构其实完全能满足业务的需求,因此0.96版本以后将-ROOT-表去掉了。如下图所示:

访问路径变成了3步:

第1步:Client请求ZK获取.META.所在的RegionServer的地址。

第2步:Client请求.META.所在的RegionServer获取访问数据所在的RegionServer地址,client会将.META.的相关信息cache下来,以便下一次快速访问。

第3步:Client请求数据所在的RegionServer,获取所需要的数据。

需要注意的是Client的元数据缓存不更新,当.META.的数据发生更新。如上面的例子,由于Region1的位置发生了变化,Client再次根据缓存去访问的时候,会出现错误,当出现异常达到重试次数后就会去.META.所在的RegionServer获取最新的数据,如果.META.所在的RegionServer也变了,Client就会去ZK上获取.META.所在的RegionServer的最新地址。

了解了Region的寻址方式以及了解了Client的缓存更新机制后,就能很好地了解这次问题的所在了。这次问题由于路由失效,导致Client去访问失效的路由,访问失败以后,才重新去获取Region的路由,获取到正确的路由,自己缓存一份并且去新的路由访问数据。而集群中Region太多导致路由寻址也很耗费事件,这么长的路径导致了请求超过了3秒。再加上业务侧没有对应重试机制,因此导致大部分的请求都出现失败。

后续的优化措施可以从如下两个维度来改进:

1、清理掉一些过期的表,减少region的数量;

2、业务侧必须添加重试机制;

你可能感兴趣的:(关于HBase元数据cache的一个小坑)