如何进行Hive debug



老实说,一般在使用Hive进行数据分析工作时,很少会遇到什么hive的BUG。但有时候我们自己写的UDF或者SerDe会遇到些诡异的问题,尤其是在升级hive的版本时,api变动或hive内部添加了新的执行优化机制可能使原来工作的UDF或SerDe不再正常工作。所以还是需要掌握些DEBUG手段,以备不时之需。

本文主要总结我在使用HQL执行Query类操作时用到的DEBUG方法,主要有这么几板斧:

  1. 打开hive DEBUG日志
  2. explain
  3. 查看plan.xml
  4. 使用IDE远程Debug

下面一一简单说明下方法,例子先不举了,一时手头没特合适的CASE。

打开hive DEBUG日志

这个比较基本,也很容易操作。出问题时先打开,兴许有些有用的信息。另外我们自己的UDF或SerDe里也可以输出些DEBUG日志,方便定位问题:

hive -hiveconf hive.root.logger=DEBUG,console

explain

即在HQL前加上EXPLAIN,比如

EXPLAIN SELECT * FROM ...

不过这个在查错误的时候用处不大,我还没用它定位到什么BUG。不过在查一些性能问题,或者查看下hive到MR任务的执行过程时会用得着

查看plan.xml

先得说下hive的执行过程。大体上是这样的:

  1. hive 客户端(一般是hive cli,入口类为CliDriver)把HQL翻译成MR执行计划(Operator树)并序列化到 plan.xml
  2. plan.xml 上传到hdfs上
  3. hive 客户端新起一进程,提交MapReduce程序,其入口类为ExecDriver,ExecDriver的Mapper会在setup阶段读取plan.xml,反序列化成Operator树结构,然后执行。

所以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文件,留言给我, 先行谢过。

使用IDE远程DEBUG

之所以说是终极手段,有两个意思:

  • 如果上面的几个手段都定位不到问题,这个八成能帮你找到问题,反正我屡试不爽
  • 用起来着实得费些力气:

首先,最好能用hive本地模式复现BUG

否则还得找到具体执行此任务的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模式运行。

为JVM 添加远程调试参数

参数比较简单,很多文章里都有介绍(不过我觉得IBM这篇最好):

-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8765
  • suspend=y 是为了让我们有机会把IDE attach到被调试进程上,否则又是个拼手快的活儿了。

麻烦之处在于,从启动hive到运行MR程序过程中,需要启动两个JVM(也有只启动一个的情况,稍后再说):

  1. hive cli需要一个JVM,入口类CliDriver
  2. 提MR程序需要一个JVM,入口类ExecDriver

一般我们希望调试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提供了三个变量设置(看代码纠出来的,官方文档还没介绍):

  • HIVE_DEBUG_RECURSIVE: 告诉hive要把 HADOOP_CLIENT_OPTS 变量传给子JVM
  • HIVE_MAIN_CLIENT_DEBUG_OPTS和HIVE_CHILD_CLIENT_DEBUG_OPTS:hive在将HADOOP_CLIENT_OPTS传给子JVM时,会把 HADOOP_CLIENT_OPTS中HIVE_MAIN_CLIENT_DEBUG_OPTS的值的部分,替换成HIVE_CHILD_CLIENT_DEBUG_OPTS的值(好绕啊)

所以最终的代码应该是:

...
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




你可能感兴趣的:(Hive)