老实说,一般在使用Hive进行数据分析工作时,很少会遇到什么hive的BUG。但有时候我们自己写的UDF或者SerDe会遇到些诡异的问题,尤其是在升级hive的版本时,api变动或hive内部添加了新的执行优化机制可能使原来工作的UDF或SerDe不再正常工作。所以还是需要掌握些DEBUG手段,以备不时之需。
本文主要总结我在使用HQL执行Query类操作时用到的DEBUG方法,主要有这么几板斧:
下面一一简单说明下方法,例子先不举了,一时手头没特合适的CASE。
这个比较基本,也很容易操作。出问题时先打开,兴许有些有用的信息。另外我们自己的UDF或SerDe里也可以输出些DEBUG日志,方便定位问题:
hive -hiveconf hive.root.logger=DEBUG,console
即在HQL前加上EXPLAIN,比如
EXPLAIN SELECT * FROM ...
不过这个在查错误的时候用处不大,我还没用它定位到什么BUG。不过在查一些性能问题,或者查看下hive到MR任务的执行过程时会用得着
先得说下hive的执行过程。大体上是这样的:
所以plan.xml精确的描述了MR程序具体的执行过程,而且xml文件层次清楚,比explain输出更丰富的信息,也很易读。MR执行计划很少出问题,但我们可以借助plan.xml排除一些怀疑会出问题的点,比如确认是翻译阶段的问题,还是MR执行阶段的问题。
曾经有一次定位UDF使用的BUG,最开始怀疑是hive生成MR执行计划出错了,但通过查看plan.xml,我们确认翻译阶段没问题。进而开始使用终极手段来看问题:IDE远程DEBUG,单步跟踪,这个我们稍后说。
查看plan.xml的方法,有点trick。HIVE cli在生成 plan.xml时,会打印日志:
Generating plan file <some_temp_dir>/plan.xml
但hive 任务执行完毕时,会把它删掉。所以你得手快,或者想办法让hql跑的时间长点,比如sleep一下之类。如果有什么参数可以让hive不删plan文件,留言给我, 先行谢过。
之所以说是终极手段,有两个意思:
否则还得找到具体执行此任务的Hadoop节点IP,更麻烦了。 启用本地模式的方法是:
# hadoop0.20 SET mapred.job.tracker=local; # hadoop 1及以上版本 set mapreduce.framework.name=local;
有一个参数容易混淆:
SET hive.exec.mode.local.auto=false;
这实际上是hive的一个优化,即在hive认为合适的时候,自动启用local模式以更快的计算(数据量比较小的时候有用)。所以设置此参数,不一定会以local模式运行。
参数比较简单,很多文章里都有介绍(不过我觉得IBM这篇最好):
-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8765
麻烦之处在于,从启动hive到运行MR程序过程中,需要启动两个JVM(也有只启动一个的情况,稍后再说):
一般我们希望调试MR阶段的代码,所以我们需要保证在启动第一个JVM时,不监听调试端口,只在第二个JVM上加上述参数。或者至少两阶段使用不同的端口,否则第二个JVM端口冲突就无法启动了。
hive启动CliDriver和ExecDriver都通过 ‘hadoop jar’来启动。所以添加JVM参数可以通过HADOOP_CLIENT_OPTS环境变量添加, 但是, 直接export HADOOP_CLIENT_OPTS 是不行的,因为hive在 调用hadoop jar之前,要调用hadoop version 来确定版本,HADOOP_CLIENT_OPTS对hadoop version同样生效,所以只能改hadoop启动脚本了,通常是/usr/lib/hadoop/bin/hadoop,要在这段代码附件设置:
... elif [ "$COMMAND" = "jar" ] ; then # 这里添加 HADOOP_CLIENT_OPTS HADOOP_CLIENT_OPTS="$HADOOP_CLIENT_OPTS -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8765" CLASS=org.apache.hadoop.util.RunJar elif [ "$COMMAND" = "checknative" ] ; then ...
还不够,需要区分JVM1 和JVM2的参数,hive提供了三个变量设置(看代码纠出来的,官方文档还没介绍):
所以最终的代码应该是:
... elif [ "$COMMAND" = "jar" ] ; then if ! echo "$HADOOP_CLIENT_OPTS"|fgrep 'dt_socket' ; then HADOOP_CLIENT_OPTS="$HADOOP_CLIENT_OPTS -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8765" fi HIVE_DEBUG_RECURSIVE=y HIVE_MAIN_CLIENT_DEBUG_OPTS="suspend=n,address=8765" HIVE_CHILD_CLIENT_DEBUG_OPTS="suspend=y,address=8766" export HADOOP_CLIENT_OPTS HIVE_CHILD_CLIENT_DEBUG_OPTS HIVE_DEBUG_RECURSIVE HIVE_MAIN_CLIENT_DEBUG_OPTS CLASS=org.apache.hadoop.util.RunJar elif [ "$COMMAND" = "checknative" ] ; then ...
还有一种情况下hive做了优化,不需要提交MR任务,也就不用起第二个JVM:
SELECT * FROM ...
这时候attach到 8765端口就行,忽略HIVE_CHILD_CLIENT_DEBUG_OPTS