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 语句是怎么执行的,如下图所示:
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;
再次运行,正常了。