Spark的HiveThriftServer2服务可用性保障(上)
Posted 爱趴信
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spark的HiveThriftServer2服务可用性保障(上)相关的知识,希望对你有一定的参考价值。
本文其实没啥大数据的内容,其实这里我要讲的是最老土的一个进程会有多少种死法,和对应的一些处理,皮这么一下很skr啊。
我们知道Spark提供了一个HiveThriftServer2服务,最近给客户deliver的一个系统,就跑了一个,跑这个有个好处,就是提供类Hive的JDBC接口,有啥查询直接写SQL就行了,这样不用写SparkSQL代码,通用性和易用性的优势不言而喻。
在yarn运行的这个HiveThriftServer2,只能用client模式,从spark应用的角度来看,这个作业的driver会运行在提交这个应用的主机上,负责提供一个tcp端口接受JDBC连接,接受SQL查询,转化为spark的jobs分发给yarn集群里面的works,最终把查询结果收集起来返回给JDBC客户端。在测试环境短时间使用下,没问题,但是部署到生产环境长时间pilot run之后,这个driver进程的稳定性问题就比较突出了,一开始基本上是半小时要吊死或者crash一次的,经过一段时间的摸索和优化之后,才达到了持续稳定服务。
下面是碰到的2个比较大的问题的处理:
问题1: 查询越来越慢,出现GC overhead limit exceed的OOM exception
执行相同的复杂SQL,会越来越慢,从10来秒慢慢变成几十秒,甚至好几分钟,有时候出现GC overhead limit exceed报错。
原因是因为默认的driver进程分给jvm的Xmx只有1G,而执行复杂查询的过程中,产生的对象太多,几乎把堆内存给占满了,导致jvm花费了大量时间努力的做full GC,但是每次执行却释放不了多少内存,频繁的full GC挤占了实际执行业务逻辑的时间,导致客户端得到结果的时间越来越长,等到full GC的消耗的时间占的比例达到jvm设定的阈值,就直接抛OOM crash掉了。
解决问题:
一开始通过扩大spark-submit的--driver-memory参数去解决这个问题,但是似乎无论加大到多少都没有效果,百思不得其解之下,执行了
ps -ef|grep HiveThriftServer2|grep java
检查一下这个driver进程的参数,发现了-Xmx1024m的参数,似乎客户集群环境下的spark-submit默认就带了这个限制,查了一下spark-submit的配置方式,找到了增加一个配置参数的方法
--conf spark.driver.extraJavaOptions=" -Xms 4G -Xmx4G"
加了这个之后,再执行上面的ps命令,可以看到 -Xmx1024m和-Xmx4G都出现在命令行里面,丑是丑了点,不过确实生效了。
用
jmap -heap [thrift_driver_pid]
可以看到堆内存最大值确实扩大到4G了,old generation的使用率从之前经常爆表的99%也降了下来。
问题2:文件句柄泄露,出现Too many open files的IOException
解决了半小时的问题,继续观察了一天,发现这次是每8~12小时之间就会出现查询特别慢的问题,检查日志发现特别慢的时候会伴随着Too many open files的IOException
于是用上面的ps命令,找到thrift driver的pid,然后
ls -l /proc/[thrift_driver_pid]/fd
发现确实有接近4096个打开的文件句柄,这确实接近了ulimit的上限了。
经过分析,发现有接近3000个的/tmp/[username]/operation_logs/*文件句柄,看来长期的执行过程中,ThriftServer的driver没有及时处理这些文件句柄。
按照正常的画风,这时候应该研究源码了,然而应该还有更省功夫的解决办法,并且我还想偷懒。
思路1: 找关闭输出operation_logs的配置
export HIVE_SERVER2_LOGGING_OPERATION_ENABLED=false
配置这个环境变量后,执行thrift服务器,通过beeline观察,确实没有给beeline的连接生成operation_logs文件。看起来似乎能解决问题,但是很奇怪的是,执行真正的JDBC客户端程序的过程中,仍然会有operation_logs文件输出,并且那个相同的IOException还是以相同的频率出现。
思路2:从客户端入手
尝试重启JDBC客户端应用,发现ThriftServer driver进程的文件句柄数应声而降,看来通过断开客户端可以触发服务端释放FD。
要自动化的利用这个特性保障thriftServer,需要解决2个问题:
i. JDBC客户端的优雅退出和重启。 由于我们的客户端是SparkSQL做数据降维后导数据到MPP的程序,不能再导数据过程中直接kill掉,于是给客户端加了个接受退出信号的后优雅退出的选项。
@volatile var goFlag = true //循环继续标志
// 处理信号,优雅退出
Seq("TERM", "INT").foreach(sig=>{
Signal.handle(new Signal(sig),
new SignalHandler {
def handle(signal: Signal): Unit = {
log.warn("get exit signal, set goFlag to false and wait for tasks finished")
goFlag = false
}
})
})
同时,配置到supervisor里面,注意stopsignal和stopwaitsecs,这样在supervisorctl restart xxx的时候,向这个客户端发送一个INT信号,然后等2分钟让它优雅退出,这样能够再重启客户端的时候,保证导数据操作的完整性。
ii. 定期侦测ThriftServer driver进程的fd占用数,在接近4096的时候,触发thriftServer的JDBC客户端进程的优雅重启。侦测的方法通过定期执行下面的命令就可以了。
ls /proc/[pid]/fd|wc -l
然后,写个脚本执行,后面一篇将介绍一下这个脚本。
以上是关于Spark的HiveThriftServer2服务可用性保障(上)的主要内容,如果未能解决你的问题,请参考以下文章
在 Python 中以编程方式启动 HiveThriftServer
在 Bluemix Apache-Spark 服务上运行的 Spark 应用程序中连接到 postgresql db