1、发现问题

今天在执行一段hive脚本时遇到报错。脚本简化后示例如下:

set hive.auto.convert.join=true;
add jar hdfs://nsha/user/dw/udf/dw.hive.udf.jar;
create temporary function myudf as 'com.xxx.dw.hive.udf.myUDF';

select 
a.apply_no,
b.overdue_level
from 
(
    select apply_no
    from   mds.mds_order_info 
      where  dt = '2020-10-21'     
) a  
left join 
(
    select  apply_no           
           ,myudf(overdue_level)  as overdue_level
    from   mds.mds_repay_info 
    where  dt = '2020-10-21' 
) b  
on  a.apply_no = b.apply_no;

执行日志如下,日志很简单,看不出重要信息,只是一个日志文件路径 /tmp/dw/dw_20201022212152_812e0225-48f9-4038-860e-4de15c46017c.log 有点用。

Total jobs = 1
Execution log at: /tmp/dw/dw_20201022212152_812e0225-48f9-4038-860e-4de15c46017c.log
2020-10-22 21:21:56	Starting to launch local task to process map join;	maximum memory = 1046478848
Execution failed with exit status: 2
Obtaining error information

Task failed!
Task ID:
  Stage-4

Logs:

/tmp/dw/hive.log

查看日志文件,

vim /tmp/dw/dw_20201022212152_812e0225-48f9-4038-860e-4de15c46017c.log

日志中报错如下,这里看到是在LocalTask的时候,找不到UDF的类。

这个语句中是两个表进行关联,其中b表是个小表,是启动了mapjoin,找不到udf类的原因是什么呢?

2020-10-22 21:21:56,952 ERROR mr.MapredLocalTask (MapredLocalTask.java:executeInProcess(356)) - Hive Runtime Error: Map local work failed
java.lang.RuntimeException: java.lang.ClassNotFoundException: com.xxxx.dw.hive.udf.myUDF
        at org.apache.hadoop.hive.ql.udf.generic.GenericUDFBridge.getUdfClass(GenericUDFBridge.java:134)
        at org.apache.hadoop.hive.ql.exec.FunctionRegistry.isStateful(FunctionRegistry.java:1414)
        at org.apache.hadoop.hive.ql.exec.FunctionRegistry.isDeterministic(FunctionRegistry.java:1377)
        at org.apache.hadoop.hive.ql.exec.ExprNodeGenericFuncEvaluator.isDeterministic(ExprNodeGenericFuncEvaluator.java:153)
        at org.apache.hadoop.hive.ql.exec.ExprNodeEvaluatorFactory.iterate(ExprNodeEvaluatorFactory.java:91)
        at org.apache.hadoop.hive.ql.exec.ExprNodeEvaluatorFactory.toCachedEvals(ExprNodeEvaluatorFactory.java:65)
        at org.apache.hadoop.hive.ql.exec.SelectOperator.initializeOp(SelectOperator.java:61)
        at org.apache.hadoop.hive.ql.exec.Operator.initialize(Operator.java:363)
        at org.apache.hadoop.hive.ql.exec.Operator.initialize(Operator.java:482)
        at org.apache.hadoop.hive.ql.exec.Operator.initializeChildren(Operator.java:439)
        at org.apache.hadoop.hive.ql.exec.Operator.initialize(Operator.java:376)
        at org.apache.hadoop.hive.ql.exec.mr.MapredLocalTask.initializeOperators(MapredLocalTask.java:461)
        at org.apache.hadoop.hive.ql.exec.mr.MapredLocalTask.startForward(MapredLocalTask.java:365)
        at org.apache.hadoop.hive.ql.exec.mr.MapredLocalTask.executeInProcess(MapredLocalTask.java:345)
        at org.apache.hadoop.hive.ql.exec.mr.ExecDriver.main(ExecDriver.java:744)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at org.apache.hadoop.util.RunJar.run(RunJar.java:233)
        at org.apache.hadoop.util.RunJar.main(RunJar.java:148)
Caused by: java.lang.ClassNotFoundException: com.xxxx.dw.hive.udf.myUDF
        at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
        at java.lang.Class.forName0(Native Method)
        at java.lang.Class.forName(Class.java:348)
        at org.apache.hadoop.hive.ql.udf.generic.GenericUDFBridge.getUdfClass(GenericUDFBridge.java:132)
        ... 20 more

2、分析问题

分析下这个Hive SQL 语句是怎么执行的,如下图所示:

UDF hive 使用 hive udf class not found_hive

1、在hive客户端提交上面Sql,在客户端本地启动一个DriverJVM进程,

2、Driver 进程解析脚本,语法分析,语意分析,读取元数据,生成逻辑执行计划,逻辑优化,最终生成一个物理执行计划。

3、接下来执行物理执行计划。对于这个SQL来说,流程如下。

4、首先从HDFS下载UDF jar包到 Driver进程中,放在classpath 中。这一步可以可以从日志中看到,创建临时自定义函数。

5、因为右侧表是小表,所以生成的物理执行计划是mapjoin ,那么就在客户端本地机器启动一个LocalTask JVM进程,(这一步可以从日志中看到)用来从HDFS读取小表数据到内存中,在内存中转换为HashTable,根据语句,在这里执行自定义函数。

6、当Local Task 执行完毕后,Driver 进程负责将classpath 中jar 包、物理执行计划、小表生成的HashTable 提交Yarn 上去执行。

7、根据物理执行计划,Yarn上执行的 ApplicationMaster 分配多个Map 节点从HDFS上读取左侧大表数据,同时会将小表的HashTable分发到各个Map节点中,每个节点分配一个小表全量放在内存里。

8、然后每个Map节点处理大表的一部分数据,同时与小表进行关联,这一步就是mapjoin,关联的结果输出写入HDFS。

以上就是预期的执行过程,然而报错应该是在哪一步呢?根据日志,显然是在Local Task这一步出错了,报的错是ClassNotFound,为什么找不到UDF的类呢?所以猜测jar包的加载是在Driver JVM中做的,Local Task中并没有加载jar包,所以找不到类。

3、解决问题

原因找到了,怎么解决呢?

第一个解决方法 是关闭mapjoin,在map节点上处理小表数据是执行UDF函数,这个时候能读取到jar包。关闭方法如下:

set hive.auto.convert.join=false;

这个方法经过测试可行,但是缺点是不能利用mapjoin这种高效的执行方式了。

第二个解决方法是既然加载不到UDF jar包,那么就不在Local Task中执行UDF,而是在map节点上执行。
那么就把语句改为如下写法,将函数放在外层,join的时候再执行自定义函数。Local Task中只是单纯的读取小表数据。

将语句修改如下:

set hive.auto.convert.join=true;
add jar hdfs://nsha/user/dw/udf/dw.hive.udf.jar;
create temporary function myudf as 'com.xxx.dw.hive.udf.myUDF';

select 
a.apply_no
-- 这里将udf放在外层使用。
,myudf(b.overdue_level)  as overdue_level
from 
(
    select apply_no
    from   mds.mds_order_info 
      where  dt = '2020-10-21'     
) a  
left join 
(
    select  apply_no   
            -- 读取小表的时候只是单纯的读取数据,不调用udf
           ,overdue_level
    from   mds.mds_repay_info 
    where  dt = '2020-10-21' 
) b  
on  a.apply_no = b.apply_no;

再次运行,正常了。