土法炼钢兴趣小组的算法知识备份

【流式数据处理】流处理全景:从日志到有状态计算

文章导航

分类入口
databasedistributed
标签入口
#stream-processing#kafka#flink#lambda#kappa#event-log#stateful-compute#lakehouse#batch#micro-batch

目录

读者在 数据湖与开放表格式 第 19 章已经看到:Flink 作业按 checkpoint 间隔把 CDC 事件写进 Iceberg,Committer 在 checkpoint 完成后再做表提交。那条链路回答的是 数据怎么落进表。但同一套实时管道里还有另一组问题:乱序到达的点击事件怎么算对「过去五分钟 UV」、作业重启后为什么不会从 offset 0 重算整个历史、状态膨胀和背压从哪来——这些属于 流计算引擎侧,lakehouse 系列刻意留空,由本系列补齐。

本文是流式数据处理系列的 第 1 篇,不教任何引擎的安装命令,而是建立三个会贯穿全系列 18 篇的心智模型:

后文默认读者具备基本分布式概念(建议 distributed 系列 里日志复制相关篇),但不假设写过 Flink 算子或调过 Kafka 副本。

环境说明:本机为 WSL2(Linux 6.6.87.2)、i9-12900K / 32 GiB,未安装 JVM、Kafka、Flink。本文概念与架构结论来自 Apache Kafka / Flink 官方文档、Dataflow Model 论文(Akidau et al.);不包含未在本机执行的 benchmark 数字或伪造的命令输出。文末给出可复现的本地实验入口。

版本锚定:Kafka 3.x(KRaft)Flink 1.20+ / 2.x 主线。云托管 MSK / Managed Flink 的内部调度与定价不在本系列范围。


一、批处理、流处理与微批:四个维度

「批 vs 流」常被简化成「离线 vs 实时」,这个二分法在工程上不够用。同一条业务链路里,延迟目标、吞吐形态、容错语义、状态驻留方式 往往同时变化。下表用四个正交维度对比三种典型范式(来源:Flink Documentation Batch and Stream Processing;Dataflow Model 论文 Section 2–3)。

维度 批处理(Batch) 流处理(Stream) 微批(Micro-batch)
延迟 分钟~小时~天(等数据齐) 毫秒~秒(事件到达即算) 秒~分钟(按固定间隔切 mini-batch)
吞吐 高(顺序扫描、向量化、大 I/O) 中高(逐条/小批,shuffle 与状态 I/O 开销) 中高(批内向量化,批间有调度间隙)
语义 输入边界固定;失败重跑整批 无界输入;checkpoint / savepoint 界定一致性点 每 micro-batch 一个 job 或 stage;失败重跑该批
状态 通常无跨批驻留状态;每 job 重算 Keyed State、窗口 state 跨事件驻留 批间 state 可保留(Structured Streaming)或每批重建

1.1 批处理:边界清晰的输入集合

经典批 job(MapReduce、Spark batch、SQL INSERT OVERWRITE ... SELECT)假设 输入在 job 开始前已经确定:HDFS 目录里有哪些文件、分区 dt=2026-06-30 是否齐全。失败时 重跑同一输入边界,输出替换或追加到目标表。状态如果存在,多半是 中间 shuffle spill 或外部 sort,job 结束即释放。

这与 columnar-engine 系列 的 OLAP 扫描模型一致:列存引擎优化的是 大表一次性读透,而不是单条记录的增量更新路径。

1.2 流处理:无界输入与持续计算

流 job 的输入 没有天然结束(除非是有界流,如读历史文件回放)。引擎把到达的 record 当作 无限序列,在 算子本地或 RocksDB 里维护状态(计数、窗口聚合、join 缓冲)。失败重启时不能「从头重扫 HDFS 目录」,而要 从 checkpoint 恢复状态 + source offset(本系列 第 10 篇)。

事件时间(event time)与 watermark 解决乱序:业务时间戳为 10:00:05 的事件可能在 10:00:20 才到达网络。流引擎必须在 不完整输入 下仍输出 可解释的中间结果,并在 watermark 推进后 关闭窗口第 2、3 篇)。

1.3 微批:用批引擎模拟流

Spark Structured Streaming(早期 DStream 已属遗留 API,本系列不展开)把无界源切成 一系列小 DataFrame,每个 trigger 间隔触发一次 micro-batch job。延迟下界是 trigger 间隔;语义上接近 at-least-once 批叠加,exactly-once 依赖 checkpoint 目录与 sink 幂等(第 18 篇 引擎对照)。

