官方文档对 pyflink 的使用说明不是很详细。本文主要参考了 孙金城 大佬的这篇文章
结合自己测试过程,有些地方做了修改,做一个记录
注意
:需要 Python 3.5+
如果已经有 apache-flink-*.dev0.tar.gz
二进制包,可以跳过这一步。
mvn clean install -DskipTests
会在 flink-python/dist 目录生成一个 apache-flink-*.dev0.tar.gz
二进制包,拷贝该包到需要安装 pyflink 的机器
本文只介绍 onyarn 模式的安装、部署
安装机器环境要求:
-i 指定国内 pip 源,安装会快一点
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple apache-flink-1.10.dev0.tar.gz
因为 yarn 集群上 python 环境不一定符合 flink 程序的版本要求,所以我们需要使用 virtualenv 打包 python 环境。
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple virtualenv
用 virtualenv 以 always-copy 方式建立一个全新的 Python 环境,这里定义名称为 venv:
virtualenv --always-copy venv
会在当前目录生成一个 venv 文件
拷贝原环境的 python 标准库到 venv
中:
VENV_PYTHON=venv/bin/python
DST=`$VENV_PYTHON -c "import os;import pip;print(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(pip.__file__)))))"`
SRC=`$VENV_PYTHON -c "import os;import contextlib;print(os.path.dirname(os.path.abspath(contextlib.__file__)))"`
if [ "$SRC" != "$DST" ]; then find "$SRC" -maxdepth 1 ! -name "site-packages" ! -name "__pycache__" ! -name "python3.*" -exec cp -r -- "{}" "${DST}" \; ; fi
使用 venv 中的 pip 安装 apache-beam
venv/bin/pip install -i https://pypi.tuna.tsinghua.edu.cn/simple apache-beam==2.15.0
把独立 python 环境打成 zip 包,目前打成的包比较大,有 90 多 MB
zip -r venv.zip venv
拷贝下面的代码到 pyflink_job.py 文件:
import logging
import os
import shutil
import sys
import tempfile
from pyflink.table import BatchTableEnvironment, EnvironmentSettings
from pyflink.table.descriptors import FileSystem, OldCsv, Schema
from pyflink.table.types import DataTypes
from pyflink.table.udf import udf
def word_count():
environment_settings = EnvironmentSettings.new_instance().in_batch_mode().use_blink_planner().build()
t_env = BatchTableEnvironment.create(environment_settings=environment_settings)
# register Results table in table environment
#tmp_dir = tempfile.gettempdir()
result_path = 'hdfs:///user/flink/python_result'
#if os.path.exists(result_path):
# try:
# if os.path.isfile(result_path):
# os.remove(result_path)
# else:
# shutil.rmtree(result_path)
# except OSError as e:
# logging.error("Error removing directory: %s - %s.", e.filename, e.strerror)
logging.info("Results directory: %s", result_path)
# we should set the Python verison here if `Python` not point
# t_env.get_config().set_python_executable("python3")
t_env.connect(FileSystem().path(result_path)) \
.with_format(OldCsv()
.field_delimiter(',')
.field("city", DataTypes.STRING())
.field("sales_volume", DataTypes.BIGINT())
.field("sales", DataTypes.BIGINT())) \
.with_schema(Schema()
.field("city", DataTypes.STRING())
.field("sales_volume", DataTypes.BIGINT())
.field("sales", DataTypes.BIGINT())) \
.create_temporary_table("Results")
@udf(input_types=DataTypes.STRING(), result_type=DataTypes.ARRAY(DataTypes.STRING()))
def split(input_str: str):
return input_str.split(",")
@udf(input_types=[DataTypes.ARRAY(DataTypes.STRING()), DataTypes.INT()], result_type=DataTypes.STRING())
def get(arr, index):
return arr[index]
t_env.register_function("split", split)
t_env.register_function("get", get)
t_env.get_config().get_configuration().set_string("parallelism.default", "1")
data = [("iPhone 11,30,5499,Beijing", ),
("iPhone 11 Pro,20,8699,Guangzhou", ),
("MacBook Pro,10,9999,Beijing", ),
("AirPods Pro,50,1999,Beijing", ),
("MacBook Pro,10,11499,Shanghai", ),
("iPhone 11,30,5999,Shanghai", ),
("iPhone 11 Pro,20,9999,Shenzhen", ),
("MacBook Pro,10,13899,Hangzhou", ),
("iPhone 11,10,6799,Beijing", ),
("MacBook Pro,10,18999,Beijing", ),
("iPhone 11 Pro,10,11799,Shenzhen", ),
("MacBook Pro,10,22199,Shanghai", ),
("AirPods Pro,40,1999,Shanghai", )]
t_env.from_elements(data, ["line"]) \
.select("split(line) as str_array") \
.select("get(str_array, 3) as city, "
"get(str_array, 1).cast(LONG) as count, "
"get(str_array, 2).cast(LONG) as unit_price") \
.select("city, count, count * unit_price as total_price") \
.group_by("city") \
.select("city, "
"sum(count) as sales_volume, "
"sum(total_price) as sales") \
.insert_into("Results")
t_env.execute("word_count")
if __name__ == '__main__':
logging.basicConfig(stream=sys.stdout, level=logging.INFO, format="%(message)s")
word_count()
执行以下命令,以 per-job 模式提交:
bin/flink run -m yarn-cluster -pyarch venv.zip -pyexec venv.zip/venv/bin/python -py pyflink_job.py
参数说明:
参数 | 说明 |
---|---|
-pyarch/–pyArchives venv.zip | 将当前目录下的 venv.zip 上传到yarn集群. -pyarch/--pyArchives 可以上传独立 python 环境 或者数据文件 ,并且只能是 zip 压缩格式。flink 将指定的 zip 文件将被解压到 python UDF worker 的工作目录,默认解压目录名和 zip 压缩包名相同,可以通过# 指定解压的目录名,例如 -pyarch data.zip#data1 将被解压为 data1。还可以使用 , 分隔指定多个 zip 压缩包,例如:-pyarch file:///tmp/py37.zip,file:///tmp/data.zip#data ,然后可以通过 -pyexec 指定 python UDF worker 的 python 路径,例如 -pyexec py37.zip/py37/bin/python ;python UDF worker 可以通过 f = open('data/data.txt', 'r') 方式获取数据文件 |
-pyexec/–pyExecutable venv.zip/venv/bin/Python | 指定venv.zip中的Python解释器来执行Python UDF,路径需要和zip包内部结构一致。 |
-py/–python pyflink_job.py | 程序入口 |
flink 还提供了一些其他参数:
参数 | 说明 |
---|---|
-pyfs,–pyFiles
|
为作业添加自定义的 python 文件,这些文件会被添加到 python UDF woker 的 PYTHONPATH 环境变量,标准 python resource 文件后缀如 .py/.egg/.zip 或者目录都是支持的,可以使用 , 分隔上传多个文件,例如 --pyFiles file:///tmp/myresource.zip,hdfs:///$namenode_address/myresource2.zip |
-pym,–pyModule
|
指定 python 入口文件的 Python module,必须和 --pyFiles 一起使用 |
-pyreq,–pyRequirements
|
指定一个 requirements.txt 文件,用来安装第三方依赖,这些依赖会被添加到 python UDF worker 的 PYTHONPATH 环境变量中。用户可以选择的将这些依赖放到一个目录中(python UDF worker 就可以直接用了)用 # 指定要上传的目录。例如 --pyRequirements file:///tmp/requirements.txt#file:///tmp/cached_dir |
本文参考
https://enjoyment.cool/2020/01/02/Apache-Flink-%E8%AF%B4%E9%81%93%E7%B3%BB%E5%88%97-PyFlink-%E4%BD%9C%E4%B8%9A%E7%9A%84%E5%A4%9A%E7%A7%8D%E9%83%A8%E7%BD%B2%E6%A8%A1%E5%BC%8F/#more
https://enjoyment.cool/2019/12/05/Apache-Flink-%E8%AF%B4%E9%81%93%E7%B3%BB%E5%88%97-%E5%A6%82%E4%BD%95%E5%9C%A8PyFlink-1-10%E4%B8%AD%E8%87%AA%E5%AE%9A%E4%B9%89Python-UDF
更多内容请关注 大神博客