概览
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 的调试
- 在 PyCharm 里创建一个 Python Remote Debug:
run
>Python Remote Debug
>+
> 选择一个 port - 安装
pydevd-pycharm
工具:pip install pydevd-pycharm
- 在 Python UDF 中添加如下代码
import pydevd_pycharm
pydevd_pycharm.settrace('localhost', port=6789, stdoutToServer=True, stderrToServer=True)
- 启动刚刚创建的 Python Remote Debug Server
- 运行 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 文件
可以试用命令行参数 pyfs
或 add_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()
当向远程集群提交作业时,无需显式地等待作业执行结束,所以当往远程集群提交作业之前,需要移除这些等待作业执行结束的代码逻辑。