实时数据管道的三块积木:Pipe、Table Stream、Dynamic Table
构建实时数据管道,云器 Lakehouse 给了三个对象:Pipe、Table Stream、Dynamic Table。它们常被当成"三选一",其实大多数时候不是替代关系,而是各管一段、拼成一条管道。先认清每块积木干什么、怎么拼,比纠结"选哪个"更重要——真正需要选的只有一处。
一条主干,加一个可选旁路
数据怎么流,先记住一条主干,再把 Table Stream 当作一个可选旁路叠上去——别把它们都塞进一条线里看。
主干(常见路径):外部数据先用 Pipe(或
COPY INTO / Studio 实时同步)进仓、变成 ODS 表 → 用 Dynamic Table 声明式加工成 DWD/DWS 结果表 → 结果被 BI、即席查询消费。这条线是大多数实时管道的默认形态:进仓、加工、查询,一路声明式,引擎自动维护。每一段的细节在后文展开。
旁路:Table Stream —— 给任意一张表装一个"变更水龙头"。 它不是主干上的某一步,而是一个按需叠加的能力:在主干上任意一张表(ODS、或结果表——动态表也行,已实测)挂一个 Table Stream,由任务消费它的增量。出路有三类:
- 回灌湖内:
进另一张湖内表——做下一层增量加工、审计留存完整变更历史、或维护 SCD Type 2 拉链表。这一路也正是"不想用 Dynamic Table、要自己写 MERGE"时的选择(详见下文"唯一要选的地方")。MERGE - 送出湖外(reverse ETL):同步到检索(Elasticsearch)、OLAP 服务层(ClickHouse / Doris)、低延迟缓存(Redis)、业务库(MySQL / PostgreSQL)、消息队列(Kafka 再分发)。
- 触发动作:告警、风控,或驱动下游业务系统。
先有表,DT 和 Stream 才能上场
Dynamic Table 和 Table Stream 都只处理已经是表的数据,它们自己不负责把外部数据弄进来。所以一条实时管道的起点,总是先用某种导入手段把外部数据变成一张表。云器 Lakehouse 进仓的方式有好几种:
- 持续、自动接住不断产生的数据(Kafka、对象存储里的新文件)→ Pipe;
- 一次性 / 批量导入 →
;COPY INTO - 从外部数据库做 CDC 同步 → Studio 实时同步。
注意一个容易混的点:对象存储里的文件虽然在"湖"(Volume)里,但还不是表,Dynamic Table 和 Table Stream 都碰不了它;得先用上面任一种方式把它导入成表,后两者才能接手。
本文讲的是持续实时场景,所以管道起点用 Pipe;它和
COPY INTO 的取舍见下一节。
Pipe:持续把外部数据导入成表
Pipe 是用 SQL 创建的持续入库对象:把 Kafka 消息、对象存储里不断上传的文件,自动、不间断地写进一张普通表。它本质是把一条
包装成常驻、自动调度、自动记录读取位点的微批导入——文件到了就进,无需你定时触发。COPY INTO
和一次性
COPY INTO 的区别就是"持续 vs 一次":历史数据一把导入用 COPY INTO;数据源源不断、要系统自动接住,用 Pipe。Pipe 靠 load_history 记录已导入文件去重,同一个文件不会重复进。
Table Stream:表的变更书签
Table Stream 不是一张存数据的表,而是一个**"读到哪了"的书签**:它记住你上次消费到源表的哪个版本,下次只把这之后的变更交给你。
构成心智模型的几条规则:
- 查询 Stream 会多一个
列:__change_type
/INSERT
/UPDATE_BEFORE
/UPDATE_AFTER
。一次DELETE
会产生UPDATE
(旧值)+UPDATE_BEFORE
(新值)两行。UPDATE_AFTER - 只有用 DML 消费才推进位点:
看多少次数据都还在;一旦SELECT
或INSERT INTO ... SELECT ... FROM stream
成功提交,位点前进,那批变更就消费掉了。事务回滚则位点不动、可重消费——这保证了不重不漏。MERGE ... USING stream - 一个 Stream 只能被一个消费者完整消费;多个下游各要一份,就各建一个 Stream(Stream 只存位点、不复制数据,多建成本很低)。
捕获增删改,STANDARD
只捕获插入、更轻量。APPEND_ONLY
Stream 也能建在 Dynamic Table 上:动态表刷新产生的变更同样能被 Stream 捕获——所以前面说的那个"可选水龙头"可以接在结果表上(哪怕它本身是动态表),把加工结果的变更送出去。消费位点、过期(STALE)等细节见 Table Stream。
Dynamic Table:声明式自维护的派生表
Dynamic Table 是在仓内做加工的主力:你写一条
SELECT 说明目标表"应该等于什么",增量怎么算、撤销怎么减、何时刷新,全部交给引擎自动维护。它专门用来搭 ODS→DWD→DWS 这类加工链路。它怎么设计、有哪几种加工形态,见 动态表设计方法。
唯一要"选"的地方:同一份加工,声明还是过程
前面说三者大多是组合关系。真正需要二选一的,只有一处:仓内的加工,用 Dynamic Table,还是 Table Stream + 任务? 两者都能把上游变更加工进目标表、保持新鲜,但方式截然不同:
- Dynamic Table = 声明结果。 你只写一条
,增量、撤销、调度、依赖顺序全由引擎维护。SELECT - Table Stream + 任务 = 编写过程。 引擎只把"变了哪些行"交给你;
怎么写、退款怎么减回去、失败怎么重试、怎么不重不漏,你自己写一套过程。MERGE
换句话说,Table Stream + 任务,正是 Dynamic Table 替你自动化掉的那套过程的手写版(这套过程的代价,见动态表设计方法开头"不用动态表你要写什么"那段)。
既然声明式这么省,为什么还要过程式?因为有些控制权声明式给不了。出现下面这些需求时,把过程拿回来,用 Table Stream + 任务:
- 自定义
/ 复合主键 upsert,或一次变更要写多个目标表;MERGE - 维护 SCD Type 2(拉链表,保留历史版本);
- 加工里要调存储过程、外部函数(动态表的
不允许);SELECT - 要对结果表直接增删改(如 GDPR 删除请求;动态表只读);
- 要 CRON 精确调度、自定义重试,或亚分钟级且逻辑特殊。
反之,只要目标能用一条标准
SELECT 表达、分钟级新鲜度够用,就交给 Dynamic Table——别自己写那套过程。完整的决策树和架构模式见实时数据管道选型指南。
各归其位
- 外部数据进仓:持续接入用 Pipe;一次性 / 批量用
;数据库 CDC 用 Studio 实时同步。COPY INTO - 仓内派生表保持新鲜、且目标能用一条
表达 → Dynamic Table。SELECT - 加工需要声明式给不了的控制(自定义 MERGE、SCD 拉链、调存储过程/外部函数、直接改结果、精确调度)→ Table Stream + 任务。
- 捕获某张表的变更、交给任务处理(回灌湖内 / 送出湖外 / 触发动作)→ Table Stream。
相关文档
- 实时数据管道选型指南:决策树、四种架构模式、完整可运行示例
- Pipe(数据管道):Pipe 类型、参数与限制
- Table Stream:变更类型、消费位点、过期机制
- 动态表设计方法:从加工目标到增量管道:声明式增量加工的四种形态
- 动态表为什么走全量、怎么保住增量:刷新模式怎么判断