微批的优势是 复用成熟批优化器(Catalyst、Whole-Stage Codegen);代价是 亚秒级延迟 往往不如原生流引擎(Flink、Kafka Streams)直接。选型不在本篇做排名,第 18 篇 给决策树。

1.4 四个维度如何一起读

业务诉求 优先看的维度 典型选择
报表 T+1 延迟可高、语义简单 批 / 湖仓 SQL
实时大屏秒级 延迟 + 状态 Flink + Kafka
近实时数仓分钟级 吞吐 + 运维复杂度 微批或 Flink 大窗口
金融对账 exactly-once 语义 + 状态 Flink EOS + Kafka 事务(第 14–15 篇

流处理不是「更快的批」,而是 在输入无界、到达乱序的前提下,定义何时输出、如何容错、状态存哪 的另一套问题域。


二、可重放日志:流平台的核心抽象

Kafka 设计文档把 topic 描述为 commit log(来源:Kafka Documentation,Design):分区内的 record 按 单调递增 offset append,consumer 通过 拉取 + 提交 offset 推进进度。这条 log 有三个工程性质:

  1. 持久化:副本 ISR 保证故障不丢已 ack 的数据(第 5 篇)。
  2. 可重放:同一 consumer group 换起始 offset,或新 group 从头读,log 内容不变。
  3. 解耦:producer 与 consumer 通过 log 间接通信,速率不匹配由 consumer lag 显式暴露。
flowchart LR
  P["Producer<br/>append"]
  L["Partition Log<br/>offset 0,1,2,..."]
  C1["Consumer A<br/>offset=2"]
  C2["Consumer B<br/>offset=5"]
  P --> L
  L --> C1
  L --> C2

Flink 从 Kafka Source 读数据时,消费进度与算子 state 一起写入 checkpoint第 10 篇)。因此 log 是真相来源(source of truth),Flink 状态是 为了低延迟计算而衍生的派生数据——丢失 state 可从 log 重放重建(代价是重算时间与资源)。

2.1 与 lakehouse 的分层对称

lakehouse 系列对象存储上的不可变 Parquet/ORC 文件 当作真相,表格式(Iceberg / Hudi / Delta)用 元数据 snapshot 指向「哪些文件属于当前表版本」。流式入湖(lakehouse 第 19 章)是 log → 文件 → snapshot;本系列是 log → 有状态计算 → sink(湖 / 服务 / 另一 topic)

Lakehouse 流处理(本系列)
持久真相 对象存储数据文件 + 表 metadata Kafka partition log(及可选湖表)
增量指针 snapshot / commit consumer offset + checkpoint id
计算 Spark/Flink/Trino 批扫描 Flink 持续算子 + state
一致性点 表 commit(CAS) checkpoint 完成 + 2PC commit(第 15 篇

读者从 lakehouse/19 进入本系列,可把 Committer 提交间隔Flink checkpoint 间隔 理解为 同一旋钮的两端:前者决定湖表可见频率与小文件(第 17 篇),后者决定 状态快照频率与故障恢复 RPO

2.2 分区内有序、分区间无序

Kafka 只保证单分区内 record 顺序与 offset 单调(来源:Kafka Documentation,Design)。跨分区没有全局顺序。流 job 若要做 全局计数,要么 单分区瓶颈,要么 keyBy 后按 key 局部有序第 4、8 篇)。这是 水平扩展与顺序性 的经典权衡,与 distributed 系列 里「复制日志有序但分片」同构。


三、Lambda 与 Kappa:架构边界,不是宗教

Lambda 架构(Marz, 2011 前后业界归纳)在工程上常表现为:

Kappa 架构(Kreps, 2014)主张 只用流:批处理是对 同一条 log 的历史 replay(换起始 offset、加大并行度),不维护两套代码路径。

本系列 站在「日志即真相、流引擎为主力」一侧,但不否定批式回补:

flowchart TB
  subgraph lambda ["Lambda(双路径)"]
    L1["Kafka log"] --> S["Speed: Flink"]
    L1 --> B["Batch: Spark SQL"]
    S --> V["Serving / 湖表"]
    B --> V
  end
  subgraph kappa ["Kappa(单 log 多消费模式)"]
    L2["Kafka log"] --> F["Flink 实时"]
    L2 --> R["Replay 批式读同一 log"]
    F --> OUT["下游"]
    R --> OUT
  end

工程判断:两套代码(Lambda)的维护成本 vs 单流 replay 的资源成本。本系列后续章节提供 checkpoint、state backend、EOS 工具,使 Kappa 路径在生产上可运维;不在此篇宣判某种架构「胜出」。


