1.1 流处理范式:Dataflow、窗口、时间语义

流处理核心范式全面解析与实战指南:批处理与流处理在延迟/容错/状态维度的本质区别与适用边界、有界流与无界流的统一视角、Dataflow 模型 Source-Transformation-Sink 拓扑、事件时间/处理时间/摄取时间三种语义差异与选择策略。

批处理与流处理

数据处理的两种基本模式:

维度批处理流处理
数据范围有界(已知大小)无界(持续产生)
处理触发定时调度事件到达即处理
结果完整性100%(全部数据到齐后计算)近似(随时间不断修正)
延迟分钟到小时毫秒到秒
容错重跑整个 batchCheckpoint 增量恢复

批处理的经典例子:每天晚上跑 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。

小结

概念含义
DataflowSource → Transformation → Sink 的拓扑
无界流没有结束,持续到达
有界流有开始和结束,批处理是其特例
Event Time事件发生时间,最准确但可能乱序
Processing Time处理时间,简单但不准确
Ingestion TimeSource 读取时分配,同一子任务内递增

下一节 1.2 窗口与时间语义 :滚动窗口、滑动窗口、会话窗口、全局窗口的详细机制。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「java」更多文章

  1. 15.3 全书总结与学习路径回顾
  2. 15.2 响应式流处理的演进趋势
  3. 15.1 引擎选型指南