简介: 以 Flink 1.12 为例,介绍如何使用 Python 语言,通过 PyFlink API 来开发 Flink 作业。
Apache Flink 作为当前最流行的流批统一的计算引擎,在实时 ETL、事件处理、数据分析、CEP、实时机器学习等领域都有着广泛的应用。从 Flink 1.9 开始,Apache Flink 社区开始在原有的 Java、Scala、SQL 等编程语言的基础之上,提供对于 Python 语言的支持。经过 Flink 1.9 ~ 1.12 以及即将发布的 1.13 版本的多个版本的开发,目前 PyFlink API 的功能已经日趋完善,可以满足绝大多数情况下 Python 用户的需求。接下来,我们以 Flink 1.12 为例,介绍如何使用 Python 语言,通过 PyFlink API 来开发 Flink 作业。内容包括:
- 环境准备
- 作业开发
- 作业提交
- 问题排查
- 总结
环境准备
第一步:安装 Python
PyFlink 仅支持 Python 3.5+,您首先需要确认您的开发环境是否已安装了 Python 3.5+,如果没有的话,首先需要安装 Python 3.5+。
第二步:安装 JDK
我们知道 Flink 的运行时是使用 Java 语言开发的,所以为了执行 Flink 作业,您还需要安装 JDK。Flink 提供了对于 JDK 8 以及 JDK 11 的全面支持,您需要确认您的开发环境中是否已经安装了上述版本的 JDK,如果没有的话,首先需要安装 JDK。
第三步:安装 PyFlink
接下来需要安装 PyFlink,可以通过以下命令进行安装:
# 创建 Python 虚拟环境
python3 -m pip install virtualenv
virtualenv -p `which python3` venv
# 使用上述创建的 Python 虚拟环境
./venv/bin/activate
# 安装 PyFlink 1.12
python3 -m pip install apache-flink==1.12.2
作业开发
PyFlink Table API 作业
我们首先介绍一下如何开发 PyFlink Table API 作业。
■ 1)创建 TableEnvironment 对象
对于 Table API 作业来说,用户首先需要创建一个 TableEnvironment 对象。以下示例定义了一个 TableEnvironment 对象,使用该对象的定义的作业,运行在流模式,且使用 blink planner 执行。
env_settings = EnvironmentSettings.new_instance().in_streaming_mode().use_blink_planner().build()
t_env = StreamTableEnvironment.create(environment_settings=env_settings)
■ 2)配置作业的执行参数
可以通过以下方式,配置作业的执行参数。以下示例将作业的默认并发度设置为4。
t_env.get_config().get_configuration().set_string('parallelism.default', '4')
■ 3)创建数据源表
接下来,需要为作业创建一个数据源表。PyFlink 中提供了多种方式来定义数据源表。
方式一:from_elements
PyFlink 支持用户从一个给定列表,创建源表。以下示例定义了包含了 3 行数据的表:[("hello", 1), ("world", 2), ("flink", 3)],该表有 2 列,列名分别为 a 和 b,类型分别为 VARCHAR 和 BIGINT。
tab = t_env.from_elements([("hello", 1), ("world", 2), ("flink", 3)], ['a', 'b'])
说明:
- 这种方式通常用于测试阶段,可以快速地创建一个数据源表,验证作业逻辑
- from_elements 方法可以接收多个参数,其中第一个参数用于指定数据列表,列表中的每一个元素必须为 tuple 类型;第二个参数用于指定表的 schema
方式二:DDL
除此之外,数据也可以来自于一个外部的数据源。以下示例定义了一个名字为my_source,类型为 datagen 的表,表中有两个类型为 VARCHAR 的字段。
t_env.execute_sql("""
CREATE TABLE my_source (
a VARCHAR,
b VARCHAR
) WITH (
'connector' = 'datagen',
'number-of-rows' = '10'
)
""")
tab = t_env.from_path('my_source')
说明:
- 通过 DDL 的方式来定义数据源表是目前最推荐的方式,且所有 Java Table API & SQL 中支持的 connector,都可以通过 DDL 的方式,在 PyFlink Table API 作业中使用,详细的 connector 列表请参见 Flink 官方文档 [1]。
- 当前仅有部分 connector 的实现包含在 Flink 官方提供的发行包中,比如 FileSystem,DataGen、Print、BlackHole 等,大部分 connector 的实现当前没有包含在 Flink 官方提供的发行包中,比如 Kafka、ES 等。针对没有包含在 Flink 官方提供的发行包中的 connector,如果需要在 PyFlink 作业中使用,用户需要显式地指定相应 FAT JAR,比如针对 Kafka,需要使用 JAR 包 [2],JAR 包可以通过如下方式指定:
# 注意:file:///前缀不能省略
t_env.get_config().get_configuration().set_string("pipeline.jars", "file:///my/jar/path/flink-sql-connector-kafka_2.11-1.12.0.jar")
方式三:catalog
hive_catalog = HiveCatalog("hive_catalog")
t_env.register_catalog("hive_catalog", hive_catalog)
t_env.use_catalog("hive_catalog")
# 假设hive catalog中已经定义了一个名字为source_table的表
tab = t_env.from_path('source_table')
这种方式和 DDL 的方式类似,只不过表的定义事先已经注册到了 catalog 中了,不需要在作业中重新再定义一遍了。
■ 4)定义作业的计算逻辑
方式一:通过 Table API
得到 source 表之后,接下来就可以使用 Table API 中提供的各种操作,定义作业的计算逻辑,对表进行各种变换了,比如:
@udf(result_type=DataTypes.STRING())
def sub_string(s: str, begin: int, end: int):
return s[begin:end]
transformed_tab = tab.select(sub_string(col('a'), 2, 4))
方式二:通过 SQL 语句
除了可以使用 Table API 中提供的各种操作之外,也可以直接通过 SQL 语句来对表进行变换,比如上述逻辑,也可以通过 SQL 语句来实现:
t_env.create_temporary_function("sub_string", sub_string)
transformed_tab = t_env.sql_query("SELECT sub_string(a, 2, 4) FROM %s" % tab)
说明:
- TableEnvironment 中提供了多种方式用于执行 SQL 语句,其用途略有不同:
■ 5)查看执行计划
用户在开发或者调试作业的过程中,可能需要查看作业的执行计划,可以通过如下方式。
方式一:Table.explain
比如,当我们需要知道 transformed_tab 当前的执行计划时,可以执行:print(transformed_tab.explain()),可以得到如下输出:
== Abstract Syntax Tree ==
LogicalProject(EXPR$0=[sub_string($0, 2, 4)])
+- LogicalTableScan(table=[[default_catalog, default_database, Unregistered_TableSource_582508460, source: [PythonInputFormatTableSource(a)]]])
== Optimized Logical Plan ==
PythonCalc(select=[sub_string(a, 2, 4) AS EXPR$0])
+- LegacyTableSourceScan(table=[[default_catalog, default_database, Unregistered_TableSource_582508460, source: [PythonInputFormatTableSource(a)]]], fields=[a])
== Physical Execution Plan ==
Stage 1 : Data Source
content : Source: PythonInputFormatTableSource(a)
Stage 2 : Operator
content : SourceConversion(table=[default_catalog.default_database.Unregistered_TableSource_582508460, source: [PythonInputFormatTableSource(a)]], fields=[a])
ship_strategy : FORWARD
Stage 3 : Operator
content : StreamExecPythonCalc
ship_strategy : FORWARD
方式二:TableEnvironment.explain_sql
方式一适用于查看某一个 table 的执行计划,有时候并没有一个现成的 table 对象可用,比如:
print(t_env.explain_sql("INSERT INTO my_sink SELECT * FROM %s " % transformed_tab))
其执行计划如下所示:
== Abstract Syntax Tree ==
LogicalSink(table=[default_catalog.default_database.my_sink], fields=[EXPR$0])
+- LogicalProject(EXPR$0=[sub_string($0, 2, 4)])
+- LogicalTableScan(table=[[default_catalog, default_database, Unregistered_TableSource_1143388267, source: [PythonInputFo