四、流表对偶与有状态计算

流表对偶(stream-table duality,Flink Documentation Stream Table Duality;Dataflow Model)指:

有状态流算子在做的事,往往是 维护一张不可全量物化的「动态表」

算子 动态表语义
keyBy + sum 按 key 聚合的累加列
滚动窗口 count 每个窗口区间一行聚合结果
流式 join 两表按 join key 的临时匹配状态

状态 就是这张动态表的 物理存储(内存 HashMap 或 RocksDB LSM,第 9、12 篇)。批 SQL 的 GROUP BY dt 每批重扫;流窗口 state 驻留到 watermark 关闭窗口,磁盘与 checkpoint 体积随 key 基数 × 窗口个数 增长(第 3、13 篇)。

4.1 无状态 vs 有状态算子

类型 示例 失败恢复
无状态 mapfilter、无状态 flatMap 重放上游即可
有状态 keyBy 后聚合、窗口、ProcessFunction + ValueState 必须恢复 state + offset

无状态算子 chain 在单 Task 内 forward 传递(第 7 篇);一旦 keyBy,state 按 KeyGroup 分片,shuffle 不可避免(第 8 篇)。

4.2 与 OLTP 的边界

postgresql-kernel 系列 的行存引擎在 进程内 B-Tree 上维护事务一致性;流 state 在 分布式算子 上维护 最终一致或 exactly-once 语义下的派生视图。流处理 不替代 OLTP;CDC 把 OLTP 变更 投影 到 log 再算(lakehouse/19 + 本系列 16–17)。


五、全系列地图:四层栈

本系列 18 篇按 传输 → 计算 → 语义 → 衔接 四层组织(详见 系列 index):

flowchart TB
  subgraph transport ["传输层 Kafka 4–6"]
    K4["04 日志与分区"]
    K5["05 ISR 与 Consumer"]
    K6["06 事务 Producer"]
  end
  subgraph compute ["计算层 Flink 7–13"]
    F7["07 运行时"]
    F8["08 DataStream"]
    F9["09 Keyed State"]
    F10["10 Checkpoint"]
    F11["11 Savepoint"]
    F12["12 RocksDB Backend"]
    F13["13 状态调优"]
  end
  subgraph semantics ["语义层 14–15"]
    S14["14 交付语义"]
    S15["15 两阶段提交 EOS"]
  end
  subgraph integrate ["衔接层 16–18"]
    I16["16 Debezium CDC"]
    I17["17 流式入湖深化"]
    I18["18 背压与引擎对照"]
  end
  BASE["01–03 流处理基础"] --> transport
  BASE --> compute
  transport --> semantics
  compute --> semantics
  semantics --> integrate
  LH["lakehouse/19 入湖侧"] -.-> S15
  LH -.-> I17
  LSM["lsm-tree RocksDB"] -.-> F12

5.1 第一部分:流处理基础(第 1–3 篇)

核心问题
本篇 批/流/微批、日志模型、Lambda/Kappa、系列地图
第 2 篇 事件时间、processing time、watermark、迟到数据
第 3 篇 滚动/滑动/会话窗口、Trigger、Evictor

读完 1–3 篇,应能回答:为什么乱序下仍要定义 watermark、窗口 state 与批式 GROUP BY 差在哪

5.2 与先修系列的衔接

先修 本系列如何使用
lakehouse/19 入湖 Writer/Committer;本系列讲引擎侧 watermark/checkpoint
lsm-tree MemTable/SSTable 对照 RocksDB state backend(12–13)
distributed 日志复制、一致性词汇对照 ISR / EOS

六、端到端数据流:一个 CDC 管道实例

把抽象叠到一个具体形状(不写虚构延迟数字):

sequenceDiagram
  participant DB as MySQL
  participant DEB as Debezium
  participant K as Kafka
  participant F as Flink
  participant ICE as Iceberg

  DB->>DEB: binlog 事件
  DEB->>K: append CDC topic
  K->>F: Source 消费 partition
  Note over F: keyBy + 窗口 / 清洗<br/>state + watermark
  F->>F: checkpoint barrier 对齐
  F->>ICE: 2PC 提交 snapshot
  1. Debezium 把行级变更写成 Kafka record(第 16 篇)。
  2. Flink事件时间 开窗或去重,状态在 RocksDB第 12 篇)。
  3. checkpoint 完成 后 Iceberg committer 提交新 snapshot(第 15、17 篇),与 lakehouse/19 的入湖协议对齐。

故障点分布在每一环:Kafka ISR 收缩、Flink checkpoint 超时、Iceberg commit 冲突——第 18 篇 收束诊断清单。


