流处理的前世今生(十二)抽象的代价,谷歌嫡传Apache Beam
发布时间:2025-10-01 03:12 浏览量:1
我们在之前介绍Spark的时候提到过,Google在大数据领域贡献了两篇核心论文《The Google File System》,《MapReduce: Simplified Data Processing on Large Clusters》 奠定了大数据处理的理论基础并直接促成了Hadoop的诞生。其实在流数据计算领域,Google也是鼻祖,其中的学术论文包含:
FlumeJava: Easy, Efficient Data-Parallel PipelinesMillWheel: Fault-Tolerant Stream Processing at Internet ScaleThe Dataflow Model: A Practical Approach to Balancing Correctness, Latency, and Cost in Massive-Scale, Unbounded, Out-of-Order Data ProcessingMapReduce 虽然简化了数据并行处理,但现实中的复杂计算往往需要多个 MapReduce 阶段的管道(pipeline)。手工编写和管理这些管道存在诸多困难:
需要编写大量协调代码来连接各个 MapReduce 阶段需要管理中间结果的创建和删除逻辑计算被低级细节掩盖,难以理解和修改管道划分固化在代码中,难以演化PCollection:不可变的分布式数据集合抽象PTable:键值对的多重映射抽象统一的并行操作:parallelDo、groupByKey、combineValues、flatten 等原语这些抽象隐藏了数据表示细节(内存、文件、数据库)和执行策略(本地循环、远程 MapReduce)。
记录操作及其参数,构建内部执行计划图(DAG)用户调用 FlumeJava.run 时才真正执行在执行前对整个计划进行优化这使得优化器能够看到完整的计算逻辑,进行全局优化。
论文中最重要的技术贡献之一,包括多种优化策略:
ParallelDo 融合(Fusion):
生产者-消费者融合:将 f 和 g∘f 合并为单个操作兄弟融合:多个读取相同输入的操作合并为单次遍历MSCR 操作(MapShuffleCombineReduce):
这是论文的关键创新,是介于 FlumeJava 抽象和 MapReduce 之间的中间层支持 M 个输入通道和 R 个输出通道每个输出通道可以是"分组"通道(grouping)或"直通"通道(pass-through)一个 MSCR 可以实现为单个 MapReduce,但表达能力更强论文通过多个基准测试(Ads Logs、SiteData、IndexStats、Build Logs)证明FlumeJava的性能表现:
FlumeJava 优化后的性能接近手工优化的 MapReduce比模块化的 MapReduce 实现快得多代码量比原始 MapReduce 减少 30-40%FlumeJava 论文的核心思想直接影响了:
Apache Beam 的设计(PCollection、PTransform 等概念)统一批流处理的理念声明式 API + 自动优化的模式可移植性抽象层的设计论文证明了通过高层抽象和自动优化,可以在不牺牲性能的前提下极大提升数据管道开发的生产力。这种"编写一次、优化执行"的理念成为现代数据处理框架的标准范式。
MillWheel: Fault-Tolerant Stream Processing at Internet ScaleMillWheel是 Google 在 2013 年发表的重要流处理系统论文,作者包括 Tyler Akidau(后来成为 Apache Beam 的核心贡献者, 现在是Redpanda的CTO)等人。这篇论文奠定了现代流处理系统的许多核心概念,直接影响了 Apache Beam 的设计。
流处理系统在 Google 需要满足三个关键需求:
容错性 - 在数千台机器上运行,任何机器都可能随时故障持久状态 - 基于模型的系统(如异常检测)依赖于数周数据生成的预测可扩展性 - 系统扩展不应导致运维成本成比例增加当时的系统(Yahoo S4、Twitter Storm、Microsoft Sonora等)无法提供这三者的完美组合。
MillWheel 最重要的第一个创新是关于逻辑时间(Logical Time)与低水位线(Low Watermarks)的概念
低水位线= 最小值(最旧未完成工作, 所有上游计算的低水位线)
MillWheel 实现真正的精确一次处理(Exactly Once),无需用户编写复杂的去重或回滚逻辑,它是这样实现的:
记录唯一 ID - 在生产时为所有记录分配唯一 ID原子检查点 - 记录 ID 与状态修改在同一原子写入中检查点化去重 - 使用布隆过滤器快速路径 + 存储层精确验证重试语义 - 交付重试直到 ACK,满足至少一次要求(精确一次的前提)所有由记录处理产生的内部更新都按键原子检查点化, 记录精确交付一次(此保证不扩展到外部系统)
MillWheel提出强生产(Strong Productions)与弱生产(Weak Productions)的概念。
强生产:
在交付前检查点化生产的记录与状态修改在同一原子写入中完成使用户应用逻辑成为幂等操作防止重复窗口聚合等问题弱生产(可选优化):
在持久化状态前乐观广播下游交付通过选择性检查点化滞后生产来改善端到端延迟适用于已经幂等的计算(如无状态过滤器)MillWheel有两种计时器(Timers)系统:挂钟时间计时器:用于定时任务(如每小时推送邮件),低水位线计时器:用于基于数据完整性的聚合(如窗口聚合),来保证:
按时间戳递增顺序触发持久化到存储,可在进程重启和机器故障后存活与输入记录相同的精确一次保证MillWheel采用有向计算图(Directed Computation Graph)。
编程模型:
class Computation {void ProcessRecord(Record data); // 记录到达时触发void ProcessTimer(Timer timer); // 计时器触发时调用void SetTimer(string tag, int64 time);void ProduceRecord(Record data, string stream);StateType MutablePersistentState;}数据模型:
输入/输出表示为 (key, value, timestamp) 三元组键提取函数由每个消费者指定用户代码在特定键的上下文中运行按键串行化处理,不同键可并行处理Apache Beam 的窗口化和水位线语义Google Cloud Dataflow 的 (key, value, event-time, window) 四元组模型流处理标准模式:精确一次处理 + 低水位线 + 计时器事件时间 vs 处理时间的明确区分MillWheel 证明了在互联网规模下实现低延迟 + 强一致性 + 容错性的可行性,为现代流处理系统建立了设计范式。Tyler Akidau 等人后来将这些概念推广到 Apache Beam,使其成为开源社区的标准。
The Dataflow Model: A Practical Approach to Balancing Correctness, Latency, and Cost in Massive-Scale, Unbounded, Out-of-Order Data Processing这篇论文提出了Google Cloud Dataflow的处理模型,该模型基于FlumeJava和MillWheel技术。
该论文主要内容包含:
统一的批流处理模型论文提出了一个单一统一模型,可以支持批处理、微批处理和流处理三种执行引擎,将执行引擎的选择变成基于延迟和资源成本的实际考量,而不是语义上的限制 。事件时间(Event Time)处理
模型允许基于事件实际发生的时间进行计算,而不是数据被处理的时间,这对于处理无序、延迟到达的数据至关重要。非对齐窗口支持(Unaligned Windows)
模型支持非对齐的事件时间窗口,特别是会话窗口(Sessions),这些窗口不是跨整个数据源应用,而是只应用于数据的特定子集(如每个用户)。灵活的触发器机制(Triggers)
触发器决定何时在处理时间中输出窗口结果。系统提供了多种预定义触发器实现,包括基于水位线的完成度估计触发、基于处理时间的触发、基于数据到达的触发等,并支持逻辑组合。增量处理与撤回(Incremental Processing & Retraction)
模型提供了三种细化模式:丢弃模式(Discarding)、累积模式(Accumulating)和累积撤回模式(Accumulating & Retracting),使得系统能够随时间产生多个结果窗格(panes),并处理数据的更新和撤回。四维分解框架模型将管道实现分解为四个相关维度:What(计算什么结果)、Where(在事件时间的哪里计算)、When(在处理时间的何时输出)、How(早期结果如何与后续改进相关) 。
这篇论文对现代流处理系统产生了深远影响,其思想被Apache Beam等开源项目采纳,成为大数据处理领域的重要理论基础。
基于这三篇论文,Apache Beam诞生了。2016 年 1 月,Google 与 Cloudera 和 dataArtisans 合作,贡献了完整的 Google Cloud Dataflow SDK,包括 Java 实现、本地运行器、I/O 连接器和核心编程模型。此举旨在将管道定义与执行引擎解耦,并建立更广泛的生态系统采用。向 Apache 基金会的捐赠Apache Beam这个项目。
Apache Beam 被迅速应用于各个行业中。
LinkedIn 运营着已知最大的 Beam 部署,通过 3,000 多个管道每天处理 4 万亿事件,为 9.5 亿多名会员提供服务。包含实时职位推荐、反滥用检测和会员活动处理,实现亚秒级延迟,同时实现了2 倍成本优化。
HSBC 部署 Beam 用于蒙特卡罗模拟的定量风险分析,实现了 100 倍扩展和 XVA(信用风险)管道2 倍更快的性能。每天处理 5-10 TB,每秒 5,000 个事件,管理20,000 多个 机器学习特征,管道正常运行时间达 99%(从 80% 提高)。
http://Booking.com 的全球广告竞价基础设施支持每月 100 万次以上查询,跨越 2 PB+ 分析数据,实现了36 倍的处理加速。瑞士市场集团的在线市场 Ricardo 使用 Beam 进行实时欺诈检测和产品分类,处理效率显著提高。
Palo Alto Networks 每天处理数千亿安全事件,实现了60% 的成本降低,同时为威胁检测保持高性能、低延迟处理。
Apache Beam 提供多语言 SDK 支持,Java、Python 和 Go 。Java SDK 提供最完整的功能集,具有广泛的 I/O 连接器和高级功能。Python SDK 已显著增长,但与 Java 相比保持一些限制(更少的 I/O 转换,缺少 Min/Max 等聚合)。Go SDK 提供基本功能,正在积极开发以实现功能对等。
核心编程模型围绕三个基本概念:Pipelines(端到端数据处理作业)、PCollections(不可变的分布式数据集合)和 PTransforms(数据转换操作)。这种抽象使开发人员能够以数据流而不是分布式系统复杂性的角度思考。
WordCount 示例:
with beam.Pipeline(options=pipeline_options) as pipeline:(pipeline| 'Read' >> beam.io.ReadFromText(input_pattern)| 'Split' >> beam.FlatMap(lambda line: re.findall(r"[a-zA-Z']+", line))| 'PairWithOne' >> beam.Map(lambda word: (word, 1))| 'GroupAndSum' >> beam.CombinePerKey(sum)| 'Write' >> beam.io.WriteToText(output_prefix))Apache Beam 的架构围绕复杂的抽象模型,将管道定义与执行分离,同时在批处理和流处理中保持统一语义。
核心架构概念构成所有 Beam 应用程序的基础。Pipelines 代表数据和计算的有向无环图(DAG),由用户通过 SDK 构建,并由运行器转换为特定于执行引擎的 API。PCollections 作为基本数据抽象——不可变、分布式、类型化的集合,可以是有界的(固定大小)或无界的(随时间增长)。
PTransforms 代表所有数据处理操作,从核心并行处理(ParDo)到分组(GroupByKey)、聚合(Combine)和连接(CoGroupByKey)。高级转换包括用于检查点的 Splittable DoFn、用于按键状态管理的 Stateful DoFn 和用于可重用功能的 Composite Transforms。
"编写一次、随处运行"模型通过多个抽象层实现可移植性:SDK 层(特定于语言的 API)、模型层(通用执行语义)、运行器层(特定于引擎的转换)和可移植性层(通过 gRPC 协议的跨语言执行)。
运行器架构支持跨不同引擎执行。 Direct Runner 提供本地执行以进行测试,而生产运行器包括 Google Cloud Dataflow(具有自动扩展的完全托管)、Apache Flink Runner(流优先运行时)和 Apache Spark Runner(RDD/Dataset 集成)。每个运行器实现图优化,包括融合优化、组合器提升、侧输入广播和动态工作重新平衡。
高级流概念将 Beam 与更简单的框架区分开来。窗口化将无界数据流细分为有限窗口(固定、滑动、会话、全局)以进行聚合。水位线为数据完整性提供时间估计,支持正确处理迟到的数据。触发器根据事件时间、处理时间或数据驱动条件确定何时发出结果。
状态和计时器支持复杂的事件处理场景。状态 API 提供按键、按窗口的可变存储(ValueState、BagState、MapState),而计时器 API 支持基于事件时间或处理时间的延迟处理回调。这些功能支持复杂的流应用程序,如会话分析和复杂聚合。
容错性依赖于基于捆绑包的恢复,其中元素失败会导致整个捆绑包重试。这种耦合的故障处理保留了融合优化优势,同时最小化持久状态要求。不同的运行器通过各种机制实现容错——Dataflow 使用托管检查点,Flink 采用分布式快照,Spark 利用 RDD 血统。
好了,关键的问题来了,Apache Beam还需要Spark或者Flink,来作为运行时,那为什么不直接用Flink或者Spark呢,Beam带来了什么,这一层抽象是不是多此一举呢?
Apache Beam带来了以下的好处:
1. 编程模型的统一性和可移植性
避免供应商锁定:用Beam编写的代码可以在不同运行时之间切换(Flink、Spark、Google Cloud Dataflow等),而无需重写业务逻辑技术栈演进的灵活性:当新的处理引擎出现或现有引擎性能改进时,可以轻松迁移而不改变代码混合部署:可以在开发环境用DirectRunner测试,生产环境用Flink,云端用Dataflow2. 更高层次的抽象
Beam提供了比Flink/Spark更统一和直观的概念模型:
四维分解框架(来自Dataflow Model论文):
What: 计算逻辑(通过Transform定义)Where: 窗口策略(事件时间窗口)When: 触发器(何时输出结果)How: 累积模式(结果如何更新)相比之下:
Spark Streaming: 主要基于微批处理,事件时间处理相对复杂Flink: 虽然事件时间支持很好,但API设计与Spark差异较大3. 更好的事件时间语义
// Beam的会话窗口代码(简洁直观)input.apply(Window.into(Sessions.withGapDuration(Duration.standardMinutes(30))).triggering(AfterWatermark.pastEndOfWindow).withAllowedLateness(Duration.standardHours(1)).accumulatingFiredPanes).apply(Sum.integersPerKey);同样的逻辑在Flink/Spark中需要更多样板代码,且在不同引擎间写法完全不同。
4. 统一的批流处理
Beam: 批处理和流处理使用完全相同的API,只是数据源不同Flink: 虽然也支持批流统一,但API仍有差异(DataStream vs DataSet,虽然新版本在改进)Spark: Structured Streaming和批处理虽然统一了,但历史包袱较重5. 更强大的窗口和触发器系统
Beam的触发器系统更加灵活:
// 组合触发器:定期触发 + Watermark触发 + 允许延迟数据AfterWatermark.pastEndOfWindow.withEarlyFirings(AfterProcessingTime.pastFirstElementInPane.plusDelayOf(Duration.standardMinutes(1))).withLateFirings(AfterPane.elementCountAtLeast(1))Apache Beam主要特点是可移植性,支持在 Spark、Flink、Dataflow 和其他运行器上执行,无需更改代码。类似Java的一次编写,到处运行。但是这样的优势是有代价的,与原生系统相比,Beam带来了15-35% 的额外性能开销,代表了巨大的成本,特别是对于资源受限的环境。使用者调试和开发,往往需要同时理解 Beam 概念和底层运行器行为。也就是说这一层抽象并不能完全屏蔽底层的运行系统。
天下没有免费的午餐,那么代价是什么呢?古尔丹