怎么用Arthas来诊断 HBase异常进程

这篇文章给大家分享的是有关怎么用Arthas来诊断 HBase异常进程的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。

1. 异常突起

HBase 集群的某一个 RegionServer 的 CPU 使用率突然飙升到百分之百,单独重启该 RegionServer 之后,CPU 的负载依旧会逐渐攀上顶峰。多次重启集群之后,CPU 满载的现象依然会复现,且会持续居高不下,慢慢地该 RegionServer 就会宕掉,慢慢地 HBase 集群就完犊子了。

2. 异常之上的现象

CDH 监控页面来看,除 CPU 之外的几乎所有核心指标都是正常的,磁盘和网络 IO 都很低,内存更是充足,压缩队列,刷新队列也是正常的。

怎么用Arthas来诊断 HBase异常进程  arthas 第1张

普罗米修斯的监控也是类似这样的,就不贴图了。

监控指标里的数字,只能直观地告诉我们现象,不能告诉我们异常的起因。因此我们的第二反应是看日志。

与此同时,日志中还有很多类似这样的干扰输出。

怎么用Arthas来诊断 HBase异常进程  arthas 第2张

后来发现这样的输出只是一些无关紧要的信息,对分析问题没有任何帮助,甚至会干扰我们对问题的定位。

但是,日志中大量 scan responseTooSlow 的警告信息,似乎在告诉我们,HBase 的 Server 内部正在发生着大量耗时的 scan 操作,这也许就是 CPU 负载高的元凶。可是,由于各种因素的作用,我们当时的关注点并没有在这个上面,因为这样的信息,我们在历史的时间段里也频繁撞见。

3. 初识 arthas

监控和日志都不能让我们百分百确定 CPU 负载高是由哪些操作引起的,我们用 top 命令也只能看到 HBase 这个进程消耗了很多 CPU,就像下图看到的这样。

怎么用Arthas来诊断 HBase异常进程  arthas 第3张

命令 top 定位到的异常的 HBase 进程 ID 是 1214,该进程就是 HRegionServer 的进程。输入序号 1,回车,就进入了监听该进程的命令行界面。

4.3 dashboard

怎么用Arthas来诊断 HBase异常进程  arthas 第4张

输入 thread 命令回车,查看该进程下所有线程的执行情况。

4.5 thread -n 3

怎么用Arthas来诊断 HBase异常进程  arthas 第5张

单位时间为 5 秒内,资源占用前三名的线程。

4.7 使用async-profiler生成火焰图

生成火焰图的最简单命令。

profiler start

隔一段时间,大概三十秒。

profiler stop

怎么用Arthas来诊断 HBase异常进程  arthas 第6张

关于火焰图的入门级知识:

查看 jvm 进程 cpu 火焰图工具。

火焰图里很清楚地定位到 CPU 时间占用最高的线程是绿框最长的那些线程,也就是 scan 操作。

5. scan 操作引起的 CPU 负载过高

通过以上的进程分析,我们最终可以确定,scan 操作的发生,导致 CPU 负载很高。我们查询 HBase 的 API 基于 happybase 封装而成。

其实常规的 scan 操作是能正常返回结果的,发生异常查询的表也不是很大,所以我们排除了热点的可能。抽象出来业务方的查询逻辑是:

from happybase.connection import Connectionimport time
start = time.time()
con = Connection(host='ip', port=9090, timeout=3000)
table = con.table("table_name")
try:
    res = list(table.scan(filter="PrefixFilter('273810955|')",                      row_start='\x0f\x10&R\xca\xdf\x96\xcb\xe2\xad7$\xad9khE\x19\xfd\xaa\x87\xa5\xdd\xf7\x85\x1c\x81ku ^\x92k',                      limit=3))
except Exception as e:
    pass
end = time.time()print 'timeout: %d' % (end - start)

PrefixFilter 和 row_start 的组合是为了实现分页查询的需求,row_start 的一堆乱码字符,是加密的一个 user_id,里面有特殊字符。日志中看到,所有的耗时查询,都有此类乱码字符的传参。于是,我们猜想,查询出现的异常与这些乱码字符有关。

但是,后续测试复现的时候又发现。

# 会超时
  res = list(table.scan(filter="PrefixFilter('273810955|')",                      row_start='27', limit=3))
  # 不会超时
  res = list(table.scan(filter="PrefixFilter('273810955|')",                      row_start='27381095', limit=3))