七、交付语义预览:为什么「日志 + checkpoint」不够

Exactly-once 不是 Kafka 或 Flink 单独保证的,而是 Source 语义 × 引擎语义 × Sink 语义 的组合,端到端由 最弱环 决定(第 14 篇):

层级 典型机制
Source offset 提交与 checkpoint 绑定
引擎 barrier 对齐 state 快照
Sink 幂等写、事务、2PC

Kafka 事务 producer第 6 篇)与 Flink 两阶段提交 sink第 15 篇)衔接,才能把 「算过了」「写进了」 钉在同一一致性点。本篇只建立词汇;证明与配置留到后文。


八、阅读路径建议

读者背景 建议路径
数据平台端到端 1 → 4 → 7 → 10 → 15 → 17 → 18
从 lakehouse/19 来 2 → 3 → 10 → 15 → 17
Kafka 运维转流计算 4 → 5 → 6 → 7 → 10 → 14
只关心窗口与乱序 1 → 2 → 3 → 8

九、有界流与无界流

Flink 把输入分为 boundedunbounded(来源:Flink Documentation,Overview):

类型 含义 典型 Source 作业结束
无界 持续到达,无预设 end Kafka、MQTT、Socket 手动 cancel 或失败
有界 读完即结束 文件 batch read、有限历史 replay FINISHED 状态

有界流 可以 用流引擎跑:读 HDFS/NAS 上固定目录,event time 仍有效——相当于 对静态数据集做流式解释。批引擎与流引擎的边界在此 模糊:Flink 批模式(Batch 执行模式)与 DataStream 读有界源 共享算子库,差异在 调度与 shuffle 实现(本系列不展开 Batch 模式调优)。

无界流作业必须面对 checkpoint foreverstate TTL第 9 篇)、lag 监控observability 系列)。Kafka log 物理上可删除旧 segment(retention),与 算子 state 保留策略 独立:offset 已 checkpoint 的数据仍可删,但 replay 窗口 受 retention 限制。

stateDiagram-v2
  [*] --> Unbounded: Kafka 持续 append
  [*] --> Bounded: 文件 Source EOF
  Unbounded --> RUNNING: 长期 RUNNING
  Bounded --> FINISHED: 读完后 FINISHED
  Unbounded --> CANCELED: 运维 cancel

Kappa 历史回补 常把 有界 replay(从旧 offset 读)与 无界 tail 接成同一作业逻辑——log retention 必须 覆盖回补区间


十、状态、时间与语义:三维依赖

流作业设计常在三轴上同时做决策:

flowchart TB
  T["时间语义<br/>event / processing"]
  S["状态形态<br/>无 / keyed / 窗口"]
  D["交付语义<br/>ALO / EOS"]
  T --> S
  S --> D
  T --> D
组合 说明
Event time + 窗口 state + EOS 实时 KPI 对账默认路径(本系列主线)
Processing time + 无 state + at-most-once 监控探针、允许丢失
Event time + 长 session state + ALO 需下游幂等;checkpoint 仍保 state

时间语义 决定窗口边界(第 2–3 篇);状态 决定 checkpoint 体积与 RocksDB 压力(第 9–13 篇);交付语义 决定 Kafka / 湖 sink 如何配置(第 14–15 篇)。三维 不可单独选型


十一、本系列边界与不展开话题

系列 index 一致,下列内容 刻意不写

不展开 原因
Flink SQL / Kafka Streams DSL 大全 机制篇用 DataStream 钉概念
Spark DStream 遗留 API 业界迁移到 Structured Streaming,仅 第 18 篇 对照
云厂商 Managed Flink / MSK 内部实现 与开源内核机制无关
Pulsar / Redpanda 独立成篇 第 18 篇 一句对照

承诺展开:Kafka 3.x log + ISR、Flink checkpoint + RocksDB state、EOS 与 lakehouse/19 入湖对齐、Debezium CDC(第 16 篇)、背压与故障(第 18 篇)。


十二、本地实验入口(可选)

下列步骤 未在本写作环境执行,读者可在 Docker Compose(Kafka KRaft + Flink 1.20+)下复现「日志 + 有状态计数」最小闭环:

# 1. 启动 Kafka + Flink(使用官方或 flink-docs 示例 compose,版本自行 pin)
# 2. 创建 topic
kafka-topics.sh --create --topic clicks --partitions 3 --replication-factor 1 ...

# 3. 提交 Flink WordCount 或自带 KafkaSource 的 keyBy+sum 示例
flink run -p 3 examples/streaming/WordCount.jar

