4. 消息队列选型:Kafka核心概念与增量链路中的角色

聊到增量计算,消息队列是绕不开的基础设施。而在众多消息队列中,Kafka 几乎成了事实标准。我这些年经手的实时项目,底层清一色都是 Kafka。为什么大家都选它?说白了,就三个字——扛得住。

这一章,我们聚焦 Kafka 的三个核心概念:Topic、Partition、Offset。然后看看它在增量链路中到底扮演什么角色,最后聊聊它的高可用机制是怎么保证不丢数据的。

4.1 三个核心概念:Topic、Partition、Offset

这三个概念,是理解 Kafka 的基石。我见过不少新手,上来就写代码,结果连数据是怎么存的都搞不清楚。嗯,我们先把这个基础打牢。

4.1.1 Topic:逻辑上的消息分类

Topic 是 Kafka 里最上层的逻辑概念。你可以把它理解成一个消息的「类别」或「主题」。比如,主数据变更的消息,我习惯放在一个叫 mdm_data_change 的 Topic 里;业务日志放在 biz_log 里。生产者往 Topic 里写,消费者从 Topic 里读,彼此不干扰。

我的习惯:Topic 的命名要清晰,带上业务含义。比如 mdm_customer_update,一看就知道是客户主数据的更新事件。别用 topic1、test 这种名字,后期维护起来很痛苦。

4.1.2 Partition:物理上的分片与并行单元

一个 Topic 可以分成多个 Partition。Partition 是 Kafka 真正存储数据的地方,也是并行处理的基本单位。

为什么要分 Partition?为了水平扩展。你想想看,如果只有一个 Partition,那所有读写都压在一个节点上,性能天花板很低。分成多个 Partition 后,每个 Partition 可以分布在不同的 Broker 上,读写压力就分散了。

我举个例子。假设我们有一个 mdm_customer Topic,设置了 6 个 Partition。那么这 6 个 Partition 可以分布在 3 台 Broker 上,每台负责 2 个。生产者写数据时,可以指定 key,Kafka 根据 key 的哈希值决定写到哪个 Partition。这样,同一个客户 ID 的变更事件,永远会落到同一个 Partition 里,保证了顺序性。

关键点:Partition 内部是有序的,但 Partition 之间不保证全局有序。如果你需要全局有序,就只能设 1 个 Partition——但那样性能就没了。实际项目中,我们通常按业务 key 保证局部有序就够了。

4.1.3 Offset:消息在 Partition 内的唯一编号

每个 Partition 里的消息,都有一个唯一的递增编号,叫 Offset。你可以把它理解成消息的「身份证号」。消费者读取消息时,就是通过 Offset 来定位的。

Offset 由 Kafka 自动管理,消费者可以手动提交 Offset,也可以自动提交。我个人的建议是:在增量链路中,尽量手动提交 Offset。为什么?因为自动提交可能会丢数据。比如,你消费了一条消息,业务逻辑还没处理完,Offset 就自动提交了。这时候程序挂了,重启后 Offset 已经往前跳了,那条没处理完的消息就丢了。

我曾经在一个实时同步项目里,就因为用了自动提交,导致几千条主数据变更没同步过去。排查了半天才发现是 Offset 提交时机的问题。从那以后,所有核心链路我都改成了手动提交。

概念 层级 作用 关键特性
Topic 逻辑 消息分类 一个 Topic 可包含多个 Partition
Partition 物理 数据分片与并行 内部有序,可水平扩展
Offset 消息级 消息唯一编号 递增,用于消费定位

4.2 Kafka 在增量链路中的角色

在增量计算架构里,Kafka 不是主角,但它是那个「承上启下」的关键角色。我把它总结为三个核心作用:

  • 解耦生产者和消费者:上游的 CDC 组件(比如 Canal、Debezium)只管往 Kafka 里写,下游的 Flink 任务只管从 Kafka 里读。两边互不依赖,各自独立部署和扩缩容。
  • 削峰填谷:主数据变更的流量往往有波峰波谷。比如白天业务高峰期,变更事件可能每秒几千条;晚上可能只有几十条。Kafka 作为缓冲区,能把波峰的数据暂存下来,让下游按自己的节奏消费。
  • 数据重放与回溯:这是 Kafka 最强大的能力之一。因为消息会持久化到磁盘,并且保留一段时间(默认 7 天),所以你可以随时从任意 Offset 开始重新消费。这在数据修复、逻辑变更后重新计算时特别有用。

