Apache Beam 2.46.0 版本深度解析:RunInference 模型服务增强、I/O 能力扩展与破坏性变更指南
Apache Beam 2.46.0 版本深度解析:RunInference 模型服务增强、I/O 能力扩展与破坏性变更指南
Apache Beam 2.46.0 发布于 2023 年 3 月 10 日,是 Beam 统一批流处理编程模型在机器学习推理(RunInference)、I/O 生态与运行环境治理方面的一次重要迭代。本文以官方发布说明 beam-2.46.0.md 为骨架,结合当前仓库中的 Python/Java SDK 源码,系统梳理本版本的四大看点:Java 容器基础镜像迁移至 Eclipse Temurin、Python SDK RunInference 全面支持模型路径侧输入与 ONNX/TensorFlow 推理、全新的顶层 CSV/JSON 读写 Transform,以及 Spark 2 支持的正式移除。读者读完本文,将能准确评估升级影响、掌握 RunInference 动态模型更新与批量元素(BatchElements)行为调整的配置方法,并理解 Avro 模块迁移的完整路径。
版本概览:2.46.0 的核心主题
2.46.0 是 Beam 在 2023 年开年的首个正式版本,官方发布说明将其定位为"同时包含改进与新功能"(improvements and new functionality)。从变更内容看,本版本的技术主线集中在三个方面:
- ML 推理产品化:RunInference PTransform 获得模型路径侧输入(SideInput)支持,新增 ONNX Runtime 后端与 TensorFlow Model Handler,模型热更新能力通过
WatchFilePattern得到落地; - 数据读取体验统一:Python SDK 新增顶层
ReadFromCsv/WriteToCsv/ReadFromJson/WriteToJsonTransform,并对文本文件加入 LZMA 压缩编解码支持; - 运行环境与依赖治理:Java SDK 容器从已弃用的 OpenJDK 基础镜像迁移到 Eclipse Temurin,Java Avro 类从
beam-sdks-java-core迁往独立的beam-sdks-java-extensions-avro模块,Go SDK 构建要求提升至 Go 1.19。
需要特别提醒的是,本版本同时包含若干破坏性变更:Spark 2 的 SparkRunner 被移除,Python BatchElements 的默认批处理策略更激进(目标批时长从 1 秒提升到 10 秒)。计划升级到 2.46.0 的团队应优先评估这两处行为变化。
核心亮点一:Java SDK 容器迁移至 Eclipse Temurin
2.46.0 将 Java SDK 的容器基础镜像从已弃用的 OpenJDK 迁移到 Eclipse Temurin。这是一次由上游生态驱动的必要治理动作:OpenJDK 官方 Docker 镜像已停止维护,而 Temurin 是 Eclipse Adoptium 项目维护的高质量 OpenJDK 发行版。
迁移带来的直接变化是基础操作系统的切换:
- Eclipse Temurin 镜像基于 Ubuntu 22.04;
- 此前的 OpenJDK 镜像基于 Debian 11。
对用户而言,这意味着运行在 Beam Java SDK 容器中的 Pipeline(尤其是 Dataflow、Flink 等基于容器的 Runner)将自动获得更新的 glibc 与系统库,但同时也意味着任何依赖 Debian 专有包或旧系统库的 Pipeline 需要重新验证兼容性。升级后建议对自定义镜像或依赖系统级动态库的 UDF 做一轮回归测试。
核心亮点二:RunInference 机器学习推理能力全面增强
2.46.0 对 Python SDK 的 RunInference PTransform 做了大量投入,使其成为本版本最具技术含量的亮点。RunInference 用于在 Beam Pipeline 中对无界/有界数据流执行模型推理,其核心抽象是 ModelHandler(模型加载与推理逻辑的载体)与 KeyedModelHandler(按 Key 分模型的处理器)。
2.1 模型路径可作为 SideInput(Issue 24042)
本版本之前,模型路径通常作为 Pipeline 构造期的静态参数传入 ModelHandler。2.46.0 起,RunInference 允许将模型路径作为 SideInput 动态传入,配合文件模式监听,即可在 Pipeline 运行期间完成模型热更新,而无需重启作业。
实现这一能力的核心是新增的 WatchFilePattern Transform,其源码位于 sdks/python/apache_beam/ml/inference/utils.py:
class WatchFilePattern(beam.PTransform):
def __init__(
self,
file_pattern,
interval=360,
stop_timestamp=MAX_TIMESTAMP,
):
从源码可以确认其关键行为:
- 参数语义:
file_pattern支持本地路径与 GCSgs://路径,可含 glob 通配符(*、?、[...]);interval为轮询检查间隔(秒),默认 360 秒;stop_timestamp为停止检查的时间戳; - 实现链路:
expand内部依次串联MatchContinuously(产生无界 PCollection)、_GetLatestFileByTimeStamp(依据文件修改时间筛选出 Pipeline 启动后新增/更新的文件,并结合 state 记录已见最大时间戳)、_ConvertIterToSingleton(收敛为单元素侧输入)与全局窗口+处理时间触发器的重触发窗口化,从而输出ModelMetadata供 RunInference 消费; - 三条硬性约束(源码 docstring 中明确给出):
- 文件名不可复用——若某文件名曾被使用,后续即使更新内容也会被忽略,触发模型更新必须上传新文件名的文件;
- Pipeline 启动前,
file_pattern必须能匹配到至少一个已存在的文件; - 该 Transform 依赖
MatchContinuously的无界数据源,仅适用于流式模式,批处理模式下可能导致 Pipeline 卡住或产生非预期结果。
配套的单元测试位于 sdks/python/apache_beam/ml/inference/utils_test.py,覆盖了 WatchFilePatternTest 的核心行为。
2.2 ONNX Runtime 后端(Issue 22972)
2.46.0 为 Python SDK 的 RunInference 新增 ONNX Runtime 支持,对应实现文件为 sdks/python/apache_beam/ml/inference/onnx_inference.py,并配套集成测试 onnx_inference_it_test.py 与单元测试 onnx_inference_test.py。这意味着用户可以直接把 .onnx 格式模型接入 Beam Pipeline,借助 ONNX Runtime 在多种硬件后端(CPU/GPU 等)上执行推理,从而在 Beam 内部完成从数据预处理到模型打分的全链路。
2.3 TensorFlow Model Handler(Issue 25366)
同一版本还引入了 TensorFlow 模型的 ModelHandler 支持,使 TensorFlow SavedModel 等格式可以通过 RunInference 加载推理。这与 ONNX、PyTorch 处理器共同构成 2.46.0 的多框架推理矩阵。
2.4 PyTorch:TorchScript 模型加载(PR 25321)
在 PyTorch 侧,2.46.0 新增了对 TorchScript 模型的加载支持。当前仓库中的实现位于 sdks/python/apache_beam/ml/inference/pytorch_inference.py(PytorchModelHandlerTensor 等类),其构造参数中包含 torch_script_model_path,用法如下:
PytorchModelHandlerTensor(
torch_script_model_path="gs://your-bucket/model.pt")
源码中同时给出了两条约束(见 pytorch_inference.py):torch_script_model_path 与 state_dict_path 只能二选一,二者同时指定会抛出 ValueError;该路径既支持本地文件也支持 GCS 等 Beam FileSystems 可访问的 URI(源码通过 FileSystems.open 读取模型)。
2.5 实战组合:模型热更新 Pipeline
综合以上能力,一个典型的"流式推理 + 模型热更新"Pipeline 可以这样组织:
import apache_beam as beam
from apache_beam.ml.inference.base import RunInference
from apache_beam.ml.inference.utils import WatchFilePattern
from apache_beam.ml.inference.pytorch_inference import PytorchModelHandlerTensor
model_handler = PytorchModelHandlerTensor(torch_script_model_path="gs://bucket/models/init.pt")
with beam.Pipeline() as p:
model_updates = (
p
| WatchFilePattern(file_pattern="gs://bucket/models/*.pt", interval=60)
)
_ = (
p
| "ReadData" >> beam.io.ReadFromPubSub(topic="input")
| "RunInference" >> RunInference(model_handler, model_path=model_updates)
| "WriteResult" >> beam.io.WriteToBigQuery("dataset.predictions")
)
其中 WatchFilePattern 每 60 秒轮询一次模型目录,一旦出现文件名全新的 .pt 模型即作为 SideInput 推送给 RunInference,实现不中断作业的模型迭代。请务必遵守前文提到的"唯一文件名"约束与"流式模式"前提。
I/O 增强:JmsIO 重试、LZMA 压缩与顶层 CSV/JSON Transform
3.1 JmsIO 发布重试策略(Java,Issue 24971)
2.46.0 为 Java SDK 的 JmsIO(JMS 消息队列 I/O)新增了对发布失败(failed publications)的重试策略支持。这对于以 JMS 为中间层的生产型 Pipeline 意义重大:此前单次发布失败会直接导致元素处理失败,现在可以配置重试以吸收瞬时故障。JmsIO 实现位于 sdks/java/io 目录下,生产环境使用时应结合目标 MQ 的可用性合理设置重试次数与退避参数。
3.2 文本文件 LZMA 压缩/解压缩(Python,Issue 25316)
Python SDK 的文本 I/O 新增 LZMA 压缩格式支持。从源码看,压缩类型注册表位于 sdks/python/apache_beam/io/filesystem.py,其中定义了 LZMA = 'lzma' 类型、MIME 类型 application/lzma 与 .lzma 扩展名映射;底层编解码由 Python 标准库 lzma.LZMADecompressor / lzma.LZMACompressor 完成(见同文件 第 198、212 行),测试覆盖见 filesystem_test.py。这意味着 ReadFromText / WriteToText 现在可以直接读写 .lzma 后缀文件(如:beam.io.ReadFromText("gs://bucket/data/*.lzma")),无需用户手工解压。
3.3 顶层 ReadFrom/WriteTo Csv/Json Transform(Python)
2.46.0 将 CSV/JSON 读写提升为 Python SDK 的顶层 Transform,源码位于 sdks/python/apache_beam/io/textio.py,内部委托给 apache_beam.dataframe.io 的 ReadViaPandas / WriteViaPandas 实现,即底层使用 pandas 进行解析与序列化。
四个 Transform 的关键签名与参数(均来自源码 docstring):
| Transform | 核心参数 | 说明 |
|---|---|---|
ReadFromCsv(path, splittable=True, filename_column=None, **kwargs) |
splittable:CSV 是否可按行拆分(单条记录跨多行时应设为 False,会禁用 liquid sharding);filename_column:追加记录来源文件名的新列名 |
其余 kwargs 透传 pandas.read_csv |
WriteToCsv(path, num_shards=None, file_naming=None, **kwargs) |
num_shards:分片数,默认 None 由系统决定;file_naming:文件命名策略,默认 path-XXXXX-of-NNNNN |
其余 kwargs 透传 pandas.DataFrame.to_csv |
ReadFromJson(path, orient='records', lines=True, dtype=False, **kwargs) |
orient:JSON 元素格式,默认 records(对象列表);lines:是否逐行一条记录,默认 True;dtype:默认 False 不推断类型 |
其余 kwargs 透传 pandas.read_json |
WriteToJson(path, num_shards=None, file_naming=None, orient='records', lines=None, **kwargs) |
lines 默认随 orient == 'records' 自动取 True |
其余 kwargs 透传 pandas.DataFrame.to_json |
一个完整的读写示例:
import apache_beam as beam
with beam.Pipeline() as p:
records = (
p
| beam.io.ReadFromCsv("gs://bucket/input/*.csv", filename_column="source_file")
)
_ = (
records
| beam.io.WriteToJson("gs://bucket/output/result", num_shards=4)
)
注意:这些 Transform 依赖 pandas,若未安装 apache_beam<a href="https://link.gitcode.com/i/68d5451bffa5162dc19034f274a707c0" target="_blank">dataframe] 扩展,textio.py 会在导入时抛出 ImportError(源码 [第 1110-1117 行 的 fallback 分支)。单元测试见 textio_test.py,覆盖了 CSV 写入后回读的完整闭环。
新特性与改进:Runner 与多语言 SDK
4.1 Samza 便携模式 UDF 指标
2.46.0 为 Samza Runner 的便携(portable)模式新增 UDF 指标支持,使运行在 Samza 上的跨语言 Pipeline 也能上报并聚合用户自定义指标,补齐了 Samza 便携模式在可观测性方面的缺口。
4.2 SparkRunner:SDF 输出无需全部驻留内存(Issue 23852)
本版本为 SparkRunner 新增实验性选项 use_bounded_concurrent_output_for_sdf,用于避免 SDF(Splittable DoFn)输出必须全部装入内存的限制。开启方式是在 Pipeline 选项的 experiments 中加入该实验项:
PipelineOptions options = PipelineOptionsFactory.create();
options.as(SparkRunnerOptions.class).setExperiments(
Arrays.asList("use_bounded_concurrent_output_for_sdf"));
这对 ParquetIO 读取等产生大量中间输出的场景尤其有帮助,能以有界并发输出的方式降低内存压力。
4.3 Go SDK:Go 1.19 构建要求与 Prism Runner(PR 25545 / 24789)
Go SDK 在本版本有两项基础性变化:
- 构建要求提升:Go SDK 现在要求 Go 1.19 及以上版本才能编译(见 sdks/go/README.md 与 prepare_go_version.sh 等脚本所管理的版本工具链);
- Prism Runner 初版落地:Go SDK 新增了首个纯 Go 实现的便携 Beam Runner——Prism,其实现位于 sdks/go/pkg/beam/runners/prism。作为"原生 Go 的可移植 Runner",Prism 为在无 JVM 环境中运行 Beam 管道提供了轻量选项,适合本地开发调试与对部署体积敏感的场景(当前仓库 runners/prism/java 下亦有配套的 Java 侧组件)。
破坏性变更:升级前必须知晓的两处行为变化
5.1 Spark 2 的 SparkRunner 被移除(PR 25263)
自 2.41.0 起标记弃用的 Spark 2 版本 SparkRunner 在 2.46.0 被正式移除。仍在 Spark 2 集群上运行的团队必须升级到 Spark 3 并使用对应版本的 SparkRunner,否则无法继续使用 Beam Spark 运行后端。当前仓库的 Spark 集成代码位于 runners/spark 目录(含 Spark 3/4 版本子目录),可作为迁移后的对照参考。
5.2 Python BatchElements 批处理行为调整
BatchElements(用于将元素按批聚合以摊销下游固定开销的 Transform)在 2.46.0 中调整了批大小估计策略,其 Python 实现位于 sdks/python/apache_beam/transforms/util.py。具体变化:
- 默认目标批时长从 1 秒提升到 10 秒(源码构造参数默认值
target_batch_duration_secs=10,见 util.py 第 1076 行); - 固定开销(fixed cost)不再计入该时长计算,以更好地处理固定开销大于单秒的场景,避免批大小被单元素固定成本严重压低。
行为影响:某些场景下批处理会"更激进",即单批聚合更多元素、吞吐更高,但单个元素的端到端延迟可能增大。若希望恢复旧行为,显式传入:
from apache_beam.transforms.util import BatchElements
pcoll | BatchElements(target_batch_duration_secs_including_fixed_cost=1)
target_batch_duration_secs_including_fixed_cost 的语义为"含固定开销的每批目标总时长(秒)",源码会对非正值参数抛出 ValueError(见 util.py 第 532-543 行),其批大小估计逻辑按 fixed_cost + num_elements * per_element_cost 的成本模型线性插值(util.py 第 710-715 行)。相关测试见 util_test.py。
弃用与迁移指南:Avro 模块迁移(Issue 24749)
2.46.0 宣布 beam-sdks-java-core 中的 Avro 相关类正式弃用,并将在未来版本移除。迁移目标是新的独立模块 beam-sdks-java-extensions-avro:
- 包路径迁移:例如
org.apache.beam.sdk.coders.AvroCoder应改为org.apache.beam.sdk.extensions.avro.coders.AvroCoder; - 兼容性保证:为降低迁移成本,新模块中 Avro 类的相对包路径与整体类层级保持不变——只是把包前缀从
org.apache.beam.sdk换成org.apache.beam.sdk.extensions.avro,其余结构一一对应; - 落地验证:当前仓库中迁移后的类已存在,例如 AvroCoder.java。
迁移方式以 Gradle 项目为例,将依赖从 core 模块切换到 extensions 模块,并把 import 语句批量替换为 org.apache.beam.sdk.extensions.avro.* 即可。建议在升级到 2.46.0 时同步完成该迁移,避免未来版本移除旧类时阻塞升级。
贡献者与发布信息
2.46.0 的发布得到了社区广泛参与。根据 git shortlog 统计,本版本共有 70 余位贡献者参与,涵盖核心维护者(如 Danny McCormick、Robert Burke、Luke Cwik、Reuven Lax 等)与众多社区新面孔。发布下载信息可参阅官网下载页的 2.46.0 条目(2023-03-10),更详细的 issue 级变更清单可在官方发布里程碑中查阅。
总结:2.46.0 的升级评估清单
| 维度 | 变化 | 行动项 |
|---|---|---|
| Java 容器基础镜像 | OpenJDK(Debian 11) → Eclipse Temurin(Ubuntu 22.04) | 回归测试依赖系统库的 UDF |
| RunInference | 模型路径 SideInput、ONNX、TensorFlow、TorchScript | 流式场景可实施模型热更新 |
| Python 顶层 I/O | ReadFrom/WriteTo Csv/Json、LZMA 文本压缩 | 安装 apache_beam[dataframe] 后方可使用 CSV/JSON Transform |
| SparkRunner | Spark 2 移除;新增 use_bounded_concurrent_output_for_sdf 实验 |
Spark 2 集群需先迁移至 Spark 3 |
| BatchElements | 默认目标批时长 1s → 10s,固定开销不计入 | 需旧行为时传 target_batch_duration_secs_including_fixed_cost=1 |
| Go SDK | 构建要求 Go 1.19;Prism Runner 初版 | 升级本地 Go 工具链 |
| Avro | core 模块弃用,迁往 extensions-avro | 批量替换 import 包前缀 |
总体而言,2.46.0 是 Beam 在"ML 推理产品化"方向上迈出的一大步,同时通过容器治理与模块重构为长期可维护性奠基。建议所有用户重点验证 RunInference 相关新特性(尤其是 WatchFilePattern 的流式约束)与 BatchElements 行为变化对现有 Pipeline 吞吐/延迟的影响。