# 4. 用 console producer 写入 JSON:{"user_id":"u1","ts":1710000000000}
# 5. 观察 Flink Web UI:Source subtask lag、算子背压(背压详解见第 18 篇)

实验目的:验证 log 可重放、offset 与 subtask 绑定、有状态算子重启后从 checkpoint 继续——不在此篇采集吞吐数据。


十三、术语表

术语 含义
Event time record 业务发生时间,由字段携带
Processing time 算子机器本地时钟处理时刻
Watermark 事件时间进度标记,驱动窗口关闭(第 2 篇)
Checkpoint 分布式一致性快照,含 state + offset(第 10 篇)
Keyed State 按 key 分片的有状态存储(第 9 篇)
Consumer lag 消费 offset 与 log end 的差
EOS End-to-end exactly-once 语义(第 14–15 篇)
Changelog 表变更流,insert/update/delete 序列

十四、常见问题

问题 简短回答 深入
流处理能否完全取代批? 否;历史回填、超大 join、低成本 ad-hoc 扫描仍偏批 第三节
Kafka 是否必须? 本系列以 Kafka 为主线;其他 log(Pulsar 等)在第 18 篇对照一句带过 第 4–6 篇
入湖小文件谁负责? 引擎 checkpoint 间隔 + 表 compaction 治理共同决定 lakehouse/17、本系列 17
Flink SQL 学不学? 本系列用 DataStream 讲机制;SQL 为表层语法,不单独成篇 index 边界说明

十五、小结

流处理的核心不是「把批 job 跑快一点」,而是在 无界、乱序、可重放日志 上定义 何时输出、状态存哪、如何容错。Kafka 提供 分区有序的可持久 log;Flink 提供 有状态计算与 checkpoint;EOS 与入湖需要 与 lakehouse 表提交协议对齐lakehouse/19、本系列 15、17)。

下一篇进入 事件时间、处理时间与 watermark:三种时间语义如何在同一作业里共存,以及乱序下窗口何时该关闭。


参考资料

  1. Apache Kafka Documentation, Design(commit log、分区有序性)。
  2. Apache Flink Documentation, Batch and Stream Processing / Stream Table Duality
  3. Akidau, T. et al., The Dataflow Model(事件时间、watermark、窗口抽象)。
  4. Kreps, J., Questioning the Lambda Architecture(Kappa 与 log replay 论述,B 级工程观点)。
  5. 本系列 index(18 篇依赖与阅读路径)。
  6. lakehouse 第 19 章(入湖侧与引擎侧分工)。
  7. distributed 系列(日志复制与一致性直觉)。

返回 系列目录 | 上一篇:系列 index | 下一篇:事件时间、处理时间与 Watermark

同主题继续阅读

把当前热点继续串成多页阅读,而不是停在单篇消费。

2026-07-01 · database / distributed

【流式数据处理】背压、故障模式与引擎对照

收束流式数据处理系列:Flink credit-based 背压如何沿算子链传播、Web UI 指标怎么读;数据倾斜、checkpoint 超时连锁、Kafka rebalance 风暴、RocksDB OOM、savepoint 不兼容五类生产故障的诊断与止血;Flink / Kafka Streams / Spark Structured Streaming / RisingWave 在状态模型、交付语义、运维与入湖成熟度上的对照表与选型决策树,不做排名。

2026-07-01 · database / distributed

【流式数据处理】Kafka · Flink · 状态 · Exactly-Once

承接数据湖流式入湖:从 Kafka 日志与副本语义,到 Flink 事件时间、watermark、窗口、RocksDB 状态与 checkpoint,再到端到端 exactly-once 与 Debezium CDC 入湖。面向数据平台与实时工程师,补全批式湖仓之外的实时计算层。

2026-07-01 · database / distributed

【流式数据处理】Checkpoint 机制:Barrier 对齐与一致性快照

从 Chandy-Lamport 分布式快照到 Flink aligned/unaligned checkpoint:CheckpointCoordinator 触发—ack—完成生命周期,Kafka source 如何把 partition offset 写入 checkpoint,以及 interval、timeout、min-pause、concurrent checkpoints 的调优边界。

2026-07-01 · database / distributed

【流式数据处理】交付语义:从 at-most-once 到 exactly-once

用 Source、引擎、Sink 三层模型拆解 at-most-once、at-least-once、exactly-once 的组合规则与最弱环决定律;对照 Flink checkpoint 模式、Kafka 事务与幂等 producer、重复消费/重复写入的三类修复手段,为两阶段提交 sink 铺垫。


By .