kafka
1 · kafka#
分布式事件流平台
S 大规模分布式系统中,各服务之间需要实时传递海量事件/数据(日志、指标、用户行为等),且数据需要被多个下游系统独立消费。
C 传统点对点通信导致系统紧耦合、难以扩展;同步调用在高峰期造成级联故障;数据一旦消费就丢失,无法回放或审计。
Q 如何在解耦生产者与消费者的同时,实现高吞吐、可持久化、可回放、可水平扩展的数据传输?
A Kafka 以分布式追加写日志为核心抽象:生产者只管写,消费者按自己的速度读并记录 offset,数据持久化在多副本分区中,可回放、可复制、可水平扩展。
问题:规模系统之间的数据传输
方法:以追加写日志为核心抽象,构建分布式提交日志;消费者只记录自己读到哪儿
Kafka 把数据的生产、存储、消费解耦,并让数据像日志一样可回放、可复制、可扩展。
1.1 · 核心概念
数据流角度:Producer 写消息 -> Broker 存储并复制消息 -> Consumer 消费消息。
业务角度:Topic 是逻辑上的消息分类;Partition 是 Topic 下的实际分片,也是实际读写单位;Broker 是 Kafka 集群中的服务器节点,负责保存多个 Partition 副本,并参与副本复制。
- Topic:逻辑上的消息分类,表示一类业务日志或事件
- Partition:一个 Topic 可以拆成多个分区;它是实际的读写单位
- 是扩展性、并行消费、分区内有序的基础
- 数据会分布到不同 Broker 上
- Offset:分区内消息的编号,消费者用它记录读到哪里
- Broker:Kafka 集群中的单个服务器节点,负责保存多个 Partition 副本
- Producer:生产者,负责写消息
- Consumer:消费者,负责读消息
- Consumer Group:共同消费一个 Topic 的一组消费者
- 一个 Partition 可以被多个消费组消费
- 但在同一个消费组内,一个 Partition 同一时刻只能分配给一个消费者
- 消费者组本质上就是
一套独立的阅读进度 - Kafka 不会因为
已消费就立刻删除消息,而是按保留策略保存一段时间
1.2 · 数据组织与扩展性
核心靠 Partition
- Partition 决定了 Kafka 的吞吐上限、并行消费能力和水平扩展能力
- 分区越多,通常越容易横向扩展,但管理成本和重平衡成本也会上升
- 消费者组内的并发上限受 Partition 数限制
- 分区数 < 消费者数:多出来的消费者会空闲
- 分区数 = 消费者数:通常能充分利用组内并发
- 分区数 > 消费者数:单个消费者需要处理多个分区
1.3 · 顺序性(分区内有序)
- Kafka 只保证
分区内有序 - 不保证一个 Topic 的全局有序
- 如果业务要求
同一类对象的消息有序,通常做法是让同一个 key 始终进入同一个分区
1.4 · 生产者设计
如何稳定高效地发?
分区策略:决定消息写入哪个分区?
- 指定 key:同一 key 进入同一分区,适合做局部有序
- 轮询:更均匀地打散流量
- 自定义:按业务规则路由
生产者常见关注点:
- 幂等性:避免因为重试导致重复写入
- 重试:临时失败时自动重发,但要结合幂等性一起看
1.5 · 消费者设计
Pull 模型:消费者主动拉取数据。
好处:
- 消费速度由消费者自己控制
- 更适合批量拉取
- 可以回放历史数据
- 消费者可以通过指定 offset,重新读取之前已经消费过的消息
消费者需要决定何时提交 offset:
- 自动提交:简单,但可能导致重复消费或丢消息窗口变大
- 手动提交:更可控,通常在业务处理成功后再提交
Rebalance:消费者加入、离开、心跳超时时,Kafka 会重新给组内消费者分配分区;期间整个组可能短暂停顿。
- 常见触发:消费者崩溃、新消费者加入、处理太慢导致心跳超时
- 缓解方式:Cooperative Rebalance(增量再平衡,只迁移受影响分区)、Static Group Membership(固定成员 ID,短暂重启不触发)
1.6 · 可靠性(单 leader + followers 拉取)#
- 每个 Partition 可以有多个副本
- Leader:负责对外读写
- Follower:同步 Leader 数据
- ISR(In-Sync Replicas):当前
跟得上Leader 的副本集合 acks:生产者写入确认机制acks=0:最快,但可能丢acks=1:Leader 写入成功就返回acks=all/-1:等待 ISR 中足够副本确认后返回
投递语义:
- at most once:最多一次,可能丢,不重复
- at least once:至少一次,可能重复
- exactly once:恰好一次
1.7 · 为什么快:设计选择
- 顺序 IO,而不是随机 IO
- 零拷贝:减少数据在内核态、用户态之间的复制
- 批量处理:批量收发,而不是一条条处理
- Page Cache:充分利用操作系统页缓存
- 写路径:Producer -> Broker -> Page Cache(写入内存后较快返回)->
异步刷盘 - 读路径:Consumer 拉取时,如果数据还在 Page Cache,可通过零拷贝直接从内存发到网卡
- 写路径:Producer -> Broker -> Page Cache(写入内存后较快返回)->
1.8 · 常见问题
问题:为什么追随者不提供服务?
主要是为了简化一致性模型,保证写后立刻读、单调读等语义更容易成立。
问题:如何实现 topic 内消息的有序?
单分区内天然有序;如果要保证某类业务消息有序,本质上是分区策略问题,让同一业务 key 始终进入同一分区。
问题:如何实现不丢失数据?
生产者侧通常要求 acks=all;再结合副本机制与消费者正确提交 offset,才能把丢失风险压低。
问题:如何保证消息不被重复消费?
重复通常很难彻底避免,实际做法是让重复无害:
- 生产者侧:开启幂等,减少因重试导致的重复发送
- 消费者侧:做消费幂等,比如唯一 ID、Redis 近期处理 key、数据库唯一索引等
问题:消息积压怎么处理?生产太快?消费太慢?
- 增加消费者,但不能超过分区数,否则多出来的消费者会空闲
- 如果分区数不够,需要扩分区,再增加消费者
- 紧急情况可以起临时消费组把积压数据转储出来,再慢慢处理