创建项目 hive-func,创建函数 CPlusUtil 实现两个整数的加法运算。代码如下:
CPlusUtil.java
package com.baidu.hive.func;
import org.apache.hadoop.hive.ql.exec.UDF;
public class CPlus extends UDF {
public int evaluate(int a, int b) {
return CPlusUtil.add(a, b);
}
}
CPlus 调用 CPlusUtil 的静态方法。
CPlusUtil.java
CPlusUtil 的静态方法是 native 的。
package com.baidu.hive.func;
public class CPlusUtil {
static {
System.loadLibrary("cplus");
}
public static native int add(int a, int b);
}
生成 hive-func-0.1.0.jar
编译项目,生成 target/hive-func-0.1.0.jar 文件。
mvn clean package
生成 so 文件
先生成头文件
cd target/classes/
javah com.baidu.hive.func.CPlusUtil
查看头文件的内容。
com_baidu_hive_func_CPlusUtil.h
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_baidu_hive_func_CPlusUtil */
#ifndef _Included_com_baidu_hive_func_CPlusUtil
#define _Included_com_baidu_hive_func_CPlusUtil
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_baidu_hive_func_CPlusUtil
* Method: add
* Signature: (II)I
*/
JNIEXPORT jint JNICALL Java_com_baidu_hive_func_CPlusUtil_add
(JNIEnv *, jclass, jint, jint);
#ifdef __cplusplus
}
#endif
#endif
我们在和头文件同目录下创建 CPlusUtil.c 文件,内容如下:
CPlusUtil.c
#include <jni.h> // JNI header provided by JDK
#include <stdio.h> // C Standard IO Header
#include "com_baidu_hive_func_CPlusUtil.h" // Generated
JNIEXPORT jint JNICALL Java_com_baidu_hive_func_CPlusUtil_add
(JNIEnv *env, jclass thisObj, jint a, jint b) {
return a+b;
};
用以下命令生成 so 文件
gcc -fPIC -I"$JAVA_HOME/include" -I"$JAVA_HOME/include/linux" -shared -o libcplus.so CPlusUtil.c
注意:java 里加载的是 cplus,生成的文件是 libcplus.so
拷贝文件到 Hadoop 集群
hadoop fs -mkdir -p /apps/hive;
hadoop fs -put hive-func-0.1.0.jar /apps/hive;
hadoop fs -put libcplus.so /apps/hive;
用 beeline 进行第 1 次测试
- 第一次可以执行。
create function default.cplus as 'com.baidu.hive.func.CPlus'
using jar 'hdfs://master-e285d25:8020/apps/hive/hive-func-0.1.0.jar',
file 'hdfs://master-e285d25:8020/apps/hive/libcplus.so';
select default.cplus(1,2);
- 退出 beeline
!q
用 beeline 进行第 2 次测试
select default.cplus(1,2);
HiveServer 后台抛出异常 UnsatisfiedLinkError
使用 hive-cli 正常
直接执行 hive 命令,可以正常使用函数。
原因分析
为了避免不同 session 之间的 add jar 造成的相互影响,HiveServer 每个 session 使用一个 class loader。使用函数的时候,从 hdfs 下载 jar 包,修改 session 的 class loader 的 uris,让 session 的 class loader 知道从哪里读取文件。第 1 次运行的时候,调用 System.loadLibrary("cplus");
是正确的。再次打开一个会话,使用了新的 class loader,认为 CPlus 和 CPlusUtil 类没有加载,造成 CPlus 和 CPlusUtil 两个类都重新加载一遍,本次加载执行 System.loadLibrary("cplus");
失败。
解决方案 1
-
把 libcplus.so 拷贝到 ${HADOOP_HOME}/lib/native 目录下
-
把 hive-func-0.1.0.jar 拷贝到 ${HIVE_HOME}/lib 目录下
-
重启 hive server2
-
测试 hiveserver,多次进出 beeline,执行以下语句,都可以正确执行。
select default.cplus(1,2);
- 测试分布式任务,可以正确执行。
create table t1 as select default.cplus(c1, c2) from tableName;
解决方案 1 总结
- 拷贝文件到 HiveServer 所在的本地文件系统是为了解决 HiveServer 的执行环境问题。
- 使用 HDFS 上的文件是为了解决分布式执行环境的问题。
- 如果 so 文件或者 jar 文件有修改,需要同时更新 HiveServer 的环境和 HDFS 上的文件,重启 HiveServer。
解决方案2
修改 c 代码,在 so 里判断本进程是否已经加载 so 文件,保证 so 文件只加载一次。使用本方案,不需要在 HiveServer 里放 jar 包和 so 文件。以后函数更新仅用更新 HDFS 上的文件,比较方便。