/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */
#content_views .toc,
/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */
#content_views.markdown_views > p:empty:has(+ .toc),
#content_views.markdown_views > .toc + p:empty,
/* 富文本旧版目录标记 */
#content_views.htmledit_views #main-toc,
#content_views.htmledit_views #hr-toc,
#content_views.htmledit_views p[id*=\”-toc\”] {
display: none !important;
}
/* 目录去掉后,紧跟的首个标题不再多出一块上边距 */
#content_views.markdown_views > .toc + h1,
#content_views.markdown_views > .toc + h2,
#content_views.markdown_views > .toc + h3,
#content_views.markdown_views > .toc + h4,
#content_views.markdown_views > .toc + p:empty + h1,
#content_views.markdown_views > .toc + p:empty + h2,
#content_views.markdown_views > .toc + p:empty + h3,
#content_views.markdown_views > .toc + p:empty + h4 {
margin-top: 0 !important;
}
流计算框架对比:Spark Streaming 与 Flink 的架构差异与选型指南
本文将深入分析 Spark Streaming 与 Flink 两大主流流处理框架在处理模型、延迟特性和状态管理方面的核心差异,帮助开发者根据业务场景做出合理选择。
1. 微批处理与真流模型的架构差异
Spark Streaming 采用微批处理模型,将实时数据流视为一系列小的批处理作业。其核心是基于 DStream(离散化流)构建的,而 DStream 实际上是一系列 RDD(弹性分布式数据集)的集合。每次微批处理的时间间隔通常为 0.5 秒至数秒。
// Spark Streaming 示例:创建 1 秒间隔的流处理
val ssc = new StreamingContext(sparkContext, Seconds(1))
val lines = ssc.socketTextStream("localhost", 9999)
val words = lines.flatMap(_.split(" "))
val pairs = words.map(word => (word, 1))
val wordCounts = pairs.reduceByKey(_ + _)
wordCounts.print()
ssc.start()
ssc.awaitTermination()
Flink 则采用真正的流处理模型,事件按到达时间顺序逐条处理,无需等待批次边界。其核心是 DataStream API,提供了事件时间处理、Exactly-Once 状态管理等高级特性。
// Flink 示例:事件驱动的流处理
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
DataStream<String> text = env.socketTextStream("localhost", 9999);
DataStream<Tuple2<String, Integer>> counts = text
.flatMap(new Tokenizer())
.keyBy(0)
.sum(1);
counts.print();
env.execute("Word Count");
| 流处理模型对比展示 Spark Streaming 微批处理与 Flink 真流处理的事件处理模型差异事件数据流数据接收批处理结果输出Spark Streaming 微批处理事件数据流事件接收单事件处理实时结果Flink 真流处理 |
图 1 清晰展示了两种模型的核心差异:Spark Streaming 将数据收集成小批次后再处理,而 Flink 则是逐条处理事件。这种架构上的根本差异导致了两者在延迟和性能方面的显著区别。
2. 延迟特性对比分析
Spark Streaming 的延迟受微批处理间隔限制,最低延迟通常为 500 毫秒到 2 秒。而 Flink 作为真正的流处理框架,可以实现毫秒级的延迟,通常为 1-100 毫秒,具体取决于处理逻辑的复杂性。
| 基础延迟 | 500ms-2s | 1ms-100ms |
| 延迟类型 | 微批延迟 | 事件处理延迟 |
| 流控制 | 基于批次背压 | 基于单事件背压 |
| 反压机制 | 有限制的批处理背压 | 细粒度的全局背压控制 |
| 延迟时间线对比展示 Spark Streaming 和 Flink 在不同时间节点下的处理延迟差异处理延迟时间线对比 (ms)05001000150020002500数据收集批处理结果输出Spark Streaming (2s延迟)10ms50ms150ms300ms500ms500ms20ms10msFlink (实时处理) |
图 2 直观展示了两种框架在延迟方面的差异:Spark Streaming 以 2 秒为周期进行批量处理,而 Flink 则能够以更细粒度处理事件,延迟显著降低。
3. 状态管理与容错机制对比
Spark Streaming 使用 Checkpoint 机制来实现容错,通过将状态和元数据定期保存到可靠存储(如 HDFS)中,并在发生故障时进行恢复。
// Spark Streaming Checkpoint 示例
val ssc = new StreamingContext(sparkContext, Seconds(1))
ssc.checkpoint("hdfs://checkpoint/path") // 设置检查点目录
val lines = ssc.socketTextStream("localhost", 9999)
val words = lines.flatMap(_.split(" "))
val pairs = words.map(word => (word, 1))
val wordCounts = pairs.updateStateByKey(updateFunction) // 有状态操作
Flink 则提供了更为丰富的状态管理机制,包括 Keyed State、Operator State 和 Broadcast State,支持 Exactly-Once 语义。Flink 的状态管理基于异步快照(Checkpoint)机制,能够对分布式状态进行一致性快照。
// Flink 状态管理示例
DataStream<Tuple2<String, Integer>> stream = env.addSource(new FlinkKafkaConsumer<>(…));
stream.keyBy(0) // 按键分区
.process(new KeyedProcessFunction<String, Tuple2<String, Integer>, Tuple2<String, Integer>>() {
private ValueState<Integer> countState;
@Override
public void open(Configuration parameters) {
countState = getRuntimeContext().getState(new ValueStateDescriptor<>(
"count", Integer.class));
}
@Override
public void processElement(Tuple2<String, Integer> value,
Context ctx,
Collector<Tuple2<String, Integer>> out) {
Integer currentCount = countState.value();
if (currentCount == null) {
currentCount = 0;
}
currentCount += value.f1;
countState.update(currentCount);
out.collect(new Tuple2<>(value.f0, currentCount));
}
});
| 状态类型 | RDD 状态 | Keyed State, Operator State, Broadcast State |
| 一致性保证 | At-Least-Once | Exactly-Once, At-Least-Once |
| 故障恢复 | 基于微批重启 | 基于异步快照恢复 |
| 状态后端 | HDFS | 内存、RocksDB、HDFS |
| 状态管理机制对比展示 Spark Streaming 和 Flink 在状态管理方面的不同实现方式应用逻辑DStream RDD微批处理CheckpointHDFSSpark Streaming 状态管理应用逻辑State Backend分布式状态异步快照状态一致性Flink 状态管理 |
图 3 展示了两种框架的状态管理机制差异:Spark Streaming 通过 RDD 和 Checkpoint 实现基于批的状态管理,而 Flink 则提供更细粒度的状态管理和精确的快照机制。
4. 选型建议与性能基准
基于前面分析,可以从业务场景角度提供以下选型建议:
| 亚秒级低延迟要求 | Flink | 真正的流处理模型 |
| 高吞吐批处理混合 | Spark Streaming | 与 Spark 生态无缝集成 |
| 复杂事件处理 | Flink | 丰富的状态管理和事件时间支持 |
| 大规模机器学习集成 | Spark Streaming | 与 MLlib 深度集成 |
| 需要 Exactly-Once 语义 | Flink | 更成熟的状态一致性保证 |
| 性能基准对比展示 Spark Streaming 和 Flink 在不同场景下的性能对比不同场景下性能对比 (相对值)吞吐量延迟Spark Streaming吞吐量延迟Flink |
图 4 显示了两种框架在不同维度上的性能对比:Flink 在延迟方面明显优于 Spark Streaming,而 Spark Streaming 在高吞吐场景下表现更稳定。
5. 最小示例代码与注意事项
以下是两种框架的最小可运行示例及注意事项:
5.1 Spark Streaming 最小示例
import org.apache.spark.SparkConf
import org.apache.spark.streaming.{Seconds, StreamingContext}
object SparkStreamingWordCount {
def main(args: Array[String]): Unit = {
// 创建 Spark 配置
val conf = new SparkConf().setAppName("SparkStreamingWordCount").setMaster("local[2]")
// 创建 StreamingContext,批处理间隔为 1 秒
val ssc = new StreamingContext(conf, Seconds(1))
// 连接到本地 socket 监听端口 9999
val lines = ssc.socketTextStream("localhost", 9999)
// 分词并统计单词频率
val words = lines.flatMap(_.split(" "))
val pairs = words.map(word => (word, 1))
val wordCounts = pairs.reduceByKey(_ + _)
// 打印结果
wordCounts.print()
// 启动流处理
ssc.start()
ssc.awaitTermination()
}
}
5.2 Flink 最小示例
import org.apache.flink.api.common.functions.FlatMapFunction;
import org.apache.flink.api.java.tuple.Tuple2;
import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.util.Collector;
public class FlinkWordCount {
public static void main(String[] args) throws Exception {
// 创建流处理执行环境
final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
// 从 socket 源获取数据
DataStream<String> text = env.socketTextStream("localhost", 9999);
// 分词并统计单词频率
DataStream<Tuple2<String, Integer>> counts = text
.flatMap(new Tokenizer())
.keyBy(0)
.sum(1);
// 打印结果
counts.print();
// 执行任务
env.execute("Flink Word Count");
}
// 自定义分词器
public static final class Tokenizer implements FlatMapFunction<String, Tuple2<String, Integer>> {
@Override
public void flatMap(String value, Collector<Tuple2<String, Integer>> out) {
// 分词并发射 (word, 1) 对
String[] words = value.toLowerCase().split("\\\\s+");
for (String word : words) {
if (word.length() > 0) {
out.collect(new Tuple2<>(word, 1));
}
}
}
}
}
5.3 注意事项
以上对比分析和示例代码旨在帮助开发者根据业务需求选择合适的流处理框架。


