1.11 flink中的动态加载udf jar包

背景

项目中想要把flink做到平台化,只需要编辑sql便能把任务跑起来,开发过程中遇到一个问题,就是如何能够自动的加载自定义的函数包,因为项目中已经把main打包成一个通用的jar, 使用时只需要把sql信息用参数形式传入就可以. 但是如果sql中需要使用到udf,那么就需要实现flink的动态加载jar

先说结论

  • 在通用的jar main中通过反射使用类加载器,加载对应的jar包
  • 通过反射设置StreamExecutionEnvironment中的configuration的confData中的pipeline.classpaths

具体代码例子如下

public static void main(final String[] args) throws Exception {
	StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
	EnvironmentSettings settings = EnvironmentSettings.newInstance().useBlinkPlanner().inStreamingMode().build();
	StreamTableEnvironment tableEnvironment = StreamTableEnvironment.create(env, settings);


    String path = "https://...est/template-core-0.0.1-shaded.jar";
	loadJar(new URL(path));

	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);
	List<String> jarList = new ArrayList<>();
	jarList.add(path);
	temp.put("pipeline.classpaths",jarList);

	tableEnvironment.executeSql("CREATE FUNCTION ReturnSelf2 AS 'flinksql.function.udf.ReturnSelf2'");
	tableEnvironment.executeSql("CREATE TABLE sourceTable (\n" +
			" f_sequence INT,\n" +
			" f_random INT,\n" +
			" f_random_str STRING,\n" +
			" ts AS localtimestamp,\n" +
			" WATERMARK FOR ts AS ts\n" +
			") WITH (\n" +
			" 'connector' = 'datagen',\n" +
			" 'rows-per-second'='5',\n" +
			"\n" +
			" 'fields.f_sequence.kind'='sequence',\n" +
			" 'fields.f_sequence.start'='1',\n" +
			" 'fields.f_sequence.end'='1000',\n" +
			"\n" +
			" 'fields.f_random.min'='1',\n" +
			" 'fields.f_random.max'='1000',\n" +
			"\n" +
			" 'fields.f_random_str.length'='10'\n" +
			")");
	tableEnvironment.executeSql("CREATE TABLE sinktable (\n" +
			"    f_random_str STRING" +
			") WITH (\n" +
			"    'connector' = 'print'\n" +
			")");
	tableEnvironment.executeSql(
			"insert into sinktable " +
				"select ReturnSelf2(f_random_str) " +
				"from sourceTable");
}
//动态加载Jar
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();
	}
	// 获取方法的访问权限
	boolean accessible = method.isAccessible();
	try {
		//修改访问权限为可写
		if (accessible == false) {
			method.setAccessible(true);
		}
		// 获取系统类加载器
		URLClassLoader classLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();
		//jar路径加入到系统url路径里
		method.invoke(classLoader, jarUrl);
	} catch (Exception e) {
		e.printStackTrace();
	} finally {
		method.setAccessible(accessible);
	}
}

再说解决过程

idea中loadjar的尝试

首先是loadJar方法是需要,在idea中,只是用了loadJar是可以加载外部udf包的,正常运行,但是当我打包放到集群上,则会提是找不到对应的class,猜测是因为idea开发中,启动的是一个minicluster, 所以我在idea中启动的时候, main调用loadJar方法, 把udf加载到minicluster JVM进程中, JM跟TM都是在同个JVM进程中运行的, 所以是可以正常启动. 但是如果是在集群环境, 即使是local standalone , 执行jps命令可以发现是JM跟TM是不同的进程的

所以是main即使在JM中 加载了,但是TM进程中没有加载

-C 参数的发现

后面发现flink命令行启动的时候可以添加-C参数, 可以指定其他的jar文件

执行命令

flink --help

 所以可以使用如下命令, 我的jar是上传到阿里云的oss上使用的

flink run -C "https://oss-cn-hangzhou.aliyuncs.com/test/cxytest/tanwan-function-0.1.jar..."  -c cn.xuying.flink.stream.SimpleTest /usr/local/soft/flink/flink-1.0-SNAPSHOT.jar

但是我们项目平台是调用了集群的restapi来管理job的,而flink的restapi对应的接口是没有提供-C的之类的参数的, 也在flink中文邮件列表提了对应的问题,不过没有一个很好的答案

含泪跑源码pipeline.classpaths的发现

中途大佬的参与,发现了flink 客户端跑flinkjob的一些细节,

checkout源码,install,然后可以修改测试代码

找到测试的job打断点

运行test方法,可以发现env的里面的属性中多了-C参数url

然后我们不使用CliFrontendRunTest, 而是直接的执行那个TestJob的main函数, 断点可以发现没有了-C的配置

所以推测, flink 的client主要是解析你的命令行, 加一些参数配置, 然后执行你编写的flink main函数中的代码, 所以我们可以在我们自己的job代码中通过反射强制加入我们自己需要-C的配置 , 如文章开头的代码例子所示

 

  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Flink提供了动态jar的功能,可以在运行时动态地添和删除jar,以便在不停止Flink作业的情况下更新代码。 具体实现步骤如下: 1. 创建一个插件目录,用于存放所有的可jar。在Flink的配置文件flink-conf.yaml以下配置: ``` plugin.dir: /path/to/plugin/dir ``` 2. 在Flink作业使用Flink自带的ClassLoader,插件目录jar。可以使用以下代码实现: ``` final List<URL> jars = new ArrayList<>(); final File pluginDir = new File("/path/to/plugin/dir"); if (pluginDir.isDirectory()) { for (final File file : pluginDir.listFiles()) { if (file.getName().endsWith(".jar")) { jars.add(file.toURI().toURL()); } } } final URL[] urls = jars.toArray(new URL[0]); final ClassLoader pluginClassLoader = new URLClassLoader(urls, getClass().getClassLoader()); Thread.currentThread().setContextClassLoader(pluginClassLoader); ``` 3. 在代码使用反射机制动态类。例如,如果要一个名为"com.example.MyClass"的类,可以使用以下代码实现: ``` final Class<?> clazz = pluginClassLoader.loadClass("com.example.MyClass"); final Object instance = clazz.newInstance(); ``` 4. 添动态jarFlink作业。可以使用以下命令实现: ``` ./bin/flink run -p 1 -c com.example.MyJob /path/to/MyJob.jar -Dflink.plugins.dir=/path/to/plugin/dir ``` 以上是实现动态jar的基本步骤,具体实现可以根据自己的需求进行调整。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值