“数据就像河流,一旦流过就消失了。传统数据库试图用静态的容器去盛动态的水,而流数据库选择让计算随数据流动。”
在计算机科学的发展历程中,数据处理范式经历了两次重大变革。
批处理(Batch Processing) 是第一种被广泛采用的计算模型。在这种模式下,数据被收集起来,存储在某个地方,然后周期性地进行处理。传统的数据仓库就是批处理的典型代表——数据每天或每小时被 ETL 流程抽取、转换、加载,用户查询的是已经「凝固」的历史快照。
流处理(Stream Processing) 则代表了另一种思维:数据一旦产生就应该立即被处理,而不是等待积累到某个阈值。Kafka、Flink 等流处理框架的兴起,正是为了满足实时性的需求。
为什么流处理变得越来越重要?让我们看几个场景:
| 场景 | 批处理延迟 | 流处理延迟 |
|---|---|---|
| 电商实时大屏 | 5-15 分钟 | < 1 秒 |
| 金融交易监控 | 分钟级 | 毫秒级 |
| 异常检测报警 | 小时级 | 秒级 |
| 推荐系统特征 | 小时级 | 实时 |
当业务对「时效性」的要求越来越高,批处理的局限性就愈发明显。
然而,传统的流处理架构并不美好。一个典型的实时数据栈可能是这样的:
数据源 → Kafka → Flink → 多个数据库 → 应用↓又一个 Flink → 另一个数据库↓第三个 Flink → ...
为什么会变成这样?
原因一:状态管理需要外部存储。 Flink 等流处理引擎的内部状态需要持久化,但引擎本身不擅长做数据存储,于是不得不把结果导出到专门的数据库。
原因二:跨流计算需要消息队列。 当一个流计算的输出需要作为另一个流的输入时,就需要 Kafka 这样的消息队列来传递数据。
原因三:查询能力需要独立数据库。 流处理引擎擅长计算,但不擅长随机查询,所以又需要独立的查询数据库。
结果就是:运维五六个组件,系统间的一致性需要手工保证,故障排查困难,扩缩容更是噩梦。
流数据库的诞生,就是为了解决这个「组件膨胀」的难题。
RisingWave 是一款面向流处理的分布式数据库。理解 RisingWave,首先要理解它的几个核心概念。
在 RisingWave 中,Table 是一个有状态的数据表。与传统数据库类似,用户可以在 Table 上执行 Insert、Update、Delete 操作。
-- 创建一个物理表CREATE TABLE orders (order_id BIGINT PRIMARY KEY,user_id BIGINT,amount DECIMAL,created_at TIMESTAMP);-- 如果指定了 CONNECTOR,数据会自动同步CREATE TABLE orders_with_connector (order_id BIGINT PRIMARY KEY,user_id BIGINT,amount DECIMAL,created_at TIMESTAMP) WITH (connector = 'kafka',kafka.topic = 'orders',kafka.brokers = 'localhost:9092');
当为 Table 连接上游 Source(如 Kafka)后,RisingWave 会自动追踪数据变化。当用户在 PostgreSQL 中执行一笔订单写入时,Debezium 会捕获这条变更日志(Change Log),RisingWave 内部会同步更新对应的 Table 内容。
Table 的特点:
如果说 Table 是数据的「容器」,那么 Materialized View(物化视图) 就是数据加工的「生产线」。
传统的 View 只是一个查询的「别名」——每次访问都会重新执行底层查询。而 Materialized View 会把查询结果持久化下来:
-- 创建一个物化视图:实时统计每小时销售额CREATE MATERIALIZED VIEW hourly_sales ASSELECTdate_trunc('hour', created_at) AS hour,SUM(amount) AS total_amount,COUNT(*) AS order_countFROM ordersGROUP BY date_trunc('hour', created_at);
与传统数据库的静态物化视图不同,RisingWave 的 Materialized View 是动态持续更新的:
新订单写入 → 自动触发增量计算 → 物化视图结果实时更新
这意味着,当你在物化视图上执行 SELECT 时,返回的不是「上次查询时的快照」,而是「此时此刻的最新结果」。
物化视图的特点:
-- 级联物化视图示例CREATE MATERIALIZED VIEW user_hourly_stats ASSELECTu.user_id,u.username,h.hour,h.total_amount,h.order_countFROM users uJOIN hourly_sales h ON u.user_id = h.user_id; -- 基于上面的物化视图
这种级联能力是流处理的核心优势——将复杂的业务逻辑拆解为多个简单的步骤,每个步骤的结果都可以被复用和组合。
在理论层面,流处理有一个优雅的概念:流表对偶性(Stream-Table Duality)。
流是表的变更日志(Change Log)
表是流的物化结果(Materialized Stream)
这个对偶性可以用一个简单的等式表达:
表 = STREAM(变更日志)流 = TABLE(历史版本)
从 Table 到 Stream:
当 Table 发生 Insert、Update、Delete 时,每一条变更操作本身就是一个「事件」。这些事件组成的序列,就是 Stream。
从 Stream 到 Table:
当一个 Stream 被物化时,每一个时间点的状态就构成 Table 的一个快照。Table 是 Stream 在某个时刻的积分。
理解这个对偶性有什么用?
它帮助我们意识到:物化视图本质上是 Stream→Table 的转换,而 CDC(Change Data Capture)则是 Table→Stream 的转换。
RisingWave 正是基于这个原理,实现了流与表的无缝衔接。
流处理之所以不同于传统的数据库查询,是因为它引入了几个独特的时间相关概念。
在流处理中,数据是「无限」的——没有终止边界。但业务往往需要对某个「时间段」的数据进行聚合,这就需要时间窗口。
滚动窗口(Tumbling Window)
固定大小、不重叠的窗口。例如每小时的销售额:
SELECTwindow_start,window_end,SUM(amount) AS total_amountFROM TUMBLING(orders,INTERVAL '1' HOUR,created_at)GROUP BY window_start, window_end;
滑动窗口(Sliding Window)
窗口大小固定,但可以重叠。例如计算最近 5 分钟的移动平均:
SELECTwindow_start,window_end,AVG(amount) AS moving_avgFROM SLIDING(orders,INTERVAL '5' MINUTE,INTERVAL '1' MINUTE, -- 滑动步长created_at)GROUP BY window_start, window_end;
会话窗口(Session Window)
基于活动的窗口,当不活动超过一定时间后窗口关闭:
SELECTwindow_start,window_end,user_id,COUNT(*) AS action_countFROM SESSION(user_events,INTERVAL '5' MINUTE, -- 不活动间隔user_id)GROUP BY window_start, window_end, user_id;
在现实世界中,数据经常是乱序或延迟到达的。想象一下:用户在 10:59 提交了一笔订单,但由于网络原因,这条数据在 11:05 才被处理。
水位线是流处理中处理乱序数据的一种机制:
-- 定义水位线策略:允许最多 5 分钟的乱序CREATE SOURCE orders_source (...)WITH (watermark = 'event_time - INTERVAL 5 MINUTE');
RisingWave 会根据水位线判断:
这种机制让系统在「实时性」和「正确性」之间取得平衡。
默认情况下,物化视图的结果在窗口关闭(Window Close)时才会 Emit(输出)。但有些场景需要更早看到结果:
-- 窗口未关闭时也输出中间结果SELECTwindow_start,window_end,SUM(amount) AS running_totalFROM ordersGROUP BY window_start, window_endEMIT ON WINDOW CLOSE; -- 默认:窗口关闭时输出-- 或者每分钟输出一次(Early Emit)SELECT ...EMIT EVERY INTERVAL '1' MINUTE; -- 每分钟输出当前窗口状态
这个特性在需要「尽早看到部分结果」的场景下非常有用,比如实时监控仪表盘。
Source 是数据进入 RisingWave 的入口。与 Table 不同,Source 本身不存储数据,它只是定义了「从哪里读取数据」。
-- 创建一个 Kafka SourceCREATE SOURCE kafka_orders (order_id BIGINT,user_id BIGINT,amount DECIMAL,created_at TIMESTAMP)WITH (connector = 'kafka',kafka.topic = 'orders',kafka.brokers = 'localhost:9092',kafka.scan.startup.mode = 'earliest') FORMAT PLAIN ENCODE JSON;
支持的数据源类型:
| 类型 | 说明 |
|---|---|
| Kafka | 主流消息队列 |
| Pulsar | 云原生消息平台 |
| PostgreSQL | 通过 CDC 同步 |
| MySQL | 通过 Debezium CDC |
| S3 | 批式导入 |
Source 的数据进入 RisingWave 后,会进入内部的流处理管道。用户可以选择:
CREATE TABLE ... FROM source_nameCREATE MATERIALIZED VIEW ... FROM source_nameSink 是计算结果的输出目的地:
-- 创建一个 Kafka Sink,将实时统计结果发送到下游CREATE SINK hourly_sales_sink ASSELECThour,total_amount,order_countFROM hourly_salesWITH (connector = 'kafka',kafka.topic = 'hourly_sales',kafka.brokers = 'localhost:9092');
常见的 Sink 类型:
| 类型 | 用途 |
|---|---|
| Kafka | 传递给下游流处理系统 |
| PostgreSQL | 同步到分析型数据库 |
| ClickHouse | 进一步分析 |
| S3 | 冷存储/归档 |
一个典型的 RisingWave 数据流是这样的:
[MySQL] --CDC--> [RisingWave Table]↓[Materialized View 1]↓[Materialized View 2] --Sink--> [Kafka]↓[Materialized View 3] --Sink--> [ClickHouse]
用户只需要定义「做什么」,RisingWave 负责「怎么做」和「什么时候做」。
让我们用一张图来总结流数据库的核心概念:
┌─────────────────────────────────────────────────────────────────┐│ RisingWave 流数据库 │├─────────────────────────────────────────────────────────────────┤│ ││ ┌─────────┐ ┌─────────────┐ ││ │ Source │ ──────────────────────────▶│ Materialized│ ││ │ (Kafka) │ │ View │ ││ └─────────┘ └──────┬──────┘ ││ │ ││ ┌─────────┐ ┌─────▼─────┐ ││ │ Table │◀─── CDC 同步 ────────────────│ Streaming │ ││ │(MySQL) │ │ Pipeline │ ││ └─────────┘ └─────┬─────┘ ││ │ ││ ┌───────▼───────┐ ││ │ Sink │ ││ │ (Kafka/PG/S3) │ ││ └────────────────┘ │└─────────────────────────────────────────────────────────────────┘
核心要点回顾:
下一篇预告
在理解了核心概念之后,我们将深入 RisingWave 的内部架构。你将了解到:
敬请期待:《架构篇:RisingWave 的系统设计》
想了解更多?