背景
用户只需要编写Fink SQ就可以快速构建实时计算任务,通过yarn session模式试运行或者语法校验需要自定义udf,其中用户可以通过上传Jar包的方式支持自定义函数UDF,这里就需要在启动作业的时候能够动态加载自定义Jar包。
解决
Flink 官方文档可以招待找到关于 pipeline.classpath 参数配置的描述,通过该配置项来加载classpath。yarn的api并没有提供该配置项目的参数入口,所以在作业的 main 函数中直接通过反射获取 Configuration 对象,并且直接设置 pipeline.classpaths 的值。
private static void loadPipelineClassPaths(StreamExecutionEnvironment env, List<String> jarList) throws Exception{
Field configuration = StreamExecutionEnvironment.class.getDeclaredField("configuration");
configuration.setAccessible(true);
Configuration o = (Configuration)configuration.get(env);
Field confData = Configuration.class.getDeclaredField("confData");
confData.setAccessible(true);
Map<String,Object> temp = (Map<String,Object>)confData.get(o);
temp.put("pipeline.classpaths", jarList);
}
同时还需要在作业的 main 方法里动态加载Jar包到 URLClassloader里:
public static void loadJar(URL jarUrl) {
//从URLClassLoader类加载器中获取类的addURL方法
Method method = null;
try {
method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
} catch (NoSuchMethodException | SecurityException e1) {
e1.printStackTrace();
}
assert method != null;
boolean accessible = method.isAccessible();
try {
if (!accessible) {
method.setAccessible(true);
}
URLClassLoader classLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();
method.invoke(classLoader, jarUrl);
} catch (Exception e) {
e.printStackTrace();
} finally {
method.setAccessible(accessible);
}
}
-C参数使用的类加载进行加载与user jar是一个类加载器,taskManager运行的时候会加载并且相应的job停止时自动卸载,同时每个job都会有自己的ClassLoader实现相互隔离。希望Flink 后续版能够在Rest API 层面能够直接提供参数来加载用户自定义的Jar包,并且能够直接读取HDFS文件来加载Jar包。
暂时将用户自定义的Jar包上传到hdfs并获取到hdfs的http连接地址,形式如下:
http://hadoop1:50070/webhdfs/v1/test.jar?op=OPEN&namenoderpcaddress=nnCluster&offset=0
对hdfs高可用场景,可以通过如下方式获取到active节点连接地址:
public static String getActiveHttpPath(Hdfs hdfsCon) throws IOException {
HdfsFileSystem hdfsFileSystem = SpringUtils.getBean(HdfsFileSystem.class);
InetSocketAddress active = HAUtil.getAddressOfActive(hdfsFileSystem.getHdfs(hdfsCon));
String httpPath = "http://" + active.getAddress().getHostAddress() + ":50075/webhdfs/v1";
return httpPath;
}