PyFlink从入门到精通

975 阅读16分钟

PyFlink简介

什么是PyFlink

PyFlink是一个可以让我们用Python语言编写Flink程序的库。它提供了与Flink Java/Scala API等价的Python API。

PyFlink的主要特征包括:

  • 支持Flink的核心API和运行时,可以访问和利用Flink的所有功能
  • 提供批处理(DataSet)和流处理(DataStream)两种编程抽象
  • 基于pandas DataFrame API实现的批处理支持
  • 基于asyncio的异步流式处理
  • Python用户自定义函数(UDF)支持
  • 机器学习库(TensorFlow/PyTorch/scikit-learn)的集成

PyFlink使得Python用户可以非常方便地开发Flink数据分析程序,不需要掌握Java或Scala即可构建复杂的流批任务。它降低了编程复杂度,提供更直观的编程体验。

总体而言,PyFlink充分发挥了Python的简洁和Flink的并行计算优势,为数据工程师和数据科学家提供了一个更易用、更富表现力的大数据分析框架。它使Python和Flink两者的优点得以完美结合。

PyFlink的特性及优势

PyFlink作为Flink的Python API,具有以下主要特性和优势:

  1. 完全兼容Flink:PyFlink支持Flink所有核心API和运行时特性,用户可以无缝使用PyFlink访问Flink的功能。
  2. 流批一体:同时支持流处理和批处理,提供统一的编程模型。
  3. 基于pandas的批处理:批处理支持基于用户熟悉的pandas DataFrame API。
  4. 异步流处理:基于asyncio实现的异步数据流处理。
  5. Python UDF:支持使用Python自定义函数,扩展计算能力。
  6. 机器学习集成:提供与TensorFlow、PyTorch等的深度集成。
  7. 部署灵活:支持在YARN、K8s、Standalone等环境部署。
  8. 性能优越:通过代码生成和优化,批处理性能可与Java版媲美。
  9. 简化编程:大大降低编程复杂度,提高工作效率。
  10. 开发调试方便:支持Jupyter Notebook,有助于交互式开发。

综上,PyFlink兼具Python的简洁优雅和Flink的性能优势,是构建流批一体分析应用的极佳选择。它可以大幅降低开发和使用门槛,提升数据分析效率。

PyFlink vs Flink Java/Scala API对比

这里我简要对比一下PyFlink和Flink的Java/Scala API:

  • 编程语言:PyFlink使用Python;Flink Java API使用Java;Flink Scala API使用Scala。
  • 编程范式:PyFlink支持面向对象和函数式编程;Java API是面向对象的;Scala API支持函数式编程。
  • 代码风格:PyFlink代码简洁明了;Java API代码相对更繁琐;Scala API代码较为简洁。
  • 批处理支持:PyFlink建立在Pandas之上,直接操作DataFrame;Java和Scala API通过DataSet API处理批数据。
  • 流处理支持:所有三者都通过DataStream API进行流处理。
  • 用户自定义函数:PyFlink通过Python函数实现UDF;Java和Scala通过继承接口实现UDF。
  • 可维护性:PyFlink的动态类型特性增加了灵活性,较易维护扩展。
  • 性能:PyFlink通过代码生成约等于Java性能;略优于Scala。
  • 可移植性:Java和Scala API可直接移植到大数据系统如Spark;PyFlink仅可在Flink环境下运行。
  • 学习曲线:PyFlink对Python用户更友好,降低了学习难度。

总体来说,PyFlink集Python和Flink优点于一体,是更易用和富有表现力的Flink API。

环境配置

安装PyFlink

使用conda安装PyFlink的步骤如下:

  1. 创建并激活一个conda环境:
conda create -n pyflink python=3.10
conda activate pyflink
  1. 配置conda以便搜索anaconda cloud上的PyFlink包:
conda config --add channels conda-forge
  1. 搜索可用的PyFlink版本:
