HBase最佳实践 – 客户端重试机制

简介:

在运维HBase的这段时间里,发现业务用户一方面比较关注HBase本身服务的读写性能:吞吐量以及读写延迟,另一方面也会比较关注HBase客户端使用上的问题,主要集中在两个方面:是否提供了重试机制来保证系统操作的容错性?是否有必要的超时机制保证系统能够fastfail,保证系统的低延迟特性?

这个系列我们集中介绍HBase客户端使用上的这两大问题,本文通过分析之前一个真实的案例来介绍HBase客户端提供的重试机制,并通过配置合理的参数使得客户端在保证一定容错性的同时还能够保证系统的低延迟特性。

案发现场

最近某业务在使用HBase客户端读取数据时出现了大量线程block的情况,业务方保留了当时的线程堆栈信息,如下图所示:

a

看到这样的问题,首先从日志和监控排查了业务表和region server,确认了在很长时间内确实没有请求进来,除此之外并没有其他有用的信息,同时也没有接到该集群上其他用户的异常反馈,从现象看,这次异常是在特定环境下才会触发的。

案件分析过程

1.根据上图图1所示,所有的请求都block在<0x0000000782a936f0>这把全局锁上,这里需要关注两个问题:

  • 哪个线程持有了这把全局锁<0x0000000782a936f0>?
  • 这是一把什么样的全局锁(对于问题本身并不重要,有兴趣可以参考步骤3)?

2.哪个线程持有了这把锁?

2.1 很容易在jstack日志中通过搜索找到全局锁<0x0000000782a936f0>被如下线程持有:

b

定睛一看,该线程持有了这把全局锁,而且处于TIMED_WAITING状态,因此这把锁可能长时间不释放,导致所有需要这把全局锁的线程都阻塞等待。好了,那问题就转化成了:为什么这个线程会处于TIME_WAITING状态?

2.2 根据上图提示,查看源码中RpcRetryingCall.java的115行代码,可以确定该线程处于TIME_WAITING状态是因为自己休眠导致,如下图所示:

c

RpcRetryingCall函数是Rpc请求重试机制的实现,所以可以有两点推断:

  • HBase客户端请求在那个时间段网络有异常导致rpc请求失败,进入重试逻辑
  • 根据HBase的重试机制(退避机制),每两次重试机制之间会休眠一段时间,即上图115行代码,这个休眠时间太长导致这个线程一直处于TIME_WAITING状态。

休眠时间由上图中expectedSleep = callable.sleep(pause,tries + 1)决定,根据hbase算法(见第三部分),默认最大的expectedSleep为20s,整个重试时间会持续8min,这也就是说全局锁会被持有8min,可这并不能解释持续将近几个小时的阻塞无请求。除非有两种情况:

  • 配置有问题:需要客户端检查hbase.client.pause和hbase.client.retries.number两个参数配置出现异常,比如hbase.client.pause参数如果手抖配成了10000,就有可能出现几个小时阻塞的情况
  • 网络持续有问题:如果线程1持有全局锁重试失败之后退出,线程2竞争到这把锁,此时网络依然有问题,线程2会再次进入重试,重试8min之后失败退出,循环下去,也有可能出现几个小时阻塞的情况

和业务方确认配置,所有参数基本属于默认配置,因此猜测一不成立,那最有可能的情况就是猜测二。经过确认,在事发当时(凌晨0点~早上6点)确实存在很多服务因为云网络升级异常发生抖动的情况出现。然而因为没有具体的日志信息,所以并不能完全确认猜测是否正确。但是,通过问题的分析可以进一步明白HBase重试机制以及部分客户端参数优化策略,这也是写这篇文章的初衷之一。

3. 再来看看这把全局锁到底是什么锁,查看源码可知这把锁是下图中红框中的regionLockObject对象:



d

参考源码注释可知,这把锁是为了防止同时多线程并发加载meta分区。全局锁代码块首先会从缓存中查找meta分区,如果不存在会执行prefetchRegionCache方法远程查找并写入缓存,因此如果第一个线程成功加载meta分区数据并写入缓存,后来线程可以直接使用。

正常情况下,prefetchRegionCache方法只有在缓存不存在的情况下会执行,如果此时网络不存在问题,远程查找meta分区信息会很快完成,持锁时间也会很短。一旦网络出现长时间抖动,就有可能出现这把锁一直被持有,阻塞其他线程。

HBase Rpc重试机制

通过上文分析可知,HBase的重试机制是这次异常发生的关键点,有必要对其进行一次解析。HBase执行rpc失败之后会执行重试操作,

重试的最大次数可以通过配置文件配置,对应的参数为hbase.client.retries.number,0.98版本中该参数的默认值为31。

同时每两次重试之间会sleep一段时间,即上文提到的expectedSleep变量,该变量实现具体算法如下:

public static int RETRY_BACKOFF[] = { 1, 2, 3, 5, 10, 20, 40, 100, 100, 100, 100, 200, 200 };
long normalPause = pause * HConstants.RETRY_BACKOFF[ntries];
long jitter =  (long)(normalPause * RANDOM.nextFloat() * 0.01f); // 1% possible jitter
return normalPause + jitter;

其中RETRY_BACKOFF是一个重试系数表,由小到大递增表示重试时间会随着重试次数逐渐递增。pause变量可以通过配置文件配置,对应的参数为hbase.client.pause,0.98版本中该参数的默认值为100。

暂时忽略jitter这个小随机变量,默认情况下最大的重试间隔休眠时间 expectedSleep = 100 * 200 = 20s。默认重试次数为31,则每次连接集群重试之间的暂停时间将依次为:

[100,200,300,500,1000,2000,4000,10000,10000,10000,10000,20000,20000,…,20000]

这意味着客户端将在448s内重试30次,然后放弃连接到集群.

客户端参数优化实践

很显然,根据上面第二部分和第三部分的介绍,一旦在网络出现抖动的异常情况下,默认最差情况下一个线程会存在8min左右的重试时间,从而会导致其他线程都阻塞在regionLockObject这把全局锁上。为了构建一个更稳定、低延迟的HBase系统,除过需要对服务器端参数做各种调整外,客户端参数也需要做相应的调整:

1. hbase.client.pause:默认为100,可以减少为50

2. hbase.client.retries.number:默认为31,可以减少为21

修改后,通过上面算法可以计算出每次连接集群重试之间的暂停时间将依次为:

[50,100,150,250,500,1000,2000,5000,5000,5000,5000,10000,10000,…,10000]

客户端将会在2min内重试20次,然后放弃连接到集群,进而会再将全局锁交给其他线程,执行其他请求。

总结

这篇文章从一个客户端异常入手,通过堆栈分析、源码追踪、合理推断,一方面整理分享程序异常的定位方法,一方面介绍HBase Rpc的重试机制以及客户端参数优化。下一篇文章将会重点介绍HBase客户端关于超时的细节,敬请期待~


本文转载自:http://hbasefly.com

原文链接

相关实践学习
lindorm多模间数据无缝流转
展现了Lindorm多模融合能力——用kafka API写入,无缝流转在各引擎内进行数据存储和计算的实验。
云数据库HBase版使用教程
&nbsp; 相关的阿里云产品:云数据库 HBase 版 面向大数据领域的一站式NoSQL服务,100%兼容开源HBase并深度扩展,支持海量数据下的实时存储、高并发吞吐、轻SQL分析、全文检索、时序时空查询等能力,是风控、推荐、广告、物联网、车联网、Feeds流、数据大屏等场景首选数据库,是为淘宝、支付宝、菜鸟等众多阿里核心业务提供关键支撑的数据库。 了解产品详情:&nbsp;https://cn.aliyun.com/product/hbase &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
Go 分布式数据库 Hbase
|
SQL 分布式计算 Java
Hbase入门(五)——客户端(Java,Shell,Thrift,Rest,MR,WebUI)
Hbase的客户端有原生java客户端,Hbase Shell,Thrift,Rest,Mapreduce,WebUI等等。 下面是这几种客户端的常见用法。
1632 0
Hbase入门(五)——客户端(Java,Shell,Thrift,Rest,MR,WebUI)
|
SQL 搜索推荐 Java
「从零单排HBase 12」HBase二级索引Phoenix使用与最佳实践
「从零单排HBase 12」HBase二级索引Phoenix使用与最佳实践
551 0
「从零单排HBase 12」HBase二级索引Phoenix使用与最佳实践
|
存储 缓存 算法
生产环境使用HBase,你必须知道的最佳实践
生产环境使用HBase,你必须知道的最佳实践
285 0
|
存储 缓存 算法
生产环境使用HBase,你必须知道的最佳实践
前面,我们已经打下了很多关于HBase的理论基础,今天,我们主要聊聊在实际开发使用HBase中,需要关注的一些最佳实践经验。 1.Schema设计七大原则 1)每个region的大小应该控制在10G到50G之间; 2)一个表最好保持在 50到100个 region的规模; 3)每个cell最大不应该超过10MB,如果超过,应该有些考虑业务拆分,如果实在无法拆分,那就只能使用mob; 4)跟传统的关系型数据库不同,一个HBase的表中列族最多不超过3个,列族中的列可以动态添加的,不要设计过多列族; 5)列族名必须尽量短,因为我们知道在存储的时候,每个keyvalue都会包含列族名;
216 0
|
监控 分布式数据库 API
HBase TB级数据规模不停机迁移最佳实践
有关HBase集群如何做不停服的数据迁移一直都是云HBase被问的比较多的一个问题,目前有许多开源的工具或者HBase本身集成的方案在性能、稳定性、使用体验上都不是很好,因此阿里云提供了BDS迁移服务,可以帮助云上客户实现TB级数据规模不停机迁移
6016 0
|
存储 NoSQL 分布式数据库
案例篇-HBase 在滴滴出行的应用场景和最佳实践
李扬 滴滴出行 资深软件开发工程师
2625 0
|
存储 缓存 Java
HBase最佳实践-读性能优化策略
任何系统都会有各种各样的问题,有些是系统本身设计问题,有些却是使用姿势问题。HBase也一样,在真实生产线上大家或多或少都会遇到很多问题,有些是HBase还需要完善的,有些是我们确实对它了解太少。总结起来,大家遇到的主要问题无非是Full GC异常导致宕机问题、RIT问题、写吞吐量太低以及读延迟较大。
2149 0
|
机器学习/深度学习 SQL 分布式计算
云HBase Phoenix索引构建最佳实践
介绍三种的不同的索引构建方法及其适用场景
2667 0
|
监控 分布式数据库 Python
云HBase Thrift使用最佳实践
  HBase原生只提供了JAVA API客户端,针对诸如python、php、c++等非java语言一般都是通过Thrift代理的方式访问HBase服务,本文从thrift架构、hbase thrift api使用以及如何监控thrift等几个方面详细介绍云HBase Thrift使用最佳实践;  Thrift是一种接口描述语言和二进制通讯协议,它被用来定义和创建跨语言的服务。
1947 0