我举个例子。有一次,我们下游的 Flink 任务因为一个 bug 挂了 3 个小时。修复后,直接从 Kafka 里重置 Offset 到故障前的位置,重新消费了一遍,数据就全补回来了。如果没有 Kafka 的持久化能力,这 3 小时的数据就彻底丢了。

注意:Kafka 的持久化不是无限的。默认保留 7 天,你可以根据磁盘空间和业务需求调整。但别设得太长,否则磁盘会爆。我一般设 3 天,够用了。

下面这张图,展示了 Kafka 在增量链路中的位置:

Kafka 在增量链路中的角色 CDC 组件 Canal / Debezium Kafka 集群 Topic / Partition / Offset 持久化 + 高可用 Flink 计算 增量处理 ① 上游 CDC 写入 Kafka,下游 Flink 从 Kafka 消费,两者完全解耦。 ② Kafka 作为缓冲区,削峰填谷,支持数据重放和回溯。 数据流方向 数据库变更 Kafka 消息 增量计算 主数据更新

4.3 Kafka 高可用机制

在增量链路中,Kafka 一旦挂了,整个数据管道就断了。所以高可用是必须的。Kafka 的高可用,主要靠三个机制:

4.3.1 副本机制(Replication)

每个 Partition 都可以设置副本数。比如,副本数设为 3,那么每个 Partition 的数据会在 3 个 Broker 上各存一份。其中一个副本是 Leader,负责读写;另外两个是 Follower,负责同步。

当 Leader 挂了,Kafka 会从 Follower 中选举一个新的 Leader。这个过程是自动的,对生产者和消费者基本透明。我见过一个场景:某台 Broker 的磁盘坏了,Kafka 集群自动把它的 Leader 角色转移到了其他 Broker 上,整个过程不到 10 秒,业务无感知。

建议:生产环境中,副本数至少设为 3。如果集群规模小,至少也要 2。副本数越多,数据越安全,但写入性能会略有下降,因为需要同步到多个副本。

4.3.2 ISR 机制(In-Sync Replicas)

ISR 是 Kafka 保证数据一致性的关键。它指的是「与 Leader 保持同步的副本集合」。只有 ISR 里的 Follower,才有资格在 Leader 挂掉后成为新的 Leader。

Kafka 的 Leader 会跟踪每个 Follower 的同步进度。如果一个 Follower 落后太多(比如网络延迟大、机器负载高),Leader 会把它踢出 ISR。等它追上进度了,再重新加回来。

我曾经遇到过一个问题:某个 Follower 因为 GC 停顿,落后了 Leader 几万条消息。结果 Leader 挂了,ISR 里只剩 Leader 自己,选不出新 Leader。后来我们调整了 replica.lag.time.max.ms 参数,给了 Follower 更多追赶时间,才解决了这个问题。

4.3.3 ACK 机制:生产者的确认策略

生产者写入消息时,可以设置 ACK 参数,控制消息的确认级别:

ACK 值 含义 可靠性 性能
0 不等待确认,直接发下一条 最低,可能丢数据 最高
1 等待 Leader 确认 中等,Leader 挂了可能丢 中等
all(或 -1) 等待所有 ISR 副本确认 最高,不丢数据 最低

在增量链路中,我强烈建议使用 acks=all。主数据变更不能丢,哪怕慢一点也值得。我曾经在一个金融项目里,因为用了 acks=1,结果 Leader 挂了,有几条消息没同步到 Follower,导致主数据不一致。后来审计查出来,花了好大功夫才补上。

避坑指南:用了 acks=all 后,如果 ISR 里只剩一个副本,那其实跟 acks=1 没区别。所以一定要保证 ISR 里有足够多的副本。建议设置 min.insync.replicas=2,这样即使 Leader 挂了,至少还有一个 Follower 能顶上。

4.3.4 消费者端的容错:手动提交 Offset

前面提到了,手动提交 Offset 是增量链路中的最佳实践。这里再补充一点:消费逻辑和 Offset 提交要放在同一个事务里。比如,你用 Flink 消费 Kafka,处理完数据后写入目标数据库,然后提交 Offset。如果写入数据库失败了,Offset 就不提交,下次重启会重新消费。

我见过一个典型的错误:先提交 Offset,再处理业务逻辑。结果业务逻辑处理到一半挂了,Offset 已经提交了,数据就丢了。嗯,这个顺序千万别搞反。