conda search pyflink 
  1. 选择需要的PyFlink版本并安装:
# 例如安装最新版本
conda install pyflink=1.17.1
  1. 验证是否安装成功:
import pyflink
print(pyflink.__version__)

这样通过conda就可以方便快捷地安装PyFlink环境,并管理多个版本。

conda也能自动处理PyFlink所依赖的其他包,如py4j、pandas等。非常适合用于数据科学、机器学习项目中。

创建PyFlink环境

这里是在Python中创建PyFlink执行环境的常见方式:

1. 创建批处理环境

from pyflink.dataset import ExecutionEnvironment

env = ExecutionEnvironment.get_execution_environment()

2. 创建流处理环境

from pyflink.datastream import StreamExecutionEnvironment

env = StreamExecutionEnvironment.get_execution_environment()

3. 设置执行模式

# 本地执行
env.set_runtime_mode(RuntimeExecutionMode.LOCAL) 

# 集群执行 
env.set_runtime_mode(RuntimeExecutionMode.CLUSTER)

4. 注册函数

env.register_function("my_udf", udf)

5. 配置参数

env.get_config().set_parallelism(16)

通过创建执行环境,可以启动PyFlink作业的运行,并进行必要的配置。

PyFlink基础概念

Execution Environment

在PyFlink中,Execution Environment(执行环境)是所有Flink程序的基础入口点。它负责建立程序的上下文环境并负责执行程序。

PyFlink中创建Execution Environment的常用方式有两种:

  1. 创建一个批处理执行环境:
env = ExecutionEnvironment.get_execution_environment()
env.set_runtime_mode(RuntimeExecutionMode.BATCH)
  1. 创建一个流处理执行环境:
# 默认是流执行环境
env = StreamExecutionEnvironment.get_execution_environment()
# 也可以直接指定
env.set_runtime_mode(RuntimeExecutionMode.STREAMING)

Execution Environment提供了下列主要功能:

  • 并行执行:设置默认的并行度,以并行方式执行函数。
  • 配置:管理执行参数,如重新启动策略、临时文件夹等。
  • 连接外部系统:通过连接器或格式描述符与文件、 Kafka等外部系统交互。
  • 指定运行环境:是在IDE直接运行还是提交到集群运行。
  • 检查点配置:配置Checkpoint参数。
  • 注册函数:在环境中注册用户自定义函数。
  • 执行程序:最终执行批处理或流处理程序。

需要运行Flink Python程序时,第一步都是创建Execution Environment,这为后续编写实际的处理逻辑打下基础。

DataSet和DataStream

在PyFlink中,DataSet和DataStream是核心的编程抽象,用于构建批处理和流处理程序。

  1. DataSet

DataSet代表一个由各种数据组成的批量数据集合,用于构建批处理程序。常见操作包括:

  • 从文件、数据库等源创建DataSet
  • 对DataSet进行映射、过滤、聚合等转换操作
  • 将DataSet写入文件、数据库等外部系统
  • 通过connect()或join()将多个DataSet连接
  • 对DataSet进行分组(groupBy)和聚合(aggregate)
  • 对DataSet排序(sortPartition)
    • 主要API:
    • from_elements() 创建DataSet
    • map() flat_map() 映射转换
    • filter() 过滤
    • reduce() aggregate() 聚合
    • join() 连接两个DataSet
    • co_group() 按分组连接
    • sort_partition() 排序

使用DataSet可以方便地对批量数据进行ETL和分析。

  1. DataStream

DataStream表示一个包含连续数据的流,用于构建流处理程序。常见操作包括:

  • 从socket、kafka等创建流数据源

  • 对DataStream进行各种转换操作

  • 定义时间语义,处理事件时间

  • 对流应用状态进行检查点配置

  • 将流输出到文件系统、kafka等外部系统

  • 通过connect、join等操作连接流

  • 对流进行窗口化操作

  • 主要API:

    • from_elements() 创建DataStream
    • map() flat_map() 映射转换
    • filter() 过滤
    • key_by() 分流
    • window() 滚动/滑动窗口
    • process() sink_to() 输出sink

