【问题标题】:Using Python user defined function in a Java Flink Job在 Java Flink 作业中使用 Python 用户定义函数
【发布时间】:2020-10-24 18:37:16
【问题描述】:

是否有在 Java Flink 作业中使用 python 用户定义的函数,或者无论如何要通信,例如 flink 与 java 完成的转换结果与 python 用户定义的函数来应用一些机器学习的东西:

我知道从 pyFlink 你可以做这样的事情:

table_env.register_java_function("hash_code", "my.java.function.HashCode")

但我需要做类似的事情,但要从 java 添加 python 函数,或者如何将 java 转换的结果直接传递给 Python UDF Flink 作业?

我希望这些问题不要太疯狂,但我需要知道是否存在以某种方式将 Flink DataStream API 与以 Java 作为主要语言的 Python Table API 进行通信?这意味着我需要从 Java 做: Source -> Transformations -> Sink,但是其中一些转换可以触发 Python 函数,或者 Python 函数将等待一些 Java 转换完成以对 Stream 结果执行某些操作。

我希望有人能理解我在这里想要做什么。

亲切的问候!

【问题讨论】:

    标签: java python apache-flink flink-cep pyflink


    【解决方案1】:

    在 Flink 1.10 中添加了对 Python UDF(用户定义的函数)的支持——参见 PyFlink: Introducing Python Support for UDFs in Flink's Table API。例如,您可以这样做:

    add = udf(lambda i, j: i + j, [DataTypes.BIGINT(), DataTypes.BIGINT()], DataTypes.BIGINT())
    table_env.register_function("add", add)
    my_table.select("add(a, b)")
    

    有关更多示例等,请参阅上面链接的博客文章或stable documentation

    在 Flink 1.11(预计下周发布)中,增加了对矢量化 Python UDF 的支持,带来了与 Pandas、Numpy 等的互操作性。此版本还包括在 SQL DDL 和 SQL 客户端中对 Python UDF 的支持。有关文档,请参阅the master docs

    听起来您想从 Java 调用 Python。 Stateful Functions API 更完整地支持这一点——请参阅remote functions。但是要从 Java DataStream API 调用 Python,我认为您唯一的选择是使用 Flink 1.11 中添加的 SQL DDL 支持。请参阅FLIP-106the docs

    FLIP-106 有这个例子:

    ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment();
    BatchTableEnvironment tEnv = BatchTableEnvironment.create(env);
    tEnv.getConfig().getConfiguration().setString("python.files", "/home/my/test1.py");
    tEnv.getConfig().getConfiguration().setString("python.client.executable", "python3");
    
    tEnv.sqlUpdate("create temporary system function func1 as 'test1.func1' language python");
    Table table = tEnv.fromDataSet(env.fromElements("1", "2", "3")).as("str").select("func1(str)");
    tEnv.toDataSet(table, String.class).collect();
    

    您应该能够将其转换为使用 DataStream API。

    【讨论】:

    • 我现在有两个主要问题。 1. 这是否意味着 Table API Python 支持(版本 1.10 Flink)是为创建独立的 flink 作业(source->transformation->sink)而设计的,而不是以某种方式将其集成为 java flink 作业的一部分? 2. 如果第一个语句是 True,那么同时运行的两个流式 flink 作业通信的最佳方式是什么(Java 一个处理事件并对其进行转换,Python 一个使用 ML 模型预测结果)
    • 1.是的,PyFlink(在 1.10 中)是关于创建整个工作的。 2. 我会使用有状态函数(在这种情况下,python 部分将作为单独部署的 Web 服务运行),或者我会使用 Kafka 之类的东西来解耦 java 和 python 作业。
    • 再次感谢大卫。我会看看这些选项。亲切的问候。
    • 嗨,大卫。我终于安装了 flink 1.11.0 来使用这个 Java-Python 集成。这是我的想法:DataStream -> Keyedby(stream)->map(x -> x.id) 我想调用 Pyhton 函数并将结果发送到此示例,请参阅一些代码:##fsTableEnv is a StreamTableEnvironment as显示在您的 FLIP-106 中有这个例子: Table table = fsTableEnv.fromDataStream(stream).as("str").select("func1(str)");得到这个错误:java.lang.IllegalStateException:实例化python函数'test.func1'失败知道吗?亲切的问候!
    • 我自己没试过。但是看代码,这似乎意味着要么flink找不到org.apache.flink.client.python.PythonFunctionFactory,要么就是找不到python函数。
    【解决方案2】:

    此集成的示例: 在你的 pom.xml 中需要这个依赖,假设 Flink 1.11 是当前版本。

    <dependency>
      <groupId>org.apache.flink</groupId>
      <artifactId>flink-table-planner-blink_2.11</artifactId>
      <version>1.11.2</version>
      <scope>provided</scope>
    </dependency>
    

    创建环境:

    private StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
    
    private StreamTableEnvironment tableEnv = getTableAPIEnv(env);
    
    /*this SingleOutputStreamOperator will contains the result of the consumption from the  defined source*/
    private SingleOutputStreamOperator<Event> stream; 
    
    
    public static StreamTableEnvironment getTableAPIEnv(StreamExecutionEnvironment env) {
            final StreamTableEnvironment tableEnv = StreamTableEnvironment.create(env);
            tableEnv.getConfig().getConfiguration().setString("python.files", path/function.py);
            tableEnv.getConfig().getConfiguration().setString("python.client.executable", path/python);
            tableEnv.getConfig().getConfiguration().setString("python.executable", path/python);
            tableEnv.getConfig().getConfiguration().setString("taskmanager.memory.task.off-heap.size", "79mb");
    /*pass here the function.py and the name of the function into the python script*/
            tableEnv.executeSql("CREATE TEMPORARY SYSTEM FUNCTION FunctionName AS 'function.FunctionName' LANGUAGE PYTHON");
            return tableEnv;
        }
    
    

    从你想要做的转换开始,例如:

    SingleOutputStreamOperator<EventProfile> profiles = createUserProfile(stream.keyBy(k -> k.id));
    
    /*The result of that ProcessFunction `createUserProfile()` will be sent into the Python function to update some values of the profile and return them back into a defined function in Flink with Java: map function for example*/
    profiles = turnIntoTable(profiles).map((MapFunction<Row, EventProfile>) x -> {
      /*you custom code here to do the mapping*/
    });
    profiles.addSink(new yourCustomSinkFunction());
    
    /*this function will process the Event and create the EventProfile class for this example but you can also use another operators (map, flatMap, etc)*/
     private SingleOutputStreamOperator<EventProfile> createUserProfile(KeyedStream<Event, String> stream) {
            return stream.process(new UserProfileProcessFunction());
        }
    
    
    /*This function will receive a SingleOutputStreamOperator and sent each record to the Python function trough the TableAPI and returns a Row of String(you can change the Row type) that will be mapped back into EventProfile class*/
    @FunctionHint(output = @DataTypeHint("ROW<a STRING>"))
    private DataStream<Row> turnIntoTable(SingleOutputStreamOperator<EventProfile> rowInput) {
            Table events = tableEnv.fromDataStream(rowInput,
                    $("id"), $("noOfHits"), $("timestamp"))
                    .select("FunctionName(id, noOfHits, timestamp)");
            return tableEnv.toAppendStream(events, Row.class);
        }
    
    

    最后

    env.execute("Job Name");
    

    function.py脚本中调用FunctionName的python函数示例:

    @udf(
        input_types=[
            DataTypes.STRING(), DataTypes.INT(), DataTypes.TIMESTAMP(precision=3)
        ],
        result_type=DataTypes.STRING()
    )
    def FunctionName(id, noOfHits, timestamp):
        # function code here
        return f"{id}|{noOfHits}|{timestamp}"
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-11-13
      • 1970-01-01
      • 2017-09-09
      • 2014-11-27
      • 2023-03-15
      • 2015-03-15
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多