2. 数据流模型与架构:从DAG到Lambda与Kappa

聊到流处理,绕不开一个核心问题:数据到底怎么流?

我见过不少团队,一上来就选框架、调参数,结果架构搭到一半发现数据路径是乱的。说白了,你连数据怎么走都没想清楚,优化吞吐就是空谈。这一章,我们就把数据流模型和主流架构掰开揉碎讲清楚。

2.1 数据流图:先画清楚数据怎么走

数据流图(Data Flow Diagram)是架构师的第一张草图。它不关心你用Kafka还是Pulsar,只关心:数据从哪来、经过什么处理、最终去哪。

我个人习惯,设计任何流处理系统前,先画一张三级数据流图:

  • 一级(上下文图):只画外部系统、流处理核心、输出目标。比如:传感器 → 流处理引擎 → 数据库。
  • 二级(处理图):拆出主要处理节点。比如:数据接入 → 清洗 → 聚合 → 窗口计算 → 输出。
  • 三级(详细图):细化到每个算子的输入输出、状态存储、容错机制。
我的经验: 三级图别画太细,否则改一次架构就要重画一次。我一般只维护一级和二级图,三级图用代码注释代替。

举个例子,一个简单的IoT数据流:

数据源(MQTT Broker)
    ↓
接入层(Kafka Connect / Flume)
    ↓
清洗算子(过滤无效数据、格式标准化)
    ↓
窗口聚合(每5秒计算一次均值)
    ↓
结果输出(写入InfluxDB + 触发告警)

嗯,这里要注意:数据流图上的每个箭头,背后都对应着网络开销、序列化开销、背压传播路径。箭头画得越乱,系统越难优化。

2.2 有向无环图(DAG):流处理的骨架

有向无环图(DAG)是流处理引擎的底层抽象。Flink、Spark Streaming、Kafka Streams,底层都是DAG。

为什么必须是DAG?因为不能有环。一旦出现环,数据就会无限循环,系统直接崩溃。你想想看,一个算子输出又回到自己输入,这不就是死循环吗?

DAG里有几个关键概念:

  • 顶点(Vertex):一个处理算子,比如map、filter、window。
  • 边(Edge):数据流动的方向,可以是one-to-one、redistribute、broadcast。
  • 分区(Partition):数据在顶点间的切分方式,直接影响并行度。

我曾经在一个项目中,因为没注意边的类型,导致数据倾斜严重。一个简单的keyBy操作,默认是hash分区,结果某个key的数据量是其他key的100倍。那台机器的CPU直接打满,其他机器闲着。后来改成自定义分区器才解决。

避坑指南: 我曾经见过一个团队,把DAG画成了一张蜘蛛网,顶点之间全是broadcast边。结果每条数据都要复制到所有下游,吞吐直接掉到原来的十分之一。记住:能用one-to-one就别用redistribute,能用redistribute就别用broadcast。

下面这张图展示了典型的流处理DAG结构:

流处理DAG示例 数据源 解析 过滤 窗口聚合 输出 边类型说明 → one-to-one: 直连 → redistribute: 重分区 → broadcast: 广播 本例全部为one-to-one 吞吐最高,延迟最低

2.3 Lambda架构:批流分离的经典方案

Lambda架构是Nathan Marz提出的。核心思想很简单:用两条路处理数据。

  • 批处理层(Batch Layer):处理全量历史数据,生成精确结果。延迟高,但准确。
  • 流处理层(Speed Layer):处理实时增量数据,提供低延迟的近似结果。
  • 服务层(Serving Layer):合并批处理和流处理的结果,对外提供查询。

我早期做实时数仓时,用的就是Lambda架构。批处理用Spark SQL跑T+1的全量聚合,流处理用Flink做秒级实时指标。听起来很完美,对吧?

但实际维护起来,问题不少:

Lambda架构的痛点:
  • 两套代码:批处理和流处理逻辑要写两遍,维护成本翻倍。
  • 结果合并复杂:批处理和流处理的结果可能不一致,需要额外的合并逻辑。
  • 数据口径对齐难:批处理用T+1的数据,流处理用实时数据,口径经常对不上。

我记得有一次,批处理跑出来的日活是100万,流处理实时显示的是95万。业务方问为什么差5万?排查了两天才发现,流处理层丢了一部分晚到数据。这种问题在Lambda架构里太常见了。

2.4 Kappa架构:简化,再简化

Kappa架构是Jay Kreps提出的。他当时在LinkedIn做Kafka,觉得Lambda太复杂了,干脆只用一条流处理链路。

核心思想:所有数据都当作流来处理。历史数据重放时,从Kafka的起始偏移量开始消费,重新计算一遍。

对比维度 Lambda架构 Kappa架构
处理链路 批处理 + 流处理 仅流处理
代码维护 两套代码 一套代码
历史重算 批处理层负责 流处理重放Kafka
延迟 批处理高,流处理低 统一低延迟
复杂度 高 中
适用场景 对历史数据精确性要求极高 实时性要求高,可接受最终一致性

你可能会问:Kappa架构是不是万能的?当然不是。它有个前提:你的消息队列(比如Kafka)必须能保存足够长时间的历史数据。如果数据量太大,Kafka的存储成本会很高。

我的建议: 如果你的业务对历史数据精确性要求极高(比如金融交易对账),Lambda架构还是更稳妥。如果追求实时性和开发效率,Kappa架构是更好的选择。我现在的项目,80%的场景用Kappa,20%的场景用Lambda。

2.5 如何选择?我的决策框架

说了这么多,到底怎么选?我总结了一个简单的决策框架:

  1. 数据量级:日均数据量超过10TB,优先考虑Lambda,因为纯流处理的状态存储压力太大。
  2. 延迟要求:秒级延迟必须Kappa;分钟级延迟Lambda也能接受。
  3. 精确性要求:需要精确一次语义且历史数据不能丢,Lambda更成熟。
  4. 团队能力:团队熟悉Flink/Spark Streaming,Kappa上手更快;团队有Hive/Spark SQL经验,Lambda更顺手。
  5. 运维成本:Kafka集群维护成本低于Hadoop + Kafka两套集群。

嗯,这里要提醒一句:架构选型没有银弹。我见过用Kappa架构做金融对账的,结果数据不一致被审计打回来。也见过用Lambda架构做实时推荐的,批处理还没跑完,推荐结果已经过时了。关键还是看业务场景。

避坑指南: 我曾经在一个项目中,为了追求架构的“先进性”,强行上了Kappa架构。结果历史数据重放时,Kafka的磁盘IO成为瓶颈,重放速度跟不上实时写入速度。最后不得不临时加了一台SSD缓存节点才扛住。所以,选架构前,先压测你的消息队列。

最后,无论选哪种架构,DAG的设计都是基础。把数据流图画清楚,把DAG的边类型选对,把分区策略调好——这些基本功做到位了,架构选型只是锦上添花。