使用DataStream可以构建实时的流处理和分析应用。

DataSet和DataStream为PyFlink提供了统一的批流处理编程抽象和API。

函数和操作符

在PyFlink中,函数和操作符是编程的基本构建块,用于实现数据转换逻辑。

  1. 函数

PyFlink支持使用Python编写函数,常见类型包括:

  • 源函数(SourceFunction):产生数据的函数
  • 映射函数(MapFunction):一对一转换函数
  • 扁平映射函数(FlatMapFunction):一对多转换函数
  • 过滤函数(FilterFunction):过滤函数
  • 减少函数(ReduceFunction):聚合函数
  • 用户定义函数(UDF):自定义业务逻辑函数

所有这些函数可以方便地在PyFlink程序中被使用。

  1. 操作符

PyFlink提供了各种预定义的转换操作符,包括:

  • map() / flat_map():一对一/一对多映射
  • filter():过滤
  • reduce() / aggregate():聚合
  • join() / connect():连接
  • union():合并
  • window():窗口

通过组合使用这些操作符,可以实现批流程序的复杂数据处理需求。

函数和操作符为PyFlink提供了丰富的编程模型,可实现自定义和灵活的数据处理。它们是开发PyFlink程序的基础。

数据类型和序列化

在 PyFlink中,支持以下主要的数据类型和序列化:

数据类型

  • 基本类型:String、Boolean、Numeric等
  • 数组类型:ArrayType
  • 行类型:RowType
  • 复合类型:TupleType、MapType等

序列化

Flink需要对数据进行序列化和反序列化。PyFlink支持多种序列化方式:

  • Pickle序列化:默认方式,可序列化大部分Python对象
  • JSON序列化:通过json库进行JSON编码
  • Avro序列化:需要注册Avro序列化schema
  • Arrow序列化:支持跟Pandas交换Arrow格式数据

数据转换

PyFlink提供了方便的数据类型转换:

  • to_array() 将集合转换为数组
  • to_dict() 将Row转换为字典
  • to_json() 序列化对象为JSON

灵活使用这些类型转换可以简化代码。

注意正确设置序列化,避免数据解析错误。 Pickle序列化对Python对象支持广,但不够高效。 Avro和Arrow支持的数据类型更受限制,但性能更好。

PyFlink编程模型

批处理编程

读取和写入数据

在PyFlink中,读取和写入批处理数据有以下常用方式:

读取数据

  • 从集合创建DataSet:
ds = env.from_elements([123])  
  • 从文件读取:TextFile格式、Csv等
ds = env.read_text('/path/to/file')
  • 从数据库读取:通过JDBC连接器
ds = env.from_jdbc(...)

写入数据

  • 写入到文件系统:
