Kafka 的架构设计遵循“分布式、分区、多副本”原则,其核心在于将数据流(Topic)拆解为并行单元(Partition)进行水平扩展。
Kafka 的架构本质是一个分布式的提交日志系统。它通过“分区”解决了并发瓶颈,通过“副本”解决了高可用问题,通过“消费者组”解决了数据复用问题,是现代数据管道和实时流处理的主流技术。
- 吞吐优先:利用顺序 I/O 和 PageCache 规避磁盘随机读写瓶颈。
- 水平扩展:通过增加 Partition 和 Broker 线性提升吞吐量。
- 解耦与缓冲:作为生产者与消费者之间的异步缓冲层,抵御流量洪峰。
- 多租户广播:通过 Consumer Group 机制,实现一份数据被多个业务系统独立消费(Pub-Sub)。
1. Kafka架构设计
1.1 架构全景图
| 组件 | 角色与职责 | 关键特性 |
|---|---|---|
| Producer | 消息生产者 | 通过 Key 决定消息发往哪个 Partition(负载均衡) |
| Consumer | 消息消费者 | 以 Consumer Group 为单位,每个 Partition 只能被组内一个 Consumer 消费 |
| Broker | Kafka 服务器节点 | 存储数据,组成集群;无需主从,通过 Zookeeper/KRaft 协调 |
| Topic | 逻辑数据分类 | 如 order_events,是消息的集合 |
| Partition | 物理数据分片 | Topic 的并行单元,每个 Partition 是一个有序、不可变的日志 |
| Replica | 副本 | 每个 Partition 有多个副本(Leader/Follower),Leader 负责读写,Follower 同步备份 |
| Zookeeper / KRaft | 元数据与协调中心 | 管理 Broker 注册、Leader 选举、Consumer Offset(新版用 KRaft 替代 ZK) |
1.2 数据流与存储机制
- 写入流程(Producer → Broker)
- 路由:Producer 根据 Key 哈希或轮询,将消息发送到 Topic 的特定 Partition。
- 追加日志:消息到达 Broker 后,以顺序追加(Append-Only) 的方式写入 Partition 日志文件。
- 持久化:消息并非立即落盘,而是先写入 PageCache,由操作系统异步刷盘,兼顾高性能与持久性。
2. 消费流程(Broker → Consumer)
- 拉取模式:Consumer 主动向 Broker 拉取(Pull)消息,可控制消费速率。
- Offset 管理:Consumer Group 维护消费位移(Offset),标记已消费位置,支持at-least-once、at-most-once、exactly-once语义。
- Rebalance:当 Consumer 加入或离开 Group 时,触发重新分配 Partition,实现自动容错。
3. 高可用机制(Replication)
- Leader/Follower:每个 Partition 有一个 Leader 和多个 Follower。所有读写仅通过 Leader。
- ISR(In-Sync Replicas) :与 Leader 保持同步的副本集合。只有 ISR 中的副本才有资格竞选 Leader。
- 故障转移:若 Leader 宕机,ZooKeeper/KRaft 会从 ISR 中选举新的 Leader,保证服务不间断。
1.3 ZooKeeper 与 KRaft
| 模式 | 架构特点 | 适用场景 |
|---|---|---|
| ZooKeeper 模式 | 依赖外部 ZK 集群进行元数据管理 | 旧版本(Kafka < 3.3),运维复杂 |
| KRaft 模式 | 内置元数据仲裁机制,去外部依赖 | 新版本(Kafka ≥ 3.3),简化部署,提升稳定性 |
2. Kafka数据流转
2.1 生产者:向 Kafka 发送消息
生产者是将数据发送到 Kafka 主题的应用程序。它们能智能地决定将每条消息发送到何处。
生产者逻辑:
- 分区选择:
如果消息有键(key),则使用 hash(key) % partition_count计算分区
如果无键,则使用轮询(round-robin)分配
也可以使用自定义的分区器逻辑
2. 交付保证:
- acks=0:发送即忘(最快,可靠性最低)
- acks=1:等待领导者确认(平衡型)
- acks=all:等待所有副本确认(最慢,可靠性最高)
生产者代码示例:
Properties props = new Properties();
props.put("bootstrap.servers", "broker1:9092,broker2:9092");
props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
Producer<String, String> producer = new KafkaProducer<>(props);
// 发送消息到 user-events 主题
ProducerRecord<String, String> record =
new ProducerRecord<>("user-events", "user123", "{"action": "purchase"}");
producer.send(record);
2.2 消费者:从 Kafka 读取消息
消费者从 Kafka 主题读取数据。与传统消息系统在消费后即删除消息不同,Kafka 会保留消息,允许多个消费者独立地读取相同的数据。
消费者组:
属于同一组的消费者共享工作负载
每个分区在同一时间只能被组内的一个消费者消费
不同的消费者组可以独立地消费相同的数据
消费者代码示例:
Properties props = new Properties();
props.put("bootstrap.servers", "broker1:9092,broker2:9092");
props.put("group.id", "user-analytics-group");
props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
Consumer<String, String> consumer = new KafkaConsumer<>(props);
consumer.subscribe(Arrays.asList("user-events"));
while (true) {
ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));
for (ConsumerRecord<String, String> record : records) {
System.out.printf("Consumed: key=%s, value=%s, partition=%d, offset=%d%n",
record.key(), record.value(), record.partition(), record.offset());
}
}
2.3 生产者与消费者:如何“对齐”
生产者与消费者,如何管理offset?
在 Kafka 中,生产者不管理 Offset,消费者负责管理 Offset,但两者的管理机制和目的完全不同。下面是详细说明:
一、生产者端:不管理 Offset
生产者不关心 Offset,只负责发送消息。它管理的是消息确认机制,这间接影响了消息的持久化位置。
生产者确认机制(acks)
| 配置 | 含义 | 可靠性 | 性能 |
|---|---|---|---|
| acks=0 | 不等待确认,只管发送 | 最低(可能丢失) | 最高 |
| acks=1 | 等待 Leader 写入成功确认 | 中等(Leader 宕机可能丢失) | 中等 |
| acks=all/acks=-1 | 等待所有 ISR 副本写入成功确认 | 最高(强一致) | 最低 |
生产者代码示例:
// 设置高可靠性配置
props.put("acks", "all"); // 等待所有副本确认
props.put("retries", 3); // 失败重试3次
props.put("max.in.flight.requests.per.connection", 1); // 保证顺序
二、消费者端:Offset 管理的核心
消费者全权负责 Offset 的管理,这是 Kafka 消费语义的核心。
Offset 存储位置:
| 存储方式 | 位置 | 管理方 | 特点 |
|---|---|---|---|
| 自动提交 | Kafka 内部 Topic:__consumer_offsets | Kafka 自动管理 | 默认方式,简单但有风险 |
| 手动提交 | __consumer_offsets或 外部存储(如数据库) | 消费者应用控制 | 精确控制,避免重复/丢失 |
Offset 提交方式对比:
| 提交方式 | 配置 | 特点 | 适用场景 |
|---|---|---|---|
| 自动提交 | enable.auto.commit=trueauto.commit.interval.ms=5000 | 每5秒自动提交,可能重复消费 | 允许少量重复的业务 |
| 同步手动提交 | enable.auto.commit=false调用 consumer.commitSync() | 提交成功才继续,性能较低 | 金融、交易等强一致性场景 |
| 异步手动提交 | enable.auto.commit=false调用 consumer.commitAsync() | 不阻塞,性能高,失败不重试 | 大部分业务场景 |
| 按记录提交 | 处理一条提交一次 | 最安全,性能最差 | 极少使用 |
手动提交代码示例
// 禁用自动提交
props.put("enable.auto.commit", "false");
try {
while (true) {
ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));
for (ConsumerRecord<String, String> record : records) {
// 处理消息
processMessage(record);
// 同步提交(确保提交成功)
// consumer.commitSync();
}
// 批量异步提交(推荐)
consumer.commitAsync();
}
} catch (Exception e) {
// 处理异常
} finally {
try {
// 最后同步提交确保成功
consumer.commitSync();
} finally {
consumer.close();
}
}
三、高级 Offset 管理策略
- 自定义 Offset 存储
将 Offset 存储到外部系统(如 MySQL、Redis),实现精确一次(Exactly-Once) 处理:
// 从数据库获取上次保存的 Offset
long offset = loadOffsetFromDB(topic, partition);
// 指定从该 Offset 开始消费
consumer.seek(new TopicPartition(topic, partition), offset);
while (true) {
ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));
for (ConsumerRecord<String, String> record : records) {
processMessage(record);
// 处理成功后,保存 Offset 到数据库
saveOffsetToDB(record.topic(), record.partition(), record.offset() + 1);
}
}
2. 消费语义控制
| 语义 | 实现方式 | 特点 |
|---|---|---|
| 至少一次(At-Least-Once) | 先处理消息,后提交 Offset | 可能重复,但不会丢失 |
| 至多一次(At-Most-Once) | 先提交 Offset,后处理消息 | 可能丢失,但不会重复 |
| 精确一次(Exactly-Once) | 事务或幂等生产者+外部存储 | 不丢失不重复,实现复杂 |
3. Offset 重置策略
当消费者组第一次启动或 Offset 失效时,可配置重置行为:
# 配置项
auto.offset.reset=latest|earliest|none
# latest:从最新位置开始消费(默认)
# earliest:从最早位置开始消费
# none:找不到 Offset 时抛出异常
3. 理解几个关键概念
Kafka 本质上是一个高吞吐、分布式、基于发布-订阅模型的实时消息系统。它通过“日志(Log)”这一底层数据结构,解决了海量数据流转的难题。
3.1 三大基础角色
这是理解数据流向的基石:
Producer(生产者) :推数据到 Kafka 的应用。
Consumer(消费者) :拉数据并处理的应用。
Broker(服务器节点) :Kafka 集群中的单个实例,负责存储和转发消息。
3.2 数据组织与存储
Topic(主题)
- 数据的逻辑分类,类似于数据库的表名。生产者发送消息时必须指定 Topic,消费者也按 Topic 订阅。
Partition(分区)
Topic 的物理分片,这是 Kafka 实现高并发和高扩展性的核心。
顺序性:消息在单个 Partition 内有序(全局无序)。
并行度:Partition 是并发的最小单位。Consumer 的吞吐量上限由它消费的 Partition 数量决定。
偏移量(Offset) :Partition 中每条消息的唯一 ID(类似数组下标),由 Kafka 自动维护。
Log(日志)
Partition 在磁盘上的物理存储形式,表现为只能追加(Append-Only)的文件。这种设计决定了 Kafka 的高性能——写入就是顺序写,读取是顺序读。
3.3 消费与保障机制
- Consumer Group(消费组)
- 一组协同工作的消费者,是 Kafka 实现“负载均衡”和“队列/发布订阅”模式的关键。
- 组内竞争:一个 Partition 在同一时间只能被组内一个 Consumer 消费。
- 组间独立:不同 Consumer Group 消费同一 Topic 互不影响(实现广播)。
2. Replication(副本)
- 数据高可用的保障。每个 Partition 有多个副本(Leader 和 Follower)。
- Leader:负责处理所有读写请求。
- Follower:异步同步 Leader 的数据。Leader 挂掉后,Follower 自动竞选成为新 Leader。
4. 理解几组关键关系
4.1 Topic与Partition的关系
Topic 和 Partition 是 Kafka 中逻辑与物理的关系。你可以把 Topic 看作数据库中的“表”,而 Partition 是这张表底层的“物理分片”。
一、核心关系:一对多的包含关系
一个 Topic 必须包含至少 1 个 Partition,一个 Partition 必须属于且仅属于一个 Topic。
- Topic(逻辑分类) :数据的主题或类别,例如 order_events。它是面向业务和开发者的逻辑概念。
- Partition(物理分片) :Topic 在物理存储上的实际分片。每个 Partition 是一个独立的、有序的日志文件。
二、关键特性与约束
- 消息顺序性
- Partition 内有序:在单个 Partition 内部,消息严格按照写入顺序(Offset)排列,Kafka 保证此顺序。
- Partition 间无序:跨 Partition 的消息没有全局顺序。如果你需要某类消息(如同一个用户 ID)严格有序,必须通过 Key 将它们路由到同一个 Partition。
2. 并行度与扩展性
- Partition 数量决定最大并行度:一个 Consumer Group 中,同时消费的 Consumer 实例数量不能超过 Partition 数量。
例如:Topic 有 3 个 Partition,Consumer Group 最多只能有 3 个 Consumer 同时工作(每个处理 1 个 Partition)。如果有第 4 个 Consumer,它会处于空闲状态。
- 水平扩展:增加 Partition 数量可以提升 Topic 的吞吐量上限。
3. 数据分布与路由
- Producer 路由:Producer 发送消息时,通过 Key 的哈希值(或轮询)决定消息写入哪个 Partition。
- 物理隔离:不同 Partition 可以分布在不同的 Broker 节点上,实现负载均衡。
三、设计决策参考
| 场景 | 建议策略 |
|---|---|
| 需要高吞吐 | 增加 Partition 数量(如 12, 24, 32) |
| 需要严格顺序 | 使用 Key 确保相关消息进入同一 Partition |
| 消费者扩展 | Partition 数应 ≥ 预期的最大消费者实例数 |
最佳实践:Partition 数量在创建 Topic 时设定,后期修改(增加)虽然可以,但可能会导致 Key 的哈希分布变化,建议初期预留一定余量(如 6-12 个)。
4.2 Topic与key的关系
Topic 是消息的逻辑分类(“去哪”),Key 是消息的路由标签(“怎么去”)。Key 本身不独立存在,它依附于 Topic,用于决定消息被发送到 Topic 下的哪一个 Partition。
一、基础定义
Topic(主题) :消息的逻辑集合,类似数据库的“表名”。
Key(键) :消息的元数据,用于控制消息在 Topic 内的分布逻辑。
二、Key 如何影响 Topic 内的路由
Key 的核心作用是决定消息进入 Topic 的哪个 Partition,从而影响顺序性和数据局部性。
| Key 设置情况 | 路由逻辑(Partition 选择) | 典型应用场景 |
|---|---|---|
| Key = null | 轮询(Round-Robin)发送到所有 Partition。 | 吞吐量优先,无需顺序保障。 |
| Key ≠ null | hash(key) % partition_num,相同 Key 必进同一 Partition。 | 保证同一用户、订单 ID 的消息顺序。 |
三、关系本质:逻辑与物理的映射
Topic 是逻辑容器,Key 是物理路由策略的输入参数。
无 Key:Topic 只是一个数据池,消息均匀分布。
有 Key:Topic 被 Key 划分为多个“逻辑子流”(Partition),实现了数据分片(Sharding) 。
四、设计误区与最佳实践
Key 不是必选项:如果不需要顺序或聚合,建议不设 Key 以获得最佳吞吐。
热点 Key 风险:如果某个 Key 的数据量极大(如“默认用户”),会导致单个 Partition 成为瓶颈。
Key 的选择:优先使用业务主键(如 user_id、order_id)作为 Key,而非随机值。
4.3 Topic 与消费者组的关系
Topic 与消费者组(Consumer Group)的关系,本质上是 “广播内容”与“订阅观众” 的关系。它们之间是完全解耦的,一个 Topic 可以被多个消费者组独立消费,互不影响。
一、核心关系:一对多的独立订阅
- 一个 Topic 可以被 0 个、1 个或多个消费者组同时订阅
- 每个消费者组都会独立、完整地消费该 Topic 的所有消息
二、多消费者组场景(Pub-Sub 模式)
- 这是 Kafka 最强大的特性之一:一份数据,多份消费。
假设你有一个 user_login_topic,记录了所有用户的登录事件: - 消费者组 A(数据分析团队):消费该 Topic,计算实时 UV/PV,写入数据仓库。
- 消费者组 B(风控团队):消费该 Topic,检测异常登录行为,触发告警。
- 消费者组 C(推送服务团队):消费该 Topic,判断用户是否长时间未登录,触发召回 Push。
这三个消费者组互不知晓对方的存在,各自维护独立的消费进度(Offset),互不干扰。
三、消费者组内部机制(Queue 模式)
虽然 Topic 是广播,但同一个消费者组内部是竞争关系,实现负载均衡。
| 维度 | 同一个消费者组内(Competing Consumers) | 不同消费者组之间(Pub-Sub) |
|---|---|---|
| 消息分配 | 一个 Partition 只能被组内一个 Consumer 消费 | 每个组都能收到全部消息 |
| 消费模式 | 队列模式(负载均衡) | 发布订阅模式(广播) |
| Offset 管理 | 组内共享进度(__consumer_offsets) | 各组独立维护进度 |
| 典型场景 | 业务逻辑处理(需要扩容时增加 Consumer) | 数据复用(不同团队消费同一份数据) |
四、关键设计约束
1 Partition 数量是并行度上限
一个消费者组内,有效的 Consumer 实例数量不能超过 Topic 的 Partition 数量。
例如:Topic 有 3 个 Partition,消费者组最多有 3 个 Consumer 在干活。如果有第 4 个 Consumer,它会处于空闲状态(Idle),直到有 Partition 被释放。
2 消费进度(Offset)独立
每个消费者组在 Kafka 的内部 Topic __consumer_offsets中独立记录自己的消费位置。
- 消费者组 A 可能消费到了 Offset=1000。
- 消费者组 B 可能因为重启,还在消费 Offset=500。
它们互不影响。
五、设计决策参考表
| 业务场景 | Topic 与 消费者组 设计策略 |
|---|---|
| 单一业务逻辑处理(如订单处理) | 1 个 Topic + 1 个消费者组,通过增加组内 Consumer 实例来扩容 |
| 数据复用(如一份日志多方使用) | 1 个 Topic + N 个消费者组,每个组对应一个下游业务 |
| 需要顺序保证 | 确保同一类消息(相同 Key)进入同一 Partition,且该 Partition 在同一组内仅由一个 Consumer 处理 |
| 测试或调试 | 使用独立的消费者组(如 test_group),避免干扰线上业务的消费进度 |
最佳实践:Topic 是数据的“生产者视图”,消费者组是数据的“消费者视图”。设计时应优先考虑“谁需要这份数据”,而不是“怎么消费这份数据”。
4.4 partition与 消费者组的关系
Consumer Group(消费者组)与 Partition(分区)的关系是 Kafka 并行处理与负载均衡的基石。简单来说:一个 Partition 在同一时刻只能被一个 Consumer Group 内的唯一 Consumer 消费。
这种“一对一”的锁定关系,直接决定了系统的吞吐量和并发能力。
一、核心关系:抢占式消费
在同一个 Consumer Group 内,Partition 是分配的最小单位。你可以把 Partition 想象成“蛋糕”,Consumer 是“吃蛋糕的人”。
规则:每个 Partition 只能分配给组内的一个 Consumer。
结果:组内 Consumer 的数量与 Partition 的数量直接决定了并发上限。
二、数量关系的三种状态
这是面试和工作中最常考察的重点:
| 状态 | 关系 | 现象与影响 |
|---|---|---|
| C = P | 消费者数 = 分区数 | 理想状态。每个 Consumer 独占一个 Partition,资源被充分利用。 |
| C < P | 消费者数 < 分区数 | 部分 Consumer 需承担多个 Partition。此时虽然能运行,但部分消费者压力较大。 |
| C > P | 消费者数 > 分区数 | 有 Consumer 处于空闲状态。多出来的 Consumer 无法分配到 Partition,造成资源浪费。 |
关键结论:一个 Consumer Group 的并发度上限等于该 Topic 的 Partition 数量。增加 Consumer 数量超过 Partition 数量不会提升性能。
三、为什么需要这种关系?
这种设计主要解决了两个核心问题:
顺序性保障:因为一个 Partition 只被一个 Consumer 处理,所以 Partition 内部的消息顺序得以严格保持(适用于订单流水等场景)。
负载均衡:Kafka 通过 Coordinator 自动将 Partition 均匀分配给组内的所有 Consumer,实现自动的负载均衡(Rebalance)。
四、Group 之间的隔离性
不同 Consumer Group 消费同一 Topic 是完全隔离的。
Group A 和 Group B 可以同时消费 topic.order的所有 Partition。
这实现了“广播”效果:一条消息可以被多个不同业务(如风控、数仓)同时消费,互不干扰。
五、实战建议
规划分区数:创建 Topic 时,Partition 数量应至少等于你预计的最大 Consumer 数量,为未来扩容留足空间(分区数只能增不能减)。
避免闲置:尽量不要让 Consumer 数量长期大于 Partition 数量。
Rebalance:当 Consumer 加入或离开组时,会触发 Partition 的重新分配,此时消费会短暂暂停,这是正常现象。
4.5 Partition 与 Broker的关系
Partition(分区)与 Broker(服务器节点)的关系,是 “存储单元”与“物理载体” 的关系。你可以把 Broker 理解为书架,把 Partition 理解为书。一个书架可以放很多本书,但同一本书的副本不能放在同一个书架上。
一、核心关系:多对多的物理分布
一个 Broker 可以存储多个 Partition,一个 Partition 的多个副本必须分布在不同的 Broker 上。
这是 Kafka 实现高吞吐(通过 Partition 拆分)和高可用(通过副本分布)的物理基础。
- 数据分布机制
Partition 是存储实体:Topic 是逻辑概念,数据实际存储在 Partition 的日志文件中。
Broker 是存储节点:Partition 的物理文件最终落在 Broker 的磁盘上。 - 副本放置策略(关键)
每个 Partition 有多个副本(Replica),Kafka 强制要求同一个 Partition 的所有副本不能放在同一个 Broker 上。这是为了确保一台机器宕机时,数据依然可用。
示例:
假设你有一个 3 节点的 Kafka 集群(Broker-0, Broker-1, Broker-2),一个 Partition P0有 3 个副本,它的分布必须是:
- P0的 Leader 副本在 Broker-0
- P0的 Follower 副本在 Broker-1
- P0的 Follower 副本在 Broker-2
二、读写机制:Leader 与 Follower
Partition 副本分为 Leader 和 Follower,读写操作只与 Leader Broker 交互。
| 角色 | 职责 | 读写权限 |
|---|---|---|
| Leader Replica | 处理所有 Producer 和 Consumer 的读写请求 | 可读可写 |
| Follower Replica | 从 Leader 异步/同步拉取数据,保持同步 | 只同步,不提供读服务 |
关键点:
写放大:Producer 只向 Leader Broker 写入数据,Leader 负责将数据复制到 Follower。
读隔离:Consumer 默认只从 Leader Broker 读取数据(除非配置了 Follower 读取,但通常不建议)。
三、容量与扩展性设计
- 分区数 vs Broker 数
- Partition 数量决定了 Topic 的最大并行度(一个 Partition 只能被一个 Consumer 消费)。
- Broker 数量决定了集群的总存储容量和故障容忍度。
2. 容量规划公式
为了保证集群健康,通常建议遵循以下经验法则:
- 单个 Broker 上的 Partition 总数 ≤ 2000 - 4000
计算逻辑:
- 假设你有 10 个 Topic,每个 Topic 有 100 个 Partition,副本因子为 3。
- 那么整个集群的 Partition 总数为 10 × 100 × 3 = 3000。
- 如果均匀分布在 3 个 Broker 上,每个 Broker 承载 3000 / 3 = 1000个 Partition,这在安全范围内。
如果单个 Broker 上的 Partition 数量过多(如超过 5000),会导致:
文件句柄耗尽、Leader 选举变慢、恢复时间变长
四、故障转移(Failover)流程
当 Broker 宕机时,Partition 与 Broker 的关系会动态调整: - 检测:ZooKeeper/KRaft 检测到 Broker-0 失联。
- 选举:对于所有 Leader 在 Broker-0 上的 Partition,从 ISR(同步副本列表)中选举一个新的 Leader(例如 Broker-1)。
- 恢复:Producer 和 Consumer 自动切换到新的 Leader Broker 继续工作。
注意:如果宕机的 Broker 是 Follower,通常对服务无影响,因为读写只依赖 Leader。
五、设计决策参考表
| 场景 | 策略 | 目标 |
|---|---|---|
| 提高吞吐量 | 增加 Partition 数量 | 提升并行处理能力 |
| 提高容错性 | 增加 Broker 数量,并确保副本因子 ≤ Broker 数 | 确保宕机时仍有副本可用 |
| 均衡负载 | 确保 Partition 均匀分布在所有 Broker 上 | 避免单节点热点 |
最佳实践:在创建 Topic 时,Partition 数量建议设置为 Broker 数量的整数倍(如 3 个 Broker,设置 6、9、12 个 Partition),这样数据分布会更均匀。
5. 如何设计Topic
5.1 命名规范(清晰、一致)
推荐结构:<环境>.<数据领域>.<数据来源>.<事件/对象>.<数据格式>
示例:
prod.log.app.user_click.avro
prod.metric.server.cpu_usage.json
prod.biz.order.order_created.avro
简化版(常用):<业务线>.<数据源>.<事件名>
trade.pc.payment_success
5.2 分区数(Partition)设计 - 决定并发度
核心公式:分区数 ≈ 目标吞吐量 / 单个分区吞吐量
单个分区吞吐量经验值:约 10-50 MB/s。
估算步骤:
- 评估峰值写入速率:例如,高峰时每秒 10 万条消息,每条平均 1KB,则吞吐量为 100,000 msg/s * 1KB ≈ 100 MB/s。
- 计算最小分区数:100 MB/s ÷ 20 MB/s/分区 ≈ 5 个分区。
- 预留缓冲:考虑未来增长,可设定为 2-4 倍,例如 5 * 3 = 15 个分区。通常选择 2 的 N 次方,如 16。
关键限制:分区数只能增加,不能减少。所以初期可适度多分,但不宜过多(每个分区都有开销)。
5.3 副本数(Replication Factor) - 决定可靠性
建议值:
- 开发/测试环境:1(节约资源)。
- 生产环境:至少为 3。这是保证高可用的标准配置,允许同时宕机 2 个节点而不丢失数据。
5.4 数据格式(序列化)
- 推荐 Avro 或 Protobuf,绝不推荐纯字符串 JSON。
原因:它们 Schema 清晰、压缩率高、兼容性好,便于上下游系统解析。配合 Schema Registry(如 Confluent Schema Registry)使用最佳。
5.5 经典设计模式
模式一:按数据来源/设备分离
topic.app_click(App端点击)
topic.web_click(Web端点击)
topic.iot_sensor(物联网传感器)
优点:来源隔离,互不影响,便于按来源分配资源和设置策略。
模式二:按数据优先级/延迟要求分离
topic.high_priority_realtime(延时<1s,分区多,副本多)
topic.low_priority_batch(延时允许分钟级,可压缩,保留时间长)
优点:资源分配更合理,保障核心业务。
模式三:原始数据与标准化数据分离(推荐架构)
1. 原始上报层 (Raw Ingestion):
- `topic.raw_app_log` (所有App原始日志,格式不一,保留7天)
- 消费者:一个统一的“标准化处理服务”。
2. 标准化数据层 (Standardized Data):
- `topic.biz_user_event` (处理后的标准用户事件,Avro格式,保留30天)
- `topic.biz_app_performance` (处理后的性能数据)
- 消费者:数仓、风控、推荐等各业务系统。
优点:解耦,原始数据可追溯,下游用标准数据,清爽稳定。
5.6 两种设计策略
| 设计维度 | 起步阶段(稳中求进,快速验证) | 扩张阶段(精细治理,保障稳定) |
|---|---|---|
| 核心目标 | 低成本启动、架构清晰、便于迭代 | 支撑高并发、保障数据质量、多团队协作 |
| 命名规范 | 极简结构:<业务线>.<事件类型>(例:trade.order_created) | 完整结构:<环境>.<数据领域>.<来源>.<事件>.<格式>(例:prod.trade.app.payment_success.avro) |
| 分区策略 | 固定数量:从 6 个分区 起步(兼顾 扩展性与高性能) | 动态评估:业务并行度:如按 user_id哈希,至少 30 个并发单元 → 分区数 ≥ 30吞吐量规划:(峰值吞吐 / 20MB/s) * 缓冲系数(2-3) |
| 副本数 | 生产环境 直接设为 3(建立高可用基线) | 分级设置:• 核心业务 Topic → 可提升至 4 或 5• 非核心 Topic → 保持 3 |
| 数据格式 | 必须使用 Protobuf/Avro + Schema Registry(杜绝 JSON 字符串) | 强制化 + 流程管控:• 所有 Topic 强制注册 Schema• Schema 变更需走审批与兼容性检查流程 |
| 保留时间 | 统一策略:默认 30 天(简化管理) | 分级策略:• 热数据(订单/支付):30 天• 温数据(用户行为):15 天• 冷数据(调试日志):8 天• 永久数据:归档至廉价存储 |
| 归档机制 | 核心数据先行:仅对支付、用户等核心事件,配置自动化归档至 S3 | 分级自动化体系:实时归档:核心数据实时入数据湖(Iceberg/Hudi)定时归档:所有数据按保留策略过期前批量压缩转储成本可视:建立存储费用监控看板 |
| Topic 数量与治理 | 粗粒度,少量 Topic:按数据领域划分(如 user.events, order.events),共 3-5 个 | 细粒度 + 分层架构:原始接入层:topic.raw_标准数据层:topic.std_衍生数据层:topic.dwd<agg_name>治理措施:• Topic 申请审批流程• 血缘图谱与依赖管理• “僵尸 Topic”定期审计下线 |
5.7 Checklist
- 命名:是否清晰、符合团队规范?
- 分区:数量是否足够支撑未来 1-2 年的峰值流量?(建议 6/12/16/24/32…)
- 副本:生产环境是否 ≥ 3?
- 格式:是否使用 Avro/Protobuf 并配备了 Schema Registry?
- 保留时间:是否根据数据价值设置(如 7天、30天、永久)?
- 归档:重要数据是否有下游归档到 HDFS/S3 的机制?
最后建议:在项目初期,可以先按“数据来源”划分 Topic,并采用 “原始-标准化”两层结构。这个模式能很好地应对初期的复杂性和未来的变化。可以先创建 1-2 个核心 Topic 跑通流程,再按业务扩展。