想象一家餐厅曾经有两座厨房:一座专门做堂食,炉火极旺,客人坐下五分钟就上菜;另一座专门做外卖盒饭,一批一批预制,天黑前统一装车。两座厨房各自备货、各自记菜单,客人问“堂食和外卖是不是同一锅菜”,店长翻遍两本账也答不上来。流批一体就像是把双厨房改造成一座中央厨房——实时链路是厨师现炒,批量链路是提前备餐,而两者共用同一份菜单、同一个货架、同一套下单系统。Flink 是传送带和灶台,Iceberg/Paimon 是智能货架,CDC 是收银台广播,数据在这座厨房里只有一份,既能被实时点单读取,也能被夜间盘点复用。
对 AI 从业者而言,掌握流批一体意味着你能用一套技术栈同时解决实时报表与离线数仓的割裂问题,真正成为懂数据管道、懂表存储、懂运维调优的复合型数据工程师。
目录
可视化演示
演示Flink与Iceberg/Paimon湖仓融合的流批一体架构,统一实时与离线数据链路,对比实时吞吐、批量吞吐、延迟与存储统一性。
本节配有交互动效(进入视口后自动加载,约 3KB)。
流批一体架构 实时 批量 延迟 存储 统一实时与离线数据链路
架构设计:一座中央厨房
一家餐厅以前有两座厨房:一座专做堂食,炉火极旺,客人刚落座五分钟就出菜;另一座专做外卖盒饭,批量预制,天黑前统一装车。两座厨房各存各的食材,各记各的菜单,客人问“堂食和外卖是不是同一锅菜”,店长翻遍两本账也答不上来。流批一体就是要把两套厨房合并成一座中央厨房:实时链路是厨师现炒,批量链路是提前备餐,但两者共用同一份菜单、同一个储物架、同一个下单系统。
最早的“双厨房”方案也有个名字,叫 Lambda 架构。Lambda 架构维护实时、批量两套代码,结果经常出现同一个指标两套数字。流批一体用 Flink 解决这个问题:Flink 把批处理当成“有边界的流”,于是流计算和批计算可以共用同一套 SQL、同一套算子、同一套状态管理。Iceberg/Paimon 这类 Lakehouse 表格式再负责把数据文件组织成带版本的快照,实时和批量都在这份快照上读写。用一句话说,就是“存储只有一份,计算语义统一”。
那统一之后的最终结果怎么算出来?可以拆成两个阶段:先对某个历史快照做一次全量批计算,再对快照之后的新事件做增量流计算,最后把两者合并。
客人最后吃到的菜,既有提前备好的一半,也有现炒的一勺;不再需要两个厨房分别解释“为什么口味不一样”。架构层级的统一,就是流批一体的第一块地基。
- 基线快照\(S_{t_0}\) 是 \(t_0\) 时刻已经完整落盘的湖表快照,好比开餐前备好的半成品食材。
- 批量查询\(Q_{\text{batch}}\) 用批量方式扫全量快照,得到一份稳定的历史结果,如同把半成品统一预加工成基础菜底。
- 增量区间\([t_0, t]\) 表示从快照时间到当前时刻之间产生的新变化事件,像客人陆续追加的点单。
- 流式查询\(Q_{\text{stream}}\) 对这些增量事件做实时计算,不断补上新变化。
- 合并结果\(\oplus\) 表示把“备好的基础菜底”和“现炒的新菜”合并成一盘完整的菜。
Lakehouse 选型:冷库还是保鲜柜
中央厨房需要一个大到能塞下整头猪的冷库,也需要一个能按小格子精细取用的保鲜柜。传统数据湖像大仓库,什么都能堆;传统数据仓库像精装超市,货架整齐但扩建很贵。Lakehouse 把两者合二为一,而 Iceberg 和 Paimon 就是两种布置这家“仓储会员店”的货架系统。
Iceberg 把表拆成元数据、清单和数据文件三层,每一次写入都生成一个新快照,读的时候只看对应快照里的文件列表。Paimon 更进一步支持主键表,像收银台小票一样按主键不断覆盖旧值,天然适合实时流水灌入后还要做更新的场景。选哪种货架,取决于你更看重“开放格式的广泛生态”,还是“高频 Upsert 的流式写入便利”。
这张货架本质上是把一堆散落的数据文件组织成一个表版本。用公式看就是:一个表的快照由若干清单组成,每个清单又指向若干数据文件。
选对货架,后厨才能做到“同一份食材,实时炒一盘,批量冻一箱”。Iceberg 生态更成熟,Paimon 流式主键更顺手,具体项目中往往按主键更新频率和查询引擎来定。
- 当前快照\(T_t\) 标识 \(t\) 时刻用户能查到的完整表版本,像冷库某一刻的库存清单。
- 清单集合\(\{ m_1, m_2, \dots, m_k \}\) 是元数据清单列表,记录这个快照涉及哪些文件组。
- 单个清单\(m_j\) 负责管理同一批写入产生的数据文件索引。
- 数据文件集合\(\{ f_1, f_2, \dots, f_n \}\) 是真正存着行数据的列式文件,如 Parquet/ORC。
- 表格式价值通过这种“快照指向清单、清单指向文件”的层级,系统可以实现 ACID 事务和时间旅行。
CDC 数据捕获:收银台的变价广播
收银台发现 101 桌加了一份菜,不会让整个后厨重新盘点食材,而是广播一句“101 桌加一份宫保鸡丁”。厨师只需要在这张小票上打补丁,不用把整本菜单重印。数据库里的变化,也应当是这种“变价广播”,而不是每天全表扫描对比——这就是 CDC 的核心。
CDC 的全称是 Change Data Capture,翻译过来是“变更数据捕获”。Flink CDC 连接器会去解析 MySQL 的 Binlog 或 PostgreSQL 的 WAL 日志,把每一次插入、更新、删除都变成一条流式事件。更厉害的是,它支持“先全量、后增量”:先做一次快照把历史数据读进来,同时记录当时的日志偏移量,快照结束后无缝切换成增量监听,中途不丢数据。
一条 CDC 事件可以用下面的结构描述:哪个主键、什么操作、改动前后的值,以及发生时间。
收银台广播代替了每天盘库,后厨不必等晚上统一对账也能知道食材变化。CDC 就是把流批一体从“定时批量更新”升级成“变化即时感知”的关键传声筒。
- 主键标识\(k\) 指明哪一行数据发生了变化,像广播里喊的桌号。
- 操作类型\(\text{type}\) 表示插入、更新还是删除。
- 旧值\(v_{\text{old}}\) 是改之前的字段值,便于下游做撤销或对比。
- 新值\(v_{\text{new}}\) 是改之后的最新字段值。
- 时间戳\(ts\) 标记事件发生时间,用于排序、水位线和延迟统计。
管道工程实践:后厨传送带的搭法
中央厨房的传送带不能再一根一根单独装,否则堂食和外卖各占一路,堵成一团。数据管道工程实践要解决的是:如何把 CDC 事件、Flink 计算、Lakehouse 表存储连成一条不堵的流水线。
典型做法是:Flink CDC 从业务库捕获变化,交给 Flink SQL 做清洗、关联、聚合,最后写入 Paimon 主键表。Paimon 表以主键控制 Upsert,新事件来了自动覆盖旧记录;Iceberg 则更常用在“大量追加、按快照读取”的场景。工程上还要设计好任务拓扑:哪张表先同步、哪条流先 join、下游大屏与报表共用哪张物理表,都要像选择“先切菜还是先热锅”一样有顺序。
流批一体的 Upsert 管道本质上是一个不断合并增量的过程。已有表和增量事件合并时,要先把被覆盖的旧行去掉,再写入新行。
传送带搭好之后,实时大屏读到的和离线报表查到的,来自同一张主键表。后厨不再为“堂食版本”和“外卖版本”各备一套账,工程维护量自然肉眼可见地下降。
- 旧状态\(R_t\) 是当前表里已经存在的完整数据,像冰柜里已有的半成品库存。
- 增量事件\(\Delta\) 是一批新增或变更记录,如刚到的外卖订单。
- 找出旧版本\(\mathrm{old}(R_t, \Delta)\) 是被增量按主键命中的旧行,需要先移除。
- 差集\(R_t \setminus \mathrm{old}(R_t, \Delta)\) 表示留下的未变化数据。
- 并上新值\(\cup \Delta\) 把新记录合并进表,完成一次 Upsert。
性能调优:把火力开大但不烧锅
厨师以为把灶台火力调到最大就能更快出菜,结果锅烧糊了,油烟报警器响成一片。流批一体性能调优不是无脑加资源,而是协调灶眼数量、备菜方式、排队秩序和存储布局。
并行度就像灶眼数量:数据量大就多加几个灶眼,但灶眼之间要 shuffle 传输数据,加太多反而被网络洗牌拖慢。小文件治理像把碎肉揉成肉饼:太多小文件会让 Lakehouse 读的时候频繁打开文件,所以要定期做 Compaction。状态后端调优则像把常用调料从最远的货架挪到灶台边:RocksDB 适合大状态,内存状态适合小状态。数据倾斜则像某几个灶眼排队特别长,需要加盐拆分、两阶段聚合,让队伍重新平均。
一条 Flink 作业的端到端耗时可以约等于“总记录数除以有效并行度和单算子吞吐”,再加上网络洗牌时间。
调优不是一味开大火,而是让每个灶眼、每份食材、每条传菜路线都均衡。性能优化之后,实时流和批量流才能在同一套资源上和平共处,而不是互相抢锅。
- 总记录数\(N\) 是本次作业需要处理的全部数据量,像一批待洗的菜。
- 有效并行度\(P_{\text{eff}}\) 是“实际能同时干活的灶眼数”,受资源和数据分布共同限制。
- 单算子吞吐\(\mu\) 是每个并行子任务每秒能处理多少条数据,像一位厨师的出菜速度。
- 网络洗牌\(T_{\text{shuffle}}\) 是数据在算子之间传输消耗的时间,像传菜员来回跑动的时间。
- 总耗时\(T_{\text{end}}\) 是最终端到端延迟,目标是让它落在业务可接受的范围内。
运维监控:厨师长的巡检台
厨师长不需要每个灶台都亲手炒,他站在巡检台前看几个关键仪表:炉温正不正常、排队积压多不多、冷藏室温度有没有超标。流批一体的运维监控也一样,要看作业流量、Checkpoint、数据新鲜度和表快照的生成情况。
Flink 最核心的“炉温表”是 Checkpoint。系统每隔一段时间把状态和偏移量做一次全局快照,故障后从最近一次成功快照恢复,这就是“锅底备份”。监控面板上要盯住 checkpoint 是否成功、耗时是否增长、有没有频繁失败。另一个关键指标是数据新鲜度——最新一条数据写入湖表后,仪表盘上延迟了多少秒。配合指标看板,还要配置告警规则:checkpoint 失败、Kafka 消费延迟突增、Paimon 提交中断,都要立刻通知值班厨师。
这些监控指标可以用一个朴素的新鲜度公式表达。
厨师长有了巡检台,就不再每天提心吊胆地问“实时任务还活着吗”。监控和告警让人先于用户发现问题,这是流批一体生产可用的最后一块拼图。
- 当前时间\(t_{\text{now}}\) 是此刻的时钟,像厨师长抬头看挂钟。
- 快照时间\(t_{\text{snapshot}}\) 是湖表最近一次可见数据版本的时间戳。
- 数据新鲜度\(\text{Freshness}\) 是两者之差,越小说明数据越新鲜。
- 检查点时间\(t_{\text{lastCheckpoint}}\) 是最近一次成功 Checkpoint 的时间。
- 恢复点目标\(\text{RPO}\) 近似表示故障后最多要回退多久的数据。
演进趋势:下一道菜长什么样
这家中央厨房接下来还有什么新菜?不再是单独追求“更快”,而是追求“更省事”:用 SQL 表达一切、用开放表格式连接一切、用云原生弹性部署一切。Flink 已经支持流批一体的 SQL API,Iceberg/Paimon 不断扩展生态,Catalog 服务让各种引擎共享同一份表元数据,未来的数据平台会越来越像“插上电就能出餐的标准厨房”。
从成本结构看,流批一体演进的核心动力是把重复建设降到最低:不需要两份存储、两套作业、两支维护队伍。用一个简单的成本模型看,总成本由引擎成本、存储成本和运维成本加权相加。
中央厨房不会倒退成两间小厨房。未来是开放格式、流批引擎、云原生调度互相融合的时代,谁先把菜单、货架和厨具统一起来,谁就能更快端出下一道菜。
- 引擎成本\(C_{\text{engine}}\) 是实时和批量计算引擎的研发与资源成本。
- 存储成本\(C_{\text{storage}}\) 是数据重复存储的成本,双份数据比一份贵一倍。
- 运维成本\(C_{\text{ops}}\) 是任务部署、监控、排查问题的成本。
- 权重系数\(\alpha\)、\(\beta\)、\(\gamma\) 表示不同成本在总成本里的重要程度。
- 总成本\(C_{\text{total}}\) 是平台综合成本,流批一体通过降低 \(\beta\) 和 \(\gamma\) 让总账更好看。
AI 应用场景
场景一:电商实时大屏与离线报表口径统一
阶段:统一数仓建设
解决的问题:过去电商平台用两套链路分别供实时大屏和 T+1 报表,实时说销售额 100 万,离线说 98 万,运营天天扯皮。用 Flink CDC + Paimon 后,业务库的订单变更实时流入 Paimon 主键表,大屏直接读表最新快照,离线报表读同一张表的批量快照,口径终于一致。
具体案例:某零售平台把 MySQL 订单表和支付流水表接入 Flink CDC,写入 Paimon 并按订单 ID 做 Upsert;实时大屏看今日 GMV,凌晨批任务扫同一张表出日报,两份数字完全对得上。
场景二:金融交易对账与风控回溯
阶段:实时风控与历史审计
解决的问题:交易系统既要毫秒级识别可疑交易,又要在月底接受审计回溯。过去实时风控和离线对账分属两套数据链路,互相不一致。用 Iceberg 的快照机制保存交易流水,Flink 实时扫新增数据做风控,审计员用 Spark 查同一个 Iceberg 表的某个历史快照完成回溯。
具体案例:某支付公司使用 Flink + Iceberg,将交易事件写入带版本快照的湖表;风控模型消费实时流拦截异常,审计系统通过 SNAPSHOT 时间旅行查询某一秒的历史全貌。
场景三:数据中台简化升级
阶段:平台架构演进
解决的问题:传统数据中台为每个实时需求单独建 Kafka Topic、Redis 结果表,并给离线需求另建 Hive 分区表,链路重复且运维成本高。流批一体把“实时结果”和“离线结果”收敛到一张主键表,下游统一通过表或 Catalog 读取。
具体案例:某互联网公司把用户行为实时指标从“Flink→Kafka→Redis”与“Flink→Hive”两条链路,替换成“Flink→Paimon 主键表”,大屏、报表、推荐服务都从表读,存储成本下降约 40%。
Python 演示
from dataclasses import dataclass @dataclassclass CdcEvent: op: str key: int value: int ts: float class PaimonTable: def __init__(self): self._rows = {} self._commits = 0 def batch_overwrite(self, rows): self._rows = {r[0]: r[1] for r in rows} self._commits += 1 def apply_cdc(self, event): if event.op == "U": self._rows[event.key] = event.value elif event.op == "D": self._rows.pop(event.key, None) self._commits += 1 def query(self): return dict(self._rows) class UnifiedPipeline: def __init__(self): self.table = PaimonTable() def run_batch(self, snapshot): self.table.batch_overwrite(snapshot) def run_stream(self, events): for event in events: self.table.apply_cdc(event) def latest(self): return self.table.query() if __name__ == "__main__": pipeline = UnifiedPipeline() pipeline.run_batch([(101, 10), (102, 20), (103, 30)]) cdc_events = [ CdcEvent("U", 101, 12, 1.0), CdcEvent("D", 102, 20, 1.2), CdcEvent("I", 104, 40, 1.5), ] pipeline.run_stream(cdc_events) print(pipeline.latest())
class PaimonTable模拟 Lakehouse 主键表,内部用字典保存最终可见行。def batch_overwrite(self, rows)模拟批量任务把全量快照一次性写入表。def apply_cdc(self, event)模拟 Flink 消费 CDC 事件,按主键做更新或删除。if event.op == "U"更新事件直接覆盖主键对应的旧值,对应 upsert 语义。elif event.op == "D"删除事件把主键对应的行移除。class UnifiedPipeline模拟一套同时容纳批处理与流处理的统一数据管道。pipeline.run_batch(...)先执行离线批量同步,建立基线。pipeline.run_stream(...)再消费增量事件,完成实时更新。print(pipeline.latest())最终实时和批量共用同一张表,得到一致结果。
常见误区
误区很多初学者认为用了 Flink + Iceberg/Paimon,所有离线任务都可以改成实时任务,延迟越低越好,批处理可以彻底取消。
正解流批一体的意思是“一套逻辑、一份存储、两种执行模式”,不是否定批处理。夜间全量回刷、数据订正、大促复盘仍然需要批任务。湖表快照本身就是给批量计算保留的稳定视图。就像中央厨房仍然需要提前备餐,不可能每道菜都从零现杀。
为何常见:因为“流批一体”听起来像“流代替批”,加上市场上大量文章强调实时,让人误以为批处理会被淘汰。
误区有人看到 Paimon 支持流读流写,认为可以直接把 Kafka 干掉的,用表来当消息队列。
正解Kafka 是消息管道,负责短时消息缓冲和订阅分发;Iceberg/Paimon 是表存储,负责文件组织、快照和 ACID 事务。Flink 作业之间的实时传输还是要靠 Kafka 或类似通道。把 Paimon 当 Kafka 用,既得不到极低延迟,还会制造大量小文件。
为何常见:看到“Paimon 支持变更日志读取”这类特性,容易过度联想成“消息队列 + 表”二合一。
误区部分初学者以为 Flink CDC 只是一个“搬家工具”,把 MySQL 数据搬到另一个库或者冰湖里就结束了。
正解Flink CDC 更准确说是“变更事件源”,它输出的每条记录都带主键、操作类型、新旧值和时间戳,供下游做实时计算、维表关联、审计回溯。它不只是一次性数据迁移,而是流批一体管道的“传感器”。就像收银台广播不是搬货,而是通知后厨去应变。
为何常见:因为 CDC 常与数据同步一起出现,加上“全量 + 增量”的说法很像数据复制,所以容易被窄化。
误区有人以为 Flink 的 Checkpoint 一开,整个实时管道就天然不重不丢。
正解Checkpoint 只保证 Flink 内部状态的一致恢复,端到端精确一次还需要源端记录偏移量、下游写入支持幂等或事务,尤其要处理好 Lakehouse 表的主键去重。Paimon 主键表和 Iceberg 的事务性 sink 正是为了配合这一点。否则,上游重放时下游仍可能写重复数据。
为何常见:官方文档常把 Checkpoint 与 exactly-once 放一起讲,让人以为“开一个开关”就万事大吉。
误区初学者做技术选型时只问“Flink 版本多少”,把 Iceberg/Paimon 当成普通文件格式,觉得随便选一个都行。
正解Flpushable 提供引擎能力,而表格式决定数据如何被组织、如何被多个引擎共享、如何做 Upsert 和时间旅行。选 Iceberg 还是 Paimon,直接影响实时主键更新、小文件合并、下游引擎兼容等生产问题。表格式是流批一体真正落地的“货架系统”,不能轻视。
为何常见:因为 Flink 名气最大,初学者容易把“流批一体”简单等同于“用 Flink”,而忽略存储侧的关键选择。
面试题
Q1. 湖仓表格式(Iceberg/Paimon)最核心的作用是什么?
- A.加快服务器之间的网络传输
- B.提供流批统一的读写语义← 正确答案
- C.完全替代 Flink 计算引擎
- D.把消息全部落成日志文件
答案:B。Lakehouse 表格式通过快照、清单和文件组织,让实时流和批量计算读写同一份数据,这正是流批一体的存储基础。它不负责网络传输,也替代不了 Flink。
Q2. Flink CDC 捕获数据库变化最依赖什么?
- A.定期执行全表扫描比对
- B.解析数据库的变更日志← 正确答案
- C.直接复制数据库物理文件
- D.使用 Redis 保存临时快照
答案:B。Flink CDC 通过解析 MySQL Binlog / PostgreSQL WAL 等变更日志来捕获插入、更新、删除事件,而不是轮询全表。这样延迟低、增量小,也支持全量加增量的一体化读取。
Q3. 下列哪些属于 Lakehouse 表格式的典型能力?
- A.支持 ACID 事务← 正确答案
- B.支持小文件自动合并← 正确答案
- C.自动给用户发报警短信
- D.支持快照和时间旅行← 正确答案
答案:ABD。ACID、小文件合并、时间旅行都是 Iceberg/Paimon 的核心能力。发短信属于监控告警系统的工作,和表格式无关。
Q4. 流批一体相比 Lambda 架构的最大变化是什么?
- A.用一套代码统一两条链路← 正确答案
- B.必须更换底层消息队列
- C.从今以后不再需要批处理
- D.只能处理有界的数据流
答案:A。Lambda 需要维护实时和批量两套代码,流批一体让 Flink 同时处理有界与无界数据,一套 SQL、一套算子覆盖两条链路,减少口径不一致和运维成本。
Q5. Flink 检查点主要解决什么问题?
- A.防止源端数据库宕机
- B.任务故障后的状态恢复← 正确答案
- C.加速 Kafka 消息消费
- D.删除湖表中的旧文件
答案:B。Checkpoint 周期性保存算子状态和源端偏移量,故障时从最近一次成功快照恢复。它不防止数据库宕机,也不负责合并小文件。
Q6. 处理 Flink 数据倾斜的常用手段有哪些?
- A.重新设计算子并行度← 正确答案
- B.对热点键加盐后拆分← 正确答案
- C.彻底关闭检查点机制
- D.使用两阶段聚合去重热键← 正确答案
答案:ABD。调整并行度、热点键加盐、两阶段聚合都能缓解倾斜;关闭 Checkpoint 只可能导致故障丢状态,不是调优手段。
Q7. Paimon 主键表最典型的使用场景是什么?
- A.保存流式变更后的结果表← 正确答案
- B.完全替代 Kafka 消息队列
- C.只做离线全量数据查询
- D.管理 Flink 集群机器资源
答案:A。Paimon 主键表天然支持按主键 Upsert,适合实时流反复更新同一行数据的场景,比如订单状态、用户画像。它不是消息管道,也不是资源调度系统。
Q8. Iceberg 元数据链路中,负责归并某一批数据文件索引的是哪个层级?
- A.根元数据文件
- B.Manifest 清单文件← 正确答案
- C.底层基础数据文件
- D.流式增量日志文件
答案:B。Iceberg 的层级是“元数据文件 → manifest list → manifest → 数据文件”。manifest 清单实际指向并统计一批数据文件,从而实现快照读和文件级裁剪。
Q9. 要实现 Flink 端到端精确一次,通常需要哪些条件配合?
- A.源端记录并恢复读取位点← 正确答案
- B.下游使用幂等或事务写入← 正确答案
- C.取消状态后端的持久化
- D.开启 Checkpoint 与屏障对齐← 正确答案
答案:ABD。端到端精确一次是源端、引擎、下游三方协同的结果。Checkpoint 保证状态恢复,源端位点防止重放乱序,下游幂等或事务防止重复写入。取消状态持久化只会破坏一致性。
Q10. 流批一体中“一张表两种读”指的是什么?
- A.实时和批量的统计口径完全分裂
- B.实时与批量读同一张湖表← 正确答案
- C.让批处理永远优先于实时
- D.用两张表分别存储两类数据
答案:B。实时任务读 Lakehouse 表的最新快照,批量任务读同一张表的某个历史快照,底层数据只有一份,这是流批一体与 Lambda 的关键区别。