ds.write_csv('/path/to/file'
  • 输出到标准输出:
print(ds)
  • 写入到数据库:通过JDBC连接器
ds.write_jdbc(...)
  • 写入到数据汇表:通过connect_sink接口
ds.connect_sink(...)  

通过这些机制,可以访问各种外部系统的数据源,也可以将处理结果输出到不同目标系统。

转换操作

在PyFlink中,常见的批处理DataSet转换操作包括:

  • map / flat_map: 元素级变换
ds.map(lambda x: x * 2)
  • filter: 过滤元素
ds.filter(lambda x: x > 5)
  • reduce / aggregate: 数据聚合
ds.reduce(lambda x, y: x + y)
  • join / co_group: 连接两个数据集
ds1.join(ds2).where(1).equal_to(2
  • union: 合并多个数据集
ds1.union(ds2)
  • partitionBy / sortPartition: 分区和排序
ds.partition_by_hash('id').sort_partition('score', Order.DESCENDING)
  • with_broadcast_set: 广播小数据集
ds.with_broadcast_set('dim_table', dim_ds)

通过组合这些转换操作,可以实现复杂的业务逻辑和数据处理流程。

分组和聚合

在PyFlink中,可以通过 groupby 和 aggregate 操作对批处理数据进行分组和聚合。

分组 GroupBy

按指定键对数据集进行分组:

grouped_ds = ds.group_by('key'

聚合 Aggregate

在分组上进行聚合运算:

grouped_ds.aggregate(AggregateFunction)  

常用的聚合函数包括:

  • Sum
  • Min
  • Max
  • Count
  • Average
  • 以及自定义聚合函数

示例:

# 按学生分组,计算每组学生的平均分数  
ds.group_by('student_id')
   .aggregate(StudentAverage('score'))

groupBy 和 aggregate 提供了对批数据集进行聚合统计和得出指标的能力,是批处理的重要操作。

流处理编程

定义源

在 PyFlink中定义流处理程序的数据源,常见的方式包括:

  1. 从集合创建流:
stream = env.from_collection([123])
  1. 从文件读取流:
stream = env.read_text('file_path'
  1. 从kafka读取流:
stream = env.add_source(FlinkKafkaConsumer(topics='topic', deserialization_schema=DeserializationSchema(), properties={}))
  1. 自定义源函数:
class MySource(SourceFunction):
  def run(self):
    ...

stream = env.add_source(MySource())  
  1. 从数据流生成流:
stream1 = ...
stream2 = stream1.filter(...)  

按数据源区分,PyFlink支持集合、文件、Kafka等外部流源,也可以自定义源函数实现自定义逻辑。

转换操作

在PyFlink中,常见的流处理DataStream转换操作包括:

  • map / flat_map: 元素级变换操作
stream.map(lambda x: x * 2)
  • filter: 过滤流中的元素
stream.filter(lambda x: x > 5)  
  • key_by: 将流进行分区
stream.key_by(lambda x: x[0]
  • reduce: 在流上进行Reduce聚合操作
stream.key_by(...).reduce(lambda x, y: x + y)
  • connect / union: 合并流
stream1.connect(stream2)
  • window: 基于时间或计数的窗口聚合
stream.key_by(...).window(TumblingWindow)
  • process: 自定义处理流的过程函数
stream.process(MyProcessFunction())

通过组合这些转换操作,可以在流上进行复杂的Events Driven 应用处理。

输出结果

在PyFlink中,常见的流处理结果输出方式包括:

  • 打印输出:
stream.print()
  • 写出到文件:
stream.write_text('/path/to/file')
  • 写出到Kafka:
stream.add_sink(FlinkKafkaProducer('topic'))
  • 写出到数据库:
stream.add_sink(JdbcSink()) 
  • 自定义Sink:
class MySink(SinkFunction):
  def invoke(self, value):
    ...

stream.add_sink(MySink())
  • 写出到动态表:
stream.execute_insert('output_table')
  • 写出到数据汇:
stream.add_sink(Converters.to_data_stream(collector))

PyFlink支持将流处理结果输出到外部存储、消息系统或通过自定义Sink函数输出到其他目标系统。

PyFlink Table API和SQL

创建表

在PyFlink中,可以通过Table API和SQL来创建表。

Table API

使用table_environment.from_elements从数据集合创建表:

from pyflink.table import TableEnvironment

env = TableEnvironment.create(...)  

table = env.from_elements([(1'John'), (2'Peter')])

或者从文件、数据库等外部系统读取数据创建表。

SQL

使用CREATE TABLE语句创建表:

CREATE TABLE MyTable (
  id BIGINT,
  name STRING
) WITH (
  'connector' = 'filesystem',
  'path' = '/path/to/file'
);

注册表

无论是使用Table API还是SQL创建的表,都需要在环境中注册以便后续查询:

env.create_temporary_table('MyTable'table)
CREATE TEMPORARY TABLE MyTable ...

Table API和SQL为PyFlink提供了声明式的表处理和分析能力。

查询表

在PyFlink中,可以通过Table API和SQL来查询表中的数据。

Table API

通过table对象查询:

result = table.select(table.id, table.name).filter(table.id > 1)

SQL

通过sql_query方法查询:

result = env.sql_query("SELECT id, name FROM MyTable WHERE id > 1")

或者在SQL CLI环境中交互查询:

Flink SQL> SELECT * FROM MyTable

结果处理

查询结果可以进一步转换或输出:

result.to_pandas() # 转为 Pandas DataFrame

result.execute_insert('output_table'# 插入到表中

Table API提供了面向对象的查询方式,SQL提供了声明式的查询方式。结合两者可以实现复杂的表分析和处理。

关联和聚合

在PyFlink中,Table API和SQL都支持对表进行联结和聚合查询。

关联查询

Table API:

result = table1.join(table2).where(table1.id == table2.id)

SQL:

SELECT * 
FROM table1 
JOIN table2 
ON table1.id = table2.id

支持INNER JOIN、LEFT JOIN、RIGHT JOIN等。

聚合查询

Table API:

result = table.group_by(table.key).select(table.key, table.value.avg)

SQL:

SELECT key, AVG(value)
FROM table 
GROUP BY key

支持聚合函数 COUNT、SUM、AVG、MAX、MIN等。

Table API 和 SQL为PyFlink提供了统一的表处理查询能力,可以混合使用。

部署和运行

本地执行

使用PyFlink进行本地部署运行的基本步骤如下:

  1. 安装PyFlink

使用pip或conda安装PyFlink及其依赖:

pip install apache-flink
  1. 编写PyFlink程序

编写批处理或流处理程序代码,并设置执行环境。

  1. 启动本地Flink集群

启动standalone session集群作为运行时环境:

./bin/start-cluster.sh
  1. 提交PyFlink程序

设置执行环境为本地集群后提交程序:

env.set_runtime_mode(execution_mode.cluster) 
env.execute("pyflink job")
  1. 监控执行

通过Flink UI监控作业运行情况:

http://localhost:8081
  1. 停止集群

作业结束后关闭本地standalone集群:

./bin/stop-cluster.sh

这提供了一个基本的PyFlink本地测试、调试、运行的工作流程。也可以部署到其他集群环境中进行生产运行。

提交到Standalone、YARN等集群

PyFlink程序可以部署到Standalone、YARN等不同的集群环境中进行生产运行。

Standalone集群

  1. 启动Flink Standalone集群
  2. 在执行环境中设置运行模式为集群:
env.set_runtime_mode(execution_mode.cluster)
  1. 设置JAR包位置:
env.add_jars("myprogram.jar"
  1. 提交PyFlink作业,会提交到Standalone集群

YARN集群

  1. 启动YARN集群
  2. 设置执行环境为YARN模式
  3. 设置JAR包位置
  4. 设置其他YARN配置(队列等)
  5. 提交PyFlink作业到YARN集群运行

Kubernetes Session集群

启动Flink K8s Session集群后,部署方式与Standalone类似。

PyFlink可以与主流集群资源管理器无缝集成,支持弹性的生产环境部署。

调试和测试

日志和监控

PyFlink提供了日志和指标监控的功能,可以帮助观察和诊断作业运行情况。

日志

PyFlink内置log4j日志框架,支持控制台打印和文件输出。

设置日志级别:

import logging
logging.info("test logging")

指标监控

PyFlink收集了丰富的指标,可以在Web UI中查看,也可以配置推送到Prometheus。

常用指标:

  • startTime: 作业启动时间
  • numRestarts: 故障重启次数
  • numBytesInLocal: 本地输入数据量
  • numBytesOutRemote:远程输出数据量
  • numLateRecordsDropped:迟到数据丢弃数

Web UI

Flink Web UI显示了作业度量指标、检查点信息、异常等监控数据。

外部系统

还可以使用ELK栈进行日志分析,Prometheus等系统存储指标数据。

PyFlink的监控功能可以提供作业运行状态的全方位视图。

测试数据

PyFlink提供了一些内置的测试数据,可以用来调试和演示程序,主要包括:

集合数据

直接使用from_elements创建包含内置数据的表或流:

env.from_elements([(1, 'Hi'), (2, 'Hello')])

文件数据

从内置的文件读取测试数据:

  • words.txt:常用英文单词
  • numbers.txt:随机数
  • logs.txt:模拟日志

例如:

env.read_text('words.txt')

生成数据

使用生成函数创建测试数据:

  • generate_sequence(from, to)
  • generate_random_bounded_string(len, chars)

例如:

ds = env.generate_sequence(1100000)

连接器数据

一些连接器提供了获取测试数据的方法:

  • kafka_get_test_data_set
  • es_get_test_data_set

等等

PyFlink提供了丰富的测试数据源,可以方便编写和调试程序。

模块化和组件测试

在PyFlink中可以通过以下方式进行模块化和组件测试:

  1. 单元测试

使用unittest等框架,基于PyFlink的组件接口编写单元测试用例,例如对自定义函数进行独立测试。

  1. 集成测试环境
  • LocalEnvironment - 在本地设置一个模拟运行环境进行集成测试。
  • RemoteEnvironment - 连接远程集群集成测试。
  1. 模拟数据源

使用TestSourceContext并行读取内存数据,测试流组件 chains。

  1. 测试Sink

使用TestSink验证和检查执行结果。

  1. 本地执行

在不启动整个集群的情况下,通过本地执行模式测试组件串联执行。

  1. 集群模拟

MiniClusterWithClient可以在本地启动一个最小化的集群用于测试。

  1. CI框架

结合GitHub Actions, Jenkins等实现自动化测试。

通过这些策略,可以对PyFlink程序进行全面的测试与验证,提高质量。

最佳实践

性能优化

这里我总结一下在Flink中的常见性能优化方案:

  1. 优化检查点配置
  • 调大checkpoint间隔
  • 使用INCREMENTAL模式
  • 优化State Backend
  1. 调优Task参数
  • 适当增大task slots提高并行度
  • 优化slot与CPU核心绑定
  1. 数据倾斜优化
  • rebalance
  • rescale
  • shuffle优化
  1. 提高网络传输效率
  • 减少数据shuffle
  • 开启backpressure
  1. 运行时通信优化
  • 减少小消息通信
  • 批量发送与接收
  1. 减少序列化开销
  • 使用Avro、Protobuf等优化序列化
  1. 算法调优与代码生成
  • 选择高效算法
  • 开启代码生成
  1. 优化状态后端
  • RocksDB参数优化
  • 更换更快的状态后端

多方面综合调优可以显著提升Flink大规模生产环境下的执行效率。

常见问题及解决方案

Flink应用运行中常见的问题及解决方案包括:

  • 数据倾斜:通过rebalance等方式缓解数据分布不均
  • 任务堆积:增加task slots数,扩大资源规模
  • 内存溢出:适当调低并行度,增加资源容量
  • 检查点失败:简化状态结构,优化检查点配置
  • 序列化错误:检查数据格式匹配情况,必要时自定义序列化器
  • 网络IO问题:扩大网络带宽,优化数据传输
  • 结果错误:检查业务逻辑,增加打印和assert校验
  • 死锁:检查看是否存在竞争资源
  • 性能下降:调优Task参数,提高并行度等
  • 高重启次数:优化故障转移配置,减少转移
  • 资源竞争:隔离不同job,指定slot资源比例

通过日志打印和指标监控,可以定位问题原因,并根据类型采取不同的优化手段进行故障排除和性能提升。

# PyFlink学习代码
https://github.com/huanglaoxie0503/flink-basic-introduction