概览

PyFlink 是 Apache Flink 的 Python API,你可以使用它构建可扩展的批处理和流处理任务。根据你需要的抽象级别的不同,有两种不同的 API 可以在 PyFlink 中使用:

  • PyFlink Table API:使用类似于 SQL 或者在 Python 中处理表格数据的方式编写强大的关系查询
  • PyFlink DataStream API:允许对 Flink 的核心组件 state 和 time 进行细粒度的控制,以便构建更复杂的流处理应用

环境安装

环境要求:Python 3.7 +

环境设置:

  • 方法 1:通过软连接或创建 Python virtual env 的方式,将 python 指向 python3 解释器
  • 方法 2:配置 python.client.executable 指定编译作业时使用的 Python 解释器路径

PyFlink 安装:PyFlink 已经被发布到 PyPi,通过 pip 安装即可

$ python -m pip install apache-flink==1.18.1

调试

打印日志信息

客户端日志:在 PyFlink 作业中,在 Python UDF 之外的地方打印的上下文和调试信息均属于客户端日志。客户端日志可以通过 print 或标准 Python logging 模块打印;在提交作业时,日志信息会打印在客户端的日志文件中。需要注意的是,客户端的日志级别是 WARNING,只有日志级别在 WARNING 及以上的日志才会打印在客户端的日志文件中。

示例:打印客户端日志

from pyflink.table import EnvironmentSettings, TableEnvironment # 创建 TableEnvironment env_settings = EnvironmentSettings.in_streaming_mode() table_env = TableEnvironment.create(env_settings) table = table_env.from_elements([(1, 'Hi'), (2, 'Hello')]) # 使用 logging 模块 import logging logging.warning(table.get_schema()) # 使用 print 函数 print(table.get_schema())

服务端日志:在 PyFlink 作业中,在 Python UDF 中打印的上下文和调试信息均属于服务端日志。服务端日志可以通过 print 或标准 Python logging 模块打印。在作业运行的过程中,日志信息会打印在 TaskManager 的日志文件中。需要注意的是,服务端的日志级别是 INFO,只有日志级别在 INFO 及以上的日志信息才会打印在 TaskManager 的日志文件中。

查看日志:如果设置了环境变量 FLINK_HOME,日志将会放置在 FLINK_HOME 指向目录的 log 目录下。否则,日志将会放在安装的 PyFlink 模块的 log 目录下。可以通过执行下面的命令来查找 PyFlink 模块的 log 目录的路径:

$ python -c "import pyflink;import os;print(os.path.dirname(os.path.abspath(pyflink.__file__))+'/log')"
调试 Python UDF

本地调试:直接在 IDE 调试 Python 函数

远程调试:可以利用 PyCharm 提供的 pydevd_pycharm 工具进行 Python UDF 的调试

  1. 在 PyCharm 里创建一个 Python Remote Debug:run > Python Remote Debug > + > 选择一个 port
  2. 安装 pydevd-pycharm 工具:pip install pydevd-pycharm
  3. 在 Python UDF 中添加如下代码
import pydevd_pycharm
pydevd_pycharm.settrace('localhost', port=6789, stdoutToServer=True, stderrToServer=True)
  1. 启动刚刚创建的 Python Remote Debug Server
  2. 运行 Python 代码
Profile Python UDF

可以打开 profile 来分析性能瓶颈,打开 profile 后结果会打印在日志中:

t_env.get_config().set("python.profile.enabled", "true")

环境变量

有如下 2 个环境变量会影响 PyFlink 的行为:

  • FLINK_HOME:PyFlink 作业在提交之前会进行编译,它需要 Flink 的发行版来编译作业。PyFlink 的安装包中已经包含了 Flink 的发行版,默认情况下会使用它。此环境变量允许指定自定义的 Flink 发行版。
  • PYFLINK_CLIENT_EXECUTABLE:这个环境变量用于指定在通过 flink run 或编译 Java / Scala 任务中包含的 Python UDF 时使用的 Python 解释器的路径的,与 python.client.executable 配置相同。各配置的优先级如下(从高到低):在代码中配置的 python.client.executable,在环境变量中配置的 PYFLINK_CLIENT_EXECUTABLE,在 flink-conf.yaml 中配置的 python.client.executable。如果以上均没有配置,则 python 命令链接的 Python 解释器将会被使用。

常见问题

准备 Python 虚拟环境

可以在 Flink 仓库的如下路径下载便捷脚本:docs/static/downloads/setup-pyflink-virtual-env.sh

通过便捷脚本,可以指定 PyFlink 的版本,来生成对应的 PyFlink 版本所需的 Python 虚拟环境,否则将安装最新版本的 PyFlink 所对应的 Python 虚拟环境。便捷脚本执行命令如下:

$ sh setup-pyflink-virtual-env.sh 1.18.1

在代码中,可以通过如下方式使用便捷脚本生成的 Python 虚拟环境:

# 指定Python虚拟环境
table_env.add_python_archive("venv.zip")
# 指定用于执行python UDF workers (用户自定义函数工作者) 的python解释器的路径
table_env.get_config().set_python_executable("venv.zip/venv/bin/python")
添加 Jar 依赖

PyFlink 作业可能依赖 jar 文件,例如用到了 connector 或 Java UDF 等。您可以再提交作业时使用以下 Python Table API 或通过命令行参数来指定依赖项:

# 注意:仅支持本地文件URL(以"file:"开头)。
table_env.get_config().set("pipeline.jars", "file:///my/jar/path/connector.jar;file:///my/jar/path/udf.jar")

# 注意:路径必须指定协议(例如:文件——"file"),并且用户应确保在客户端和群集上都可以访问这些URL。
table_env.get_config().set("pipeline.classpaths", "file:///my/jar/path/connector.jar;file:///my/jar/path/udf.jar")
添加 Python 文件

可以试用命令行参数 pyfsadd_python_file 添加 Python 的文件依赖,这些依赖可以是 Python 文件,Python 包或本地目录。

示例:添加 Python 文件依赖并在代码逻辑中导入

myDir 目录及其下层结构如下:

myDir ├──utils ├──__init__.py ├──my_util.py

将目录 myDir 添加到 Python 依赖中并在代码中导入

table_env.add_python_file('myDir') def my_udf(): from utils import my_util

在 mini cluster 环境执行作业时,显式等待作业执行结束

在 mini cluster 环境执行作业(例如在 IDE 中执行作业)时,如果在作业中使用了如下 API:

  • TableEnvironment.execute_sql
  • StatementSet.execute
  • StreamExecutionEnvironment.execute_async

因为这些 API 是异步的,所以需要显式地等待作业执行结束。否则程序会在已提交的作业执行结束之前推出,以致于无法观测到已提交作业的执行结果。

示例:在使用异步 API 时显示等待作业执行结束

# 异步执行 SQL / Table API 作业 t_result = table_env.execute_sql(...) t_result.wait() # 异步执行 DataStream 作业 job_client = stream_execution_env.execute_async('My DataStream Job') job_client.get_job_execution_result().result()

当向远程集群提交作业时,无需显式地等待作业执行结束,所以当往远程集群提交作业之前,需要移除这些等待作业执行结束的代码逻辑。