也就是,即使不是乱码字符传参,filter 和 row_start 组合异常,也会导致 CPU 异常的高,row_start 指定的过小,小于前缀,数据扫描的范围估计就会变大,类似触发了全表扫描,CPUload 势必会变大。

6. 频繁创建连接或使用线程池造成 scan 线程持续增长

我们操作 HBase 的公共代码是由 happybase 封装而成,其中还用到了 happybase 的线程池,在我们更深入的测试中又发现了一个现象,当我们使用连接池或在循环中重复创建连接时,然后用 arthas 监控线程情况,发现 scan 的线程会很严重,测试代码如下:

6.1 连接在循环外部创建,重复使用

from happybase.connection import Connectionimport time
con = Connection(host='ip', port=9090, timeout=2000)
table = con.table("table")for i in range(100):
    try:
          start = time.time()
        res = list(table.scan(filter="PrefixFilter('273810955|')",                              row_start='\x0f\x10&R\xca\xdf\x96\xcb\xe2\xad7$\xad9khE\x19\xfd\xaa\x87\xa5\xdd\xf7\x85\x1c\x81ku ^\x92k',                              limit=3))
    except Exception as e:
        pass
    end = time.time()print 'timeout: %d' % (end - start)

程序开始运行时,可以打开 arthas 进入到 HRegionServer 进程的监控,运行 thread 命令,查看此时的线程使用情况:

怎么用Arthas来诊断 HBase异常进程  arthas 第7张

小部分在运行,大部分在等待。此时,CPU 的负载情况:

怎么用Arthas来诊断 HBase异常进程  arthas 第8张

6.2 循环在内部频繁创建然后使用

代码如下:

from happybase.connection import Connectionimport timefor i in range(100):
    try:
        start = time.time()
        con = Connection(host='ip', port=9090, timeout=2000)
        table = con.table("table")
        res = list(table.scan(filter="PrefixFilter('273810955|')",                              row_start='\x0f\x10&R\xca\xdf\x96\xcb\xe2\xad7$\xad9khE\x19\xfd\xaa\x87\xa5\xdd\xf7\x85\x1c\x81ku ^\x92k',                              limit=3))
    except Exception as e:
        pass
    end = time.time()print 'timeout: %d' % (end - start)

下图中可以看到开始 RUNNING 的线程越来越多,CPU 的消耗也越来越大。

怎么用Arthas来诊断 HBase异常进程  arthas 第9张

怎么用Arthas来诊断 HBase异常进程  arthas 第10张

6.3 连接池的方式访问 HBase

CPU 被之前的实验拉高,重启下集群使 CPU 的状态恢复到之前平稳的状态。然后继续我们的测试,测试代码:

没有超时时间

from happybase import ConnectionPool
import timepool = ConnectionPool(size=1, host='ip', port=9090)for i in range(100):
    start = time.time()
    try:
        with pool.connection(2000) as con:
            table = con.table("table")
            res = list(table.scan(filter="PrefixFilter('273810955|')",                                  row_start='\x0f\x10&R\xca\xdf\x96\xcb\xe2\xad7$\xad9khE\x19\xfd\xaa\x87\xa5\xdd\xf7\x85\x1c\x81ku ^\x92k',                                  limit=3))
    except Exception as e:
        pass
    end = time.time()print 'timeout: %d' % (end - start)

如果不指定超时时间,会只有一个线程持续运行,因为我的连接池设置为 1。

怎么用Arthas来诊断 HBase异常进程  arthas 第11张

指定超时时间

from happybase import ConnectionPool
import timepool = ConnectionPool(size=1, host='ip', port=9090, timeout=2000)for i in range(100):
    start = time.time()
    try:
        with pool.connection(2000) as con:
            table = con.table("table")
            res = list(table.scan(filter="PrefixFilter('273810955|')",                                  row_start='\x0f\x10&R\xca\xdf\x96\xcb\xe2\xad7$\xad9khE\x19\xfd\xaa\x87\xa5\xdd\xf7\x85\x1c\x81ku ^\x92k',                                  limit=3))
    except Exception as e:
        pass
    end = time.time()print 'timeout: %d' % (end - start)

此次测试中,我指定了连接池中的超时时间,期望的是,连接超时,及时断开,继续下一次耗时查询。此时,服务端处理 scan 请求的线程情况:

怎么用Arthas来诊断 HBase异常进程  arthas 第12张

7. hbase.regionserver.handler.count

怎么用Arthas来诊断 HBase异常进程  arthas 第13张

