批处理与流处理
数据处理的两种基本模式:
| 维度 | 批处理 | 流处理 |
|---|---|---|
| 数据范围 | 有界(已知大小) | 无界(持续产生) |
| 处理触发 | 定时调度 | 事件到达即处理 |
| 结果完整性 | 100%(全部数据到齐后计算) | 近似(随时间不断修正) |
| 延迟 | 分钟到小时 | 毫秒到秒 |
| 容错 | 重跑整个 batch | Checkpoint 增量恢复 |
批处理的经典例子:每天晚上跑 Hive SQL 统计当日 GMV。流处理的例子:每笔订单完成后立即更新实时销售额面板。
Dataflow 模型
流处理系统的基本抽象是 Dataflow:数据从 Source 流入,经过 Transformation 转换,从 Sink 流出。
Source → Transformation → Transformation → Sink
↑ ↑ ↑ ↑
Kafka map/filter window/reduce Redis/ES
Topic aggregate
每个 Transformation 是无状态的函数(如 map)或有状态的算子(如 reduce、window)。Dataflow 的拓扑在作业启动时确定,运行时不可动态修改。
有界流 vs 无界流
Flink 对流的定义:
- 无界流(Unbounded Stream):没有定义结束,数据持续到达。如 Kafka Topic、TCP socket、IoT 传感器数据。
- 有界流(Bounded Stream):有明确的开始和结束。如 “2026-11-05 0:00 到 23:59 的日志文件”。
关键洞察:批处理是有界流的特例。如果 Flink 处理一个有界流(如读取 HDFS 文件),它的行为等价于批处理;如果处理无界流(如 Kafka Topic),它就是流处理。
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
// 无界流:Kafka Source(1.20 的新连接器)
KafkaSource<String> source = KafkaSource.<String>builder()
.setBootstrapServers("kafka:9092")
.setTopics("eventd-raw")
.setGroupId("eventd")
.setStartingOffsets(OffsetsInitializer.committedOffsets(OffsetResetStrategy.EARLIEST))
.setValueOnlyDeserializer(new SimpleStringSchema())
.build();
env.fromSource(source, WatermarkStrategy.noWatermarks(), "kafka-source");
// 有界流:文件读取
env.readTextFile("/data/orders/2026-11-05.log");
env.addSource(new FlinkKafkaConsumer<>(...)) 和 env.readTextFile(...) 在 1.20 都已标记废弃:前者由 KafkaSource 取代,后者应由 FileSource 或 DataStream API 的 fromSource 取代。旧代码仍能编译运行,但不要在新项目里使用。
三种时间语义
流处理中最容易出错的概念是时间。同一笔订单,可能存在三个不同的时间戳:
| 时间类型 | 定义 | 示例 | 特点 |
|---|---|---|---|
| Event Time | 事件实际发生的时间 | 用户点击按钮的瞬间 | 业务语义最准确,但可能乱序到达 |
| Ingestion Time | 事件进入 Flink Source 的时间 | Source 读到记录并打时间戳的瞬间(不是 Kafka broker 的接收时间) | 由 Source 分配,同一 Source 子任务内单调递增,跨子任务不保证 |
| Processing Time | 算子处理该事件的时间 | System.currentTimeMillis() | 最简单,但受机器时钟和延迟影响 |
事件时间 vs 处理时间
时间线:
06:00:00 事件实际发生(Event Time)
↓ 网络延迟 2 秒
06:00:02 Flink Source 读取并打时间戳(Ingestion Time)
↓ Flink 作业延迟 1 秒
06:00:03 Flink 算子处理(Processing Time)
如果使用 Processing Time 分配窗口,这个事件会被分配到 06:00:03 的窗口,而不是它实际发生的 06:00:00 窗口。这在实时性要求高的场景下会导致错误统计。
为什么事件时间会乱序
分布式系统中,事件从产生到被处理的路径不保证顺序:
用户 A(北京)06:00:00 下单 ──→ 网关(北京)──→ Kafka ──→ Flink
用户 B(纽约)06:00:01 下单 ──→ 网关(纽约)──→ Kafka ──→ Flink
↓
由于网络延迟,B 的事件可能先到达!
所以流处理引擎需要 Watermark 机制来容忍乱序(第 4.2 节展开)。
时间语义的选择策略
| 场景 | 推荐时间语义 | 原因 |
|---|---|---|
| 实时报表(GMV、UV) | Event Time | 需要按业务发生时间统计,不受处理延迟影响 |
| 告警触发(CPU > 80%) | Processing Time | 需要立即响应,不关心历史时间 |
| 日志分析 | Event Time | 需要按日志产生时间聚合 |
| 测试/演示 | Processing Time | 简单,无需处理乱序 |
eventd v3 的实时指标使用 Event Time,因为用户需要看到「每分钟实际发生了多少订单」,而不是「系统每分钟处理了多少订单」。
Dataflow 的容错
批处理容错简单:失败了重跑。流处理容错更复杂,因为数据在持续流动。
Flink 的 Checkpoint 机制(第 5.3 节):
数据流:A → B → C → D
↑
Checkpoint Barrier(快照标记)
当 Barrier 经过所有算子,系统保存一致的状态快照。
故障时从最近 Checkpoint 恢复「状态」,但数据丢不丢、重不重,
取决于 Source 是否提交 offset、Sink 是否事务或幂等写。
Kafka Streams 的容错类似,但粒度不同:它依赖 Kafka 的 log 回放 + 本地状态存储的 changelog Topic。
小结
| 概念 | 含义 |
|---|---|
| Dataflow | Source → Transformation → Sink 的拓扑 |
| 无界流 | 没有结束,持续到达 |
| 有界流 | 有开始和结束,批处理是其特例 |
| Event Time | 事件发生时间,最准确但可能乱序 |
| Processing Time | 处理时间,简单但不准确 |
| Ingestion Time | Source 读取时分配,同一子任务内递增 |
下一节 1.2 窗口与时间语义 :滚动窗口、滑动窗口、会话窗口、全局窗口的详细机制。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。