1. 为什么在 Flink 里用 Avro?
Avro 的优势主要体现在三点:
- Schema 驱动:数据自描述(或由外部 schema 管理),便于跨语言、跨团队协作
- 体积小、性能好:二进制编码更省带宽、解析效率更高
- 演进友好:字段可新增/可兼容演进(配合 schema registry 更强)
Flink 的好处在于:它的序列化框架能够很好地处理 Avro Schema 生成的类,你可以像操作普通 POJO 一样 keyBy / groupBy / join。
2. 依赖与环境准备
2.1 Java / Scala 工程依赖(Maven)
只要引入 Flink 的 avro 模块:
xml
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-avro</artifactId>
<version>2.2.0</version>
</dependency>
2.2 PyFlink 使用 Avro:需要额外 JAR
PyFlink 本身运行在 JVM 上,连接器/格式能力都来自 JAR,所以你需要把 Avro 相关 JAR 加到作业依赖中。
常见方式:
- Table API:
pipeline.jars - DataStream API:
env.add_jars("file:///...")
示例(Table API):
python
t_env.get_config().set(
"pipeline.jars",
"file:///path/to/flink-avro-2.2.0.jar"
)
生产建议:把依赖打成 fat jar 或在集群侧做统一分发,避免"本地能跑、集群找不到类"。
3. Java DataStream:用 AvroInputFormat 读取 Avro 文件
3.1 基于 Avro 生成类(推荐)
假设你有 Avro schema 生成的 POJO:User.class。
java
AvroInputFormat<User> users = new AvroInputFormat<User>(in, User.class);
DataStream<User> usersDS = env.createInput(users);
3.2 Avro 生成类可以直接 keyBy 字段名
Flink 支持对 POJO 字段做字符串 key 选择:
java
usersDS.keyBy("name");
这对做分区、聚合非常方便。
4. 不推荐 GenericData.Record:为什么慢?
文档里特别强调:GenericData.Record 能用,但不推荐。原因是:
- Record 通常会携带完整 schema 信息
- 对象更"重",序列化/反序列化成本更高
- 性能和内存通常不如生成类(SpecificRecord / POJO)
结论:能生成类就生成类;必须动态 schema 时再考虑 Generic Record。
5. Avro Schema 写法的"隐蔽坑":UNION 单类型会生成 Object
这是最容易踩的坑之一,而且会直接影响你能不能拿这个字段做 key/join/group。
5.1 正常写法(生成正确类型)
json
{"name": "type_double_test", "type": "double"}
生成字段类型为 double,可用于 key/join/group。
5.2 坑:UNION 只有一个分支,会生成 Object
json
{"name": "type_double_test", "type": ["double"]}
很多人以为这等价于 "double",但生成类字段很可能变成 Object。
后果:
- Flink 的 POJO 字段选择依赖明确类型
- 字段是
Object时,不能作为 join/group key(语义不明确、序列化也不友好)
5.3 正确的可空写法(允许 null + 类型)
json
{"name": "type_double_test", "type": ["null", "double"]}
这个是 Avro 常规 nullable 类型写法,生成字段类型可控,Flink 也更容易处理。
一句话:不要写 ["double"] 这种单元素 union;要么写 "double",要么写 ["null","double"]。
6. PyFlink:用 AvroSchema + AvroInputFormat 读取 Avro 文件
PyFlink 下通常不直接使用 Java 生成类,而是通过 schema 解析 Avro 文件,读出来的元素是 原生 Python 对象(dict 风格)。
示例(与你提供的内容一致):
python
from pyflink.datastream import StreamExecutionEnvironment
from pyflink.datastream.formats.avro import AvroInputFormat, AvroSchema
AVRO_FILE_PATH = "/path/to/user.avro"
schema = AvroSchema.parse_string("""
{
"type": "record",
"name": "User",
"fields": [
{"name": "name", "type": "string"},
{"name": "favoriteNumber", "type": ["int", "null"]},
{"name": "favoriteColor", "type": ["string", "null"]}
]
}
""")
env = StreamExecutionEnvironment.get_execution_environment()
ds = env.create_input(AvroInputFormat(AVRO_FILE_PATH, schema))
def json_dumps(record):
import json
return json.dumps(record)
ds.map(json_dumps).print()
env.execute()
关键点:
- schema 必须和文件里的 Avro schema 兼容
- 读出来是 Python 对象,后续你可以轻松转 JSON、做 map/filter、写入下游
7. 生产实践建议
7.1 统一 schema 管理(强烈建议)
如果你们有多条链路/多团队协作:
- 用 Schema Registry 管理 Avro schema(演进、兼容策略更可控)
- 在 Flink 作业里只拉取 schema id/版本
7.2 字段类型要"稳定"
尤其是要做 keyBy / join / groupBy 的字段:
- 避免 union 单类型生成 Object
- 可空就用
["null", "type"] - 避免频繁变更字段类型(比如 int→string)
7.3 PyFlink 依赖别忘了带 JAR
PyFlink 里 Avro "不是 pip 装一下就完事",它需要 JVM 侧的 format jar。
- 本地跑 OK,提交集群报 ClassNotFound 是最常见事故之一
8. 总结
- Java/Scala 侧:引入
flink-avro,用AvroInputFormat<User>读文件,POJO 支持keyBy("field") - PyFlink 侧:准备 Avro schema,用
AvroSchema + AvroInputFormat读取,得到 Python 原生对象 - 最大坑:
["double"]这种单元素 union 会让生成类字段变成Object,导致 Flink 不能拿它做 join/group key - 生产建议:schema 演进要规范、依赖要打包、关键字段类型要稳定