参考大神的博客,以及自己对这个参数的理解,每一个客户端发起的 RPC 请求(读或写),发送给服务端的时候,服务端就会有一个线程池,专门负责处理这些客户端的请求,这个线程池可以保证同一时间点有 30 个线程可运行,剩余请求要么阻塞,要么被塞进队列中等待被处理,scan 请求撑满了服务端的线程池,大量的耗时操作,把 CPU 资源消耗殆尽,其余常规的读写请求也势必大受影响,慢慢集群就完犊子了。

8. 控制 scan 请求占用很小的队列

首先,这个 hbase.regionserver.handler.count 的参数不能被调小,如果太小,集群并发高时,读写延时必高,因为大部分请求都在排队。理想情况是,读和写占用不同的线程池,在处理读请求时,scan 和 get 分别占用不同的线程池,实现线程池资源隔离。如果是我的话,第一反应可能也会简单、粗略地搞仨线程池,写线程池,get 线程池、scan 线程池。scan 线程池分配很小的核心线程,让其占用很小的资源,限制其无限扩张。但是真实的情况是这样吗?暂时,我还没仔细研究源码,HBase 提供了如下参数,可以满足读写资源分离的需求。

hbase.regionserver.handler.count

描述
在RegionServer上旋转的RPC侦听器实例数。主机将相同的属性用于主机处理程序的计数。过多的处理程序可能适得其反。使它成为CPU计数的倍数。如果大多数情况下是只读的,则处理程序计数接近cpu计数的效果很好。从两倍的CPU计数开始,然后从那里进行调整。
默认30

hbase.ipc.server.callqueue.handler.factor

描述
确定呼叫队列数量的因素。值为0表示在所有处理程序之间共享一个队列。值为1表示每个处理程序都有自己的队列。
默认0.1

hbase.ipc.server.callqueue.read.ratio

描述
将呼叫队列划分为读写队列。指定的间隔(应在0.0到1.0之间)将乘以呼叫队列的数量。值为0表示不拆分呼叫队列,这意味着读取和写入请求都将被推送到同一组队列中。小于0.5的值表示读队列少于写队列。值为0.5表示将有相同数量的读取和写入队列。大于0.5的值表示将有比写队列更多的读队列。值1.0表示除一个队列外的所有队列均用于调度读取请求。示例:给定呼叫队列的总数为10,读比率为0表示:10个队列将包含两个读/写请求。read.ratio为0.3表示:3个队列将仅包含读取请求,而7个队列将仅包含写入请求。read.ratio为0.5表示:5个队列仅包含读取请求,而5个队列仅包含写入请求。read.ratio为0.8表示:8个队列将仅包含读取请求,而2个队列将仅包含写入请求。read.ratio为1表示:9个队列将仅包含读取请求,而1个队列将仅包含写入请求。
默认0

hbase.ipc.server.callqueue.scan.ratio

描述
给定读取呼叫队列的数量(根据呼叫队列总数乘以callqueue.read.ratio计算得出),scan.ratio属性会将读取呼叫队列分为小读取队列和长读取队列。小于0.5的值表示长读队列少于短读队列。值为0.5表示将有相同数量的短读和长读队列。大于0.5的值表示长读取队列比短读取队列多。值为0或1表示使用相同的队列进行获取和扫描。示例:假设读取呼叫队列的总数为8,则scan.ratio为0或1表示:8个队列将同时包含长读取请求和短读取请求。scan.ratio为0.3表示:2个队列将仅包含长读请求,而6个队列将仅包含短读请求。scan.ratio为0.5表示:4个队列将仅包含长读请求,而4个队列将仅包含短读请求。scan.ratio为0.8表示:6个队列将仅包含长读请求,而2个队列将仅包含短读请求。
默认0

这几个参数的作用官网解释的还挺详细,按照其中的意思,配置一定比例,就可以达到读写队列,get 和 scan 队列分离的目的,但是,调配参数后,继续如上测试,发现,并不难控制 RUNNING 的线程的数量,发现没毛用。

这里有一个疑问,队列和我所理解的线程池直接到底是什么关系?是否是一个东西?这个之后需要观其源码,窥其本质。

感谢各位的阅读!关于“怎么用Arthas来诊断 HBase异常进程”这篇文章就分享到这里了,希望以上内容可以对大家有一定的帮助,让大家可以学到更多知识,如果觉得文章不错,可以把它分享出去让更多的人看到吧!

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:niceseo99@gmail.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

评论

有免费节点资源,我们会通知你!加入纸飞机订阅群

×
天气预报查看日历分享网页手机扫码留言评论Telegram