2015年,Twitter在SIGMOD发表论文宣布了一个令人震惊的数据:他们用新系统Heron替换了运行多年的Storm后,吞吐量提升了14倍,延迟降低了10倍,资源消耗却减少了三分之二。这不仅仅是性能数字的跃升,更揭示了流处理系统设计哲学的根本性分歧。
流处理并非新技术。早在1992年,Tapestry系统就首次提出了流式查询的概念。但真正让流处理成为企业基础设施核心组件的,是过去二十年开源生态的爆发式演进。从Apache Storm的开创性工作,到Spark Streaming的微批处理模型,再到Apache Flink的原生流处理架构,每一次范式转变背后都蕴含着深刻的技术权衡。
无限流的有限处理:流处理的本质问题
流处理面对的核心挑战可以用一个数学问题来描述:如何在一个无界(unbounded)、无序(unordered)的数据流上,产生确定(deterministic)、**一致(consistent)**的计算结果?
传统批处理假设数据是有限的、静态的——查询执行完毕后,结果就固定了。但流处理面对的是一条永不停息的数据河流,结果需要随着新数据的到达持续更新。更棘手的是,分布式系统中网络延迟、时钟漂移、故障恢复等因素会导致数据乱序到达,如何保证计算结果的正确性?
Google在2013年发表的Millwheel论文中提出了三个核心概念,奠定了现代流处理的理论基础:**事件时间(Event Time)与处理时间(Processing Time)**的分离、水位线(Watermark)机制、以及Exactly-once语义。这三个概念成为后续所有流处理系统必须回答的问题。
graph TD
subgraph "事件时间 vs 处理时间"
E1[事件发生<br/>Event Time: 10:00:00] --> D1[网络延迟]
D1 --> E2[事件到达<br/>Processing Time: 10:00:05]
E3[事件发生<br/>Event Time: 10:00:02] --> D2[网络延迟更短]
D2 --> E4[事件到达<br/>Processing Time: 10:00:03]
end
style E1 fill:#e1f5fe
style E3 fill:#e1f5fe
style E2 fill:#fff3e0
style E4 fill:#fff3e0
第一代:Apache Storm的开创与困境
2011年,Nathan Marz在Twitter开源了Storm,这是第一个真正意义上被大规模工业采用的分布式流处理系统。Storm的设计哲学可以用一句话概括:流是无界的元组序列,计算是拓扑图上的持续流动。
Storm的核心抽象是Topology(拓扑)——一个由Spout(数据源)和Bolt(处理节点)组成的有向无环图。Spout从外部数据源(如Kafka)读取数据并发射Tuple(元组),Tuple沿着拓扑图流动,经过各个Bolt的处理后输出结果。
graph LR
subgraph "Storm拓扑架构"
S[Spout<br/>数据源] --> B1[Bolt 1<br/>过滤]
B1 --> B2[Bolt 2<br/>聚合]
B1 --> B3[Bolt 3<br/>转换]
B2 --> B4[Bolt 4<br/>输出]
B3 --> B4
end
style S fill:#4caf50,color:#fff
style B1 fill:#2196f3,color:#fff
style B2 fill:#2196f3,color:#fff
style B3 fill:#2196f3,color:#fff
style B4 fill:#ff9800,color:#fff
Storm的可靠性保证依赖于其独特的Acker机制。每个Tuple在Spout发射时被赋予一个唯一的消息ID,后续每个Bolt在处理并发射新Tuple时,会将新Tuple与输入Tuple"锚定"(anchor)。一棵Tuple树就这样在拓扑中生长,当整棵树被成功处理完毕,Acker会通知Spout进行确认(ack);如果处理失败或超时,Spout会重发该Tuple。
这个设计巧妙地实现了At-least-once语义,但也埋下了隐患。首先,Acker机制需要在内存中维护所有正在处理的Tuple树,在大吞吐场景下内存压力巨大。其次,Tuple重放会导致重复计算——如果一个聚合操作收到了重复的Tuple,结果就会错误。最后,Storm没有内置的状态管理机制,开发者需要自己处理状态的持久化和恢复。
Twitter在论文中披露的数据揭示了这些问题的代价:在处理每秒数百万条消息的生产环境中,Storm的垃圾回收停顿时间可以达到数秒,导致严重的延迟抖动。更关键的是,缺乏Exactly-once语义使得Storm无法用于金融、计费等对数据准确性要求极高的场景。
第二代:Spark Streaming的微批处理折中
2013年,AMPLab在Spark基础上推出了Spark Streaming,提出了一种完全不同的设计思路:既然无限流难以处理,为什么不把它切成有限的小批次?
Spark Streaming的核心抽象是DStream(Discretized Stream)。系统按照固定的时间间隔(如1秒、5秒)将到达的数据切分成一个个微批次(Micro-batch),每个批次内的数据被封装成一个RDD,然后使用Spark的批处理引擎进行处理。
graph TD
subgraph "微批处理模型"
S[数据流] --> B1[批次 1<br/>t=0~1s]
S --> B2[批次 2<br/>t=1~2s]
S --> B3[批次 3<br/>t=2~3s]
S --> B4[批次 4<br/>t=3~4s]
B1 --> P1[Spark RDD处理]
B2 --> P2[Spark RDD处理]
B3 --> P3[Spark RDD处理]
B4 --> P4[Spark RDD处理]
end
style S fill:#e8f5e9
style B1 fill:#bbdefb
style B2 fill:#bbdefb
style B3 fill:#bbdefb
style B4 fill:#bbdefb
style P1 fill:#fff9c4
style P2 fill:#fff9c4
style P3 fill:#fff9c4
style P4 fill:#fff9c4
这个设计带来的最大优势是复用Spark生态系统。开发者可以使用相同的API处理批数据和流数据,可以无缝地在流处理作业中使用Spark SQL、MLlib等组件。更重要的是,由于每个批次都是有限数据集,Spark Streaming可以轻松实现Exactly-once语义——只需在每个批次处理完毕后原子性地提交结果即可。
但微批处理模型有无法回避的代价。首先是延迟:系统必须等待一个完整的批次间隔才能开始处理数据,这意味着最小延迟就是批次大小。在实践中,为了保证吞吐量,批次大小通常设置在500毫秒到几秒之间,这对于需要亚秒级响应的场景是不可接受的。
其次是吞吐量的不均匀性。假设批次大小为1秒,如果某秒内的数据量突然激增,系统可能无法在下一个批次到来前处理完毕,导致延迟累积。Spark Streaming通过**背压(Backpressure)**机制动态调整消费速率来缓解这个问题,但本质上仍然是"头痛医头"。
Yahoo在2015年的基准测试中对比了Storm、Spark Streaming和Flink的性能。结果显示,在99%延迟指标上,Flink和Storm可以达到10-20毫秒级别,而Spark Streaming受限于批次大小,延迟在数百毫秒到数秒之间波动。
第三代:Apache Flink的原生流处理革命
Apache Flink的起源可以追溯到2010年柏林工业大学的Stratosphere研究项目。与Spark Streaming"用批模拟流"的思路相反,Flink的设计哲学是**“流是第一公民,批是流的特例”**。
Flink的核心抽象是DataStream——一个真正的、无边界的流。每条数据到达后立即被处理,不需要等待批次积累。这种**原生流处理(Native Streaming)**模型消除了微批处理的人为延迟,将端到端延迟降低到毫秒级别。
但Flink真正的突破在于状态管理和容错机制的设计。
Chandy-Lamport算法的工程化
1985年,K. Mani Chandy和Leslie Lamport在ACM TOCS上发表了著名的论文《Distributed Snapshots》,提出了一种在分布式系统中获取一致快照的算法。Flink将这个算法改造为Barrier Snapshot机制,成为其容错能力的基石。
sequenceDiagram
participant JM as JobManager
participant S1 as Source 1
participant S2 as Source 2
participant OP as Operator
participant SK as Sink
JM->>S1: 触发Checkpoint
JM->>S2: 触发Checkpoint
S1->>S1: 保存状态
S2->>S2: 保存状态
S1->>OP: 发送Barrier
S2->>OP: 发送Barrier
Note over OP: 等待所有Barrier对齐
OP->>OP: 保存状态
OP->>SK: 发送Barrier
SK->>SK: 保存状态
SK->>JM: 确认Checkpoint完成
具体实现如下:当JobManager决定触发一次Checkpoint时,它会向所有数据源注入一个Barrier(屏障)。Barrier随数据流流动,当算子收到Barrier时,会暂停处理后续数据,将自己的状态写入持久化存储,然后向下游发射Barrier。当所有Barrier都流经整个拓扑并到达Sink后,一次全局一致的快照就完成了。
这个设计的精妙之处在于:不需要全局暂停。各个算子可以在不同时间点完成状态快照,只要保证Barrier对齐,最终就能得到一致的快照。这意味着Checkpoint过程对业务逻辑的影响被最小化。
增量Checkpoint与状态后端
对于有状态的计算(如窗口聚合、去重、Join),状态大小可能达到数十GB甚至TB级别。每次全量快照显然不可持续。Flink引入了增量Checkpoint机制:只记录自上次Checkpoint以来变化的状态数据。
配合RocksDB状态后端,Flink可以将状态存储在本地磁盘并异步上传到分布式存储。RocksDB是一个嵌入式LSM-tree数据库,其写入性能随着数据量增长保持稳定。这使得Flink能够处理几乎无限大小的状态。
阿里巴巴在2019年的技术博客中披露,他们的Flink集群管理的状态总量达到PB级别,单个作业的状态大小超过10TB。这在Storm时代是无法想象的。
两阶段提交实现端到端Exactly-once
Checkpoint解决了内部状态的容错问题,但流处理系统通常需要与外部系统交互——从Kafka读取数据,将结果写入数据库。如何保证整个链路的Exactly-once语义?
Flink采用了两阶段提交协议(Two-Phase Commit, 2PC)。以Kafka为例:
sequenceDiagram
participant S as Source
participant J as JobManager
participant SK as Kafka Sink
participant K as Kafka
Note over S,K: 第一阶段:Pre-commit
S->>S: 处理数据
S->>SK: 发送数据
SK->>K: 开启事务
SK->>K: 写入数据(未提交)
J->>SK: Checkpoint完成通知
Note over S,K: 第二阶段:Commit
SK->>K: 提交事务
K->>K: 数据可见
alt Checkpoint失败
SK->>K: 回滚事务
K->>K: 数据丢弃
end
第一阶段(Pre-commit):当算子完成Checkpoint后,Kafka Sink会预提交事务,将数据写入Kafka但标记为"未提交"状态。
第二阶段(Commit):当所有算子的Checkpoint都成功完成后,JobManager通知所有Sink正式提交事务。如果任何一个算子的Checkpoint失败,所有Sink都会回滚事务。
这个设计要求外部系统支持事务或幂等写入。对于不支持事务的系统(如普通数据库),开发者需要实现幂等写入逻辑或接受At-least-once语义。
并行演进:Kafka Streams、Heron、Samza的三条路线
在Storm、Spark Streaming、Flink三大主流系统之外,几条并行的技术路线也在演进,各自针对不同的场景做出了独特的权衡。
Kafka Streams:轻量级的嵌入式方案
2016年,Confluent推出了Kafka Streams,这是一款轻量级流处理库——注意是"库"而非"框架"。开发者不需要部署独立的集群,流处理逻辑直接运行在应用程序进程中。
Kafka Streams的设计哲学是:既然大多数流处理应用都以Kafka作为数据源和数据汇,为什么不直接利用Kafka的能力?
其核心创新是流表对偶性(Stream-Table Duality):一个Kafka Topic既可以被视为追加写入的流,也可以被视为持续更新的表。Kafka Streams通过Changelog Topic实现状态持久化——所有状态变更都写入一个内部Topic,恢复时从该Topic重放。这与数据库的Write-Ahead Log异曲同工。
Kafka Streams支持Exactly-once语义,实现方式是利用Kafka的事务机制。在一个事务中,应用可以原子性地消费输入、更新状态、生产输出。这种设计的代价是只能与Kafka深度绑定,但好处是架构简洁、运维成本低。
LinkedIn在技术博客中披露,他们使用Kafka Streams处理超过10000个流处理任务,涵盖了用户画像更新、广告投放、推荐系统等核心业务。
Twitter Heron:Storm的继承者
面对Storm在生产环境中暴露的问题,Twitter在2015年推出了Heron。Heron的核心设计目标是在保持与Storm API完全兼容的前提下,解决Storm的可扩展性、调试性和资源效率问题。
Heron的架构创新在于进程级隔离。每个处理实例(Heron称之为Instance)运行在独立的进程中,而不是像Storm那样所有实例共享一个JVM。这样做的好处是:
- 单个实例的故障不会影响其他实例
- 可以精确监控每个实例的资源消耗
- 内存泄漏等问题更容易定位
Heron还引入了Backpressure机制:当下游处理不过来时,会向上游发送信号减慢数据发射速率,而不是简单地丢弃数据或让队列爆炸。
Twitter的论文显示,Heron在相同硬件配置下,吞吐量比Storm高2-14倍,延迟降低10-100倍。但Heron并没有解决Storm缺乏Exactly-once语义的核心问题,这限制了其在金融等高可靠性场景的应用。
Apache Samza:LinkedIn的中间路线
LinkedIn在开发Kafka的同时,也在构建自己的流处理系统Samza。Samza的设计哲学可以概括为:利用现有基础设施的能力,而不是重新发明轮子。
Samza与Kafka、YARN深度集成:Kafka提供消息传递和状态持久化,YARN提供资源管理和进程隔离。这种设计使得Samza可以快速落地,但也限制了其灵活性。
Samza的一个重要创新是本地状态存储:每个任务维护一个嵌入式RocksDB实例作为状态存储,避免了跨网络访问状态的延迟。状态变更通过Kafka Changelog Topic实现持久化和恢复。
LinkedIn的论文显示,Samza在生产环境中处理超过800亿条消息/天,支撑了消息推送、广告排序等核心业务。但随着Kafka Streams的推出,Samza的重要性有所下降。
核心技术突破:从概念到工程实现
流处理系统二十年演进的本质,是将学术概念转化为可大规模部署的工程实践。几个核心技术突破贯穿始终。
事件时间与水位线:处理乱序数据的数学方案
在理想世界中,数据按照事件发生的顺序到达处理系统。但在分布式系统中,网络延迟、时钟不同步、重试机制等因素会导致数据乱序到达。如果按照到达顺序(处理时间)进行窗口计算,结果将无法复现。
Google Millwheel论文提出的水位线(Watermark)机制优雅地解决了这个问题。水位线是一个时间戳标记,语义是:“所有事件时间小于等于该时间戳的数据都已经到达”。
graph LR
subgraph "水位线推进机制"
D1[数据 t=3] --> D2[数据 t=5]
D2 --> D3[数据 t=4<br/>迟到数据]
D3 --> W1[水位线 W=4]
W1 --> D4[数据 t=7]
D4 --> D5[数据 t=6<br/>迟到数据]
D5 --> W2[水位线 W=6]
W1 -.->|触发| W1_T[窗口 [0,4]<br/>可以计算]
W2 -.->|触发| W2_T[窗口 [4,6]<br/>可以计算]
end
style W1 fill:#4caf50,color:#fff
style W2 fill:#4caf50,color:#fff
style W1_T fill:#ffeb3b
style W2_T fill:#ffeb3b
style D3 fill:#f44336,color:#fff
style D5 fill:#f44336,color:#fff
水位线的生成策略通常基于启发式假设。最常见的是**有界乱序(Bounded Out-of-Orderness)**策略:假设数据的乱序程度不会超过某个阈值D,那么当收到事件时间为T的数据时,可以认为事件时间小于T-D的数据都已经到达。
当水位线推进到某个窗口的结束时间,系统就可以安全地触发该窗口的计算并输出结果。对于迟到的数据(事件时间早于当前水位线),系统可以选择丢弃、侧输出或触发更新计算。
Flink将水位线机制实现为数据流中的特殊元素,随数据流动并触发计算。这使得开发者可以专注于业务逻辑,而不必关心乱序处理的复杂性。
窗口操作的演进:从固定到动态
窗口(Window)是流处理的核心概念——将无限流切分为有限块以便聚合计算。不同窗口类型适用于不同场景:
graph TD
subgraph "窗口类型对比"
subgraph "滚动窗口 Tumbling"
T1[窗口1<br/>0-5s] --> T2[窗口2<br/>5-10s] --> T3[窗口3<br/>10-15s]
end
subgraph "滑动窗口 Sliding"
S1[窗口1<br/>0-10s] --> S2[窗口2<br/>2-12s] --> S3[窗口3<br/>4-14s]
end
subgraph "会话窗口 Session"
E1[事件组1] --- G1[间隔]
G1 --- E2[事件组2] --- G2[间隔]
G2 --- E3[事件组3]
end
end
style T1 fill:#e3f2fd
style T2 fill:#e3f2fd
style T3 fill:#e3f2fd
style S1 fill:#fff3e0
style S2 fill:#fff3e0
style S3 fill:#fff3e0
style E1 fill:#e8f5e9
style E2 fill:#e8f5e9
style E3 fill:#e8f5e9
滚动窗口(Tumbling Window):固定大小、不重叠。适用于周期性报告,如每5分钟的UV统计。
滑动窗口(Sliding Window):固定大小、按步长滑动。适用于移动平均计算,如每1分钟计算过去1小时的平均值。
会话窗口(Session Window):基于数据活动动态划分大小。适用于用户行为分析,如一次会话中的所有点击。
窗口操作的工程实现面临两个挑战:状态管理和**触发器(Trigger)**设计。
状态管理方面,每个窗口需要维护中间状态。当窗口数量巨大时(如按用户ID分组,每个用户一个会话窗口),状态可能爆炸。Flink通过状态TTL(Time-To-Live)机制自动清理过期状态,以及增量聚合(Incremental Aggregation)减少状态大小。
触发器设计方面,何时触发窗口计算需要权衡延迟和完整性。水位线到达时触发可以保证正确性,但延迟可能较长;提前触发可以降低延迟,但结果可能不完整。Flink允许开发者自定义触发策略,如"水位线到达时触发,之后每收到一个迟到数据更新一次"。
反压机制:流量控制的系统工程
当数据生产速率超过消费速率时,系统需要某种机制防止资源耗尽。这被称为反压(Backpressure)或流量控制(Flow Control)。
Storm的早期版本缺乏内置反压机制,当下游处理不过来时,数据会在上游队列中堆积,最终导致OOM。Spark Streaming通过动态调整批次大小来应对,但本质上是"事后补救"。
Flink在1.5版本引入了基于信用的流量控制(Credit-based Flow Control),这是一个精巧的设计:
sequenceDiagram
participant U as 上游算子
participant N as 网络
participant D as 下游算子
D->>U: 发送信用 Credit=3<br/>(有3个可用缓冲区)
Note over U: 收到信用,可以发送数据
U->>N: 发送数据包1
U->>N: 发送数据包2
U->>N: 发送数据包3
Note over U: 信用耗尽,停止发送
N->>D: 数据包1到达
N->>D: 数据包2到达
N->>D: 数据包3到达
D->>D: 处理数据,释放缓冲区
D->>U: 发送新信用 Credit=3
Note over U: 可以继续发送
- 下游算子向上游发送"信用"(Credit),表示自己有多少缓冲区可用
- 上游只在收到足够信用后才发送数据
- 下游处理数据后释放缓冲区,信用恢复
这种设计将反压的传播延迟降低到网络往返级别,而不是等待队列填满。更关键的是,它可以精确控制每个连接的数据量,避免全局锁竞争。
流批一体的愿景与实践
2015年,Google发表了《The Dataflow Model》论文,提出了一个激进的观点:批处理和流处理可以统一在同一个编程模型下,批处理只是流处理的一个特例——有界的流。
这个愿景的吸引力是显而易见的:企业只需要维护一套代码、一套基础设施,就可以处理历史数据和实时数据。Apache Beam项目就是这一愿景的开源实现,它定义了一套统一的编程模型,可以在Flink、Spark、Google Cloud Dataflow等引擎上运行。
Flink的流批一体实践
Flink从一开始就设计为流批一体的系统。在Flink中,数据集(DataSet)API和流(DataStream)API共享相同的运行时引擎。执行计划优化器可以根据数据源是否为有界流,自动选择不同的执行模式:
- 对于无界流:采用流模式,持续执行
- 对于有界流:采用批模式,可以启用优化(如排序合并Join而非流式Join)
阿里巴巴的Blink团队进一步增强了Flink的SQL能力,使其可以用同一套SQL语句处理流数据和批数据。这在他们2019年的技术博客中被称为"让SQL成为流批处理的统一语言"。
现实中的权衡
流批一体听起来美好,但实践中面临挑战:
首先是语义差异。流处理需要考虑迟到数据、水位线、增量更新,而批处理假设数据已经完整。同一份SQL在流模式和批模式下可能产生不同的结果。
其次是性能权衡。流式处理为低延迟优化,批处理为高吞吐优化。一个引擎很难在两个维度上都做到极致。
最务实的做法是:将流处理用于实时数据新鲜度要求高的场景,将批处理用于历史数据分析,通过数据湖架构(如Delta Lake、Iceberg)作为桥梁。
新一代流处理系统:云原生与流式数据库
随着云原生技术的发展和流式SQL的普及,新一代流处理系统正在涌现。
RisingWave:流式数据库的新范式
RisingWave是一个2021年启动的开源项目,它的定位是流式数据库(Streaming Database),而非传统的流处理引擎。
核心区别在于抽象层级。传统流处理引擎要求开发者思考流、算子、状态等概念;RisingWave将流处理结果呈现为物化视图(Materialized View)——一个持续更新的表。用户可以用标准SQL查询物化视图,就像查询普通表一样。
RisingWave的架构设计针对云原生环境进行了优化:
- 存算分离:计算节点无状态,状态存储在共享存储(如S3)中
- 弹性伸缩:可以根据负载动态增减计算节点,无需迁移状态
- 简化运维:无需手动配置状态后端、Checkpoint路径等
这种设计的代价是延迟略高于Flink(通常在毫秒到秒级),但换来了显著降低的运维复杂度。
云托管服务的兴起
除了开源项目,云厂商也在推出托管的流处理服务:
- AWS Kinesis Data Analytics
- Google Cloud Dataflow
- Azure Stream Analytics
这些服务的共同特点是:屏蔽底层基础设施复杂性,用户只需上传代码或SQL,服务自动处理资源分配、故障恢复、弹性伸缩。
选择的艺术:没有银弹
流处理系统的选择本质上是在多个维度上进行权衡:
graph LR
subgraph "流处理系统选型决策树"
START[流处理需求] --> Q1{延迟要求}
Q1 -->|毫秒级| Q2{是否深度绑定Kafka}
Q1 -->|秒级可接受| Q3{是否需要Spark生态}
Q2 -->|是| KS[Kafka Streams<br/>轻量级部署]
Q2 -->|否| Q4{状态大小}
Q4 -->|TB级| FL[Flink + RocksDB<br/>大状态支持]
Q4 -->|GB级以下| FL_L[Flink + Heap<br/>低延迟]
Q3 -->|是| SS[Spark Streaming<br/>生态复用]
Q3 -->|否| Q5{SQL优先级}
Q5 -->|高| RW[RisingWave<br/>SQL友好]
Q5 -->|中| FL_ALL[Flink]
end
style START fill:#e1f5fe
style KS fill:#c8e6c9
style FL fill:#c8e6c9
style FL_L fill:#c8e6c9
style SS fill:#c8e6c9
style RW fill:#c8e6c9
style FL_ALL fill:#c8e6c9
| 维度 | Storm | Spark Streaming | Flink | Kafka Streams |
|---|---|---|---|---|
| 延迟 | 毫秒级 | 秒级 | 毫秒级 | 毫秒级 |
| 吞吐量 | 中等 | 高 | 高 | 高 |
| Exactly-once | ❌ | ✅ | ✅ | ✅ |
| 事件时间处理 | ❌ | 部分 | ✅ | ✅ |
| 状态管理 | 手动 | 自动 | 自动 | 自动 |
| 运维复杂度 | 高 | 中 | 高 | 低 |
| 生态系统 | 弱 | 强 | 强 | 弱(绑定Kafka) |
选择建议:
- 超低延迟场景(毫秒级风控、实时推荐):Flink
- 高吞吐 + 兼容Spark生态:Spark Streaming(或考虑Spark Structured Streaming的连续处理模式)
- 轻量级部署 + 深度绑定Kafka:Kafka Streams
- SQL友好 + 简化运维:RisingWave或云托管服务
- 已有Storm代码迁移:Heron
没有最优解,只有最适合特定场景的权衡。这正是流处理系统二十年演进的启示:技术进步不是线性的替代,而是问题空间的持续拓展和解决方案的持续细化。
从Storm的开创,到Spark Streaming的工程务实,再到Flink的理论完备,每一步都在解决前一代系统的核心痛点,同时也引入了新的复杂度。新一代系统又在这些复杂度上做减法,试图让流处理能力触达更多开发者。这个循环仍在继续。
参考文献
- Akidau, T., et al. “MillWheel: Fault-Tolerant Stream Processing at Internet Scale.” VLDB 2013.
- Akidau, T., et al. “The Dataflow Model: A Practical Approach to Balancing Correctness, Latency, and Cost in Massive-Scale, Unbounded, Out-of-Order Data Processing.” VLDB 2015.
- Carbone, P., et al. “Apache Flink: Stream and Batch Processing in a Single Engine.” Data Engineering Bulletin 2015.
- Kulkarni, S., et al. “Twitter Heron: Stream Processing at Scale.” SIGMOD 2015.
- Chandy, K.M., Lamport, L. “Distributed Snapshots: Determining Global States of Distributed Systems.” ACM TOCS 1985.
- Kreps, J., et al. “Kafka: A Distributed Messaging System for Log Processing.” NetDB 2011.
- Noghabi, S.A., et al. “Samza: Stateful Scalable Stream Processing at LinkedIn.” VLDB 2017.
- Zaharia, M., et al. “Spark Streaming: Fault-tolerant Scalable Stream Processing.” HotCloud 2012.
- Apache Flink Documentation. https://nightlies.apache.org/flink/
- Yahoo Streaming Benchmarks. https://github.com/yahoo/streaming-benchmarks
- Confluent Blog. “Exactly-Once Semantics are Possible: Here’s How Kafka Does it.” 2017.
- Alibaba Tech Blog. “Alibaba Blink: Real-Time Computing for Big-Time Gains.” 2018.
- Google Cloud Blog. “The stream processing model behind Google Cloud Dataflow.” 2024.
- RisingWave Documentation. https://docs.risingwave.com/
- Apache Beam Documentation. https://beam.apache.org/
- Kreps, J. “Questioning the Lambda Architecture.” O’Reilly Radar 2014.
- Marz, N., Warren, J. “Big Data: Principles and best practices of scalable realtime data systems.” Manning 2015.
- Stonebraker, M., et al. “The 8 Requirements of Real-Time Stream Processing.” ACM SIGMOD Record 2005.
- Golab, L., Özsu, M.T. “Issues in Data Stream Management.” ACM SIGMOD Record 2003.
- Babcock, B., et al. “Models and Issues in Data Stream Systems.” PODS 2002.