请用你自己的话,介绍一下 Kafka 是什么?它与传统的 RabbitMQ 或 ActiveMQ 有什么核心区别?
核心回答:
Kafka 是一个分布式、分区、多副本的基于 ZooKeeper(注:新版本正在去 ZK,用 KRaft) 协调的消息队列系统。但它不只是一个 MQ,更是一个分布式流处理平台。
与传统 MQ 的核心区别(重点):
设计哲学不同:
传统 MQ(RabbitMQ/ActiveMQ):是“万金油”,注重消息的可靠性、路由的灵活性(Exchange)和即时消费,消费完通常立即删除。
Kafka:是“日志聚合器”,天生为海量吞吐而生。它不删除消息,而是基于 Offset 位移持久化,允许消费者“重播”历史数据。
吞吐量:
Kafka 利用 PageCache 顺序读写 + 零拷贝(sendfile),单机吞吐可达百万级/秒;传统 MQ 通常为万级。
消费模型:
传统 MQ 消费完消息即删除,是“临时性”的。
Kafka 消费者是“拉取(Pull)”模式,且消费进度(Offset)由客户端控制,这使得它天然支持批处理和流式计算(如 Flink)。
Kafka 是如何保证同一分区内的消息是有序的?如果某个消费者组里的消费者数量超过了分区数,会发生什么?
问题 1 回答:
Kafka 保证分区(Partition)内有序,主题(Topic)级别无序。
机制:每个分区内部维护一个单调递增的 Offset。生产者发送时,若指定了
key,则同一key会哈希到同一分区;若不指定,则采用轮询或随机,但 Kafka 保证同一 Producer 在同一个分区内,发送顺序与追加顺序一致(通过max.in.flight.requests.per.connection=1可严格保序,否则重试可能导致乱序)。
max.in.flight.requests.per.connection 是 Producer(客户端)配置
默认值:5(Kafka 0.11 之前是 1,0.11 起改为 5)
作用域:单个 Producer 到单个 Broker 的连接上,允许同时未确认(unacked)的请求数上限
问题 2 回答(考察 Group 协调机制):
如果消费者组(Group)内的消费者实例数 大于 主题的分区数,多余的那几个消费者将处于“空闲(Idle)”状态,分不到任何分区。
因为 Kafka 的分配策略(如 Range、RoundRobin、Sticky)规定:一个分区只能被同一 Group 内的一个消费者消费。
引申意义:要提高并行度,只能增加分区数,而不是无限增加消费者。
在项目生产中,如果业务要求消息绝对不丢失,你在 Kafka 生产端和 Broker 端分别会怎么配置?acks 参数怎么设?
第一层:生产端(Producer)配置(必须组合拳):
acks = all(或-1):Leader 必须等待所有 ISR(In-Sync Replica)副本都写入成功才返回确认。enable.idempotence = true:开启幂等性,防止网络重试导致的消息重复(且要求acks=all且max.in.flight.requests.per.connection <= 5)。retries = Integer.MAX_VALUE:网络抖动时无限重试,配合delivery.timeout.ms设置总超时。
第二层:Broker 端配置:
min.insync.replicas > 1(通常设为 2):至少要有 2 个副本写入成功,配合acks=all,确保哪怕 Leader 宕机,数据也存在于 Follower 中。replication.factor ≥ 3每个分区的副本总数unclean.leader.election.enable = false:禁止非 ISR 副本竞选 Leader,代价是 ISR 全挂时分区不可用(牺牲可用性),收益是不丢已确认数据(保一致性)。为了绝对不丢,必须设为 false。
关键陷阱提示:
面试官如果追问 “消息真的能 100% 不丢吗?”
回答:严格物理意义上做不到。但在业务层面,acks=all + min.insync=2可保证 Broker 不丢。唯一极端风险是 Broker 所有副本同时掉电(概率极低)。更务实的做法是业务侧做落库补偿或死信队列重放。
ISR = In-Sync Replicas(同步副本集合),指与 Leader 副本保持"足够同步"的所有副本(含 Leader 自身)。它是 Kafka 在"一致性"和"可用性"之间做权衡的核心机制。
min.insync.replicas > 1(通常设为 2)
含义:一个分区至少要有多少个 ISR(In-Sync Replica,同步副本)副本确认写入成功,Leader 才认为这次写入是成功的。
它是配合
acks=all生效的。acks=all说的是“Leader 要等所有 ISR 都写入”,但“所有 ISR”到底有几个,取决于当前 ISR 集合大小;min.insync.replicas则是给这个集合设了一个下限。设为 2 的含义:当 ISR 里只剩 1 个副本时(比如另外两个 Follower 落后或宕机),Broker 会拒绝写入,producer 收到
NotEnoughReplicasException。为什么这是“不丢”的关键:如果
min.insync.replicas=1,那么 ISR 只剩 Leader 自己时,acks=all退化成acks=1——Leader 写成功就返回,但此时数据只有一份,Leader 一宕机数据就丢了。设成 2 就堵住了这个漏洞。
replication.factor ≥ 3
含义:每个分区的副本总数(包括 Leader 和所有 Follower)至少为 3。
这是副本冗余的“总量”指标,决定数据在物理上存了几份。
和
min.insync.replicas的关系:replication.factor是总副本数,min.insync.replicas是允许写入所需的最小同步副本数。二者必须满足:
1 ≤ min.insync.replicas ≤ replication.factor典型生产组合是 replication.factor=3、min.insync.replicas=2。含义是:总共 3 份,至少 2 份同步确认才算写成功。这样允许 1 个副本宕机/落后而不影响写入,同时最多容忍 1 个副本丢失而不丢数据。
如果
replication.factor=3但min.insync.replicas=3:任一 Follower 掉线都会导致无法写入,可用性太低,一般不这么配。如果
replication.factor=2、min.insync.replicas=2:没有任何容错余量,一个副本挂了就写不进去。
max.in.flight.requests.per.connection <= 5
Kafka 的幂等 Producer 在 Broker 端为每个 (PID, 分区) 维护一个最多能容忍的乱序序列号数量,这个容量是 5。也就是说:
Broker 最多能正确处理“序列号领先当前期望值不超过 5”的乱序批次;
如果同时 in-flight 的请求超过 5 个,就可能出现某个批次的序列号领先期望值超过 5,超出 Broker 的乱序缓存窗口,导致:
Broker 无法正确排序/去重,可能抛出
OutOfOrderSequenceException;严重时 Producer 会被迫重置 PID 或报错,幂等性保证被破坏。
因此 Kafka 官方规定:开启幂等性时,max.in.flight.requests.per.connection 必须 ≤ 5。这不是性能调优建议,而是幂等性正确性的硬性要求。
=1:最保守,绝对有序,但吞吐低(每次都要等 RTT)。2~5:允许一定并发,兼顾吞吐,且仍在 Broker 乱序窗口内,幂等性有效。>5:可能破坏幂等性保证,Kafka 客户端在enable.idempotence=true时如果检测到该值 >5,会直接报配置错误(较新版本)或自动降级/告警。
既然你用了 enable.idempotence 防止了生产重复,那消费端的重复消费问题你怎么解决?如果让你设计,你会在代码里怎么处理?
核心认知:
Kafka 只保证至少一次(At Least Once),不保证恰好一次(Exactly Once),除非我们实现了事务性消费。
消费重复产生的根本原因:消费处理成功,但提交 Offset 失败(如处理完重启,位移未提交导致重新拉取)。
我的解决方案(分层设计):
业务幂等性(最推荐):
利用消息中的唯一 UUID/Business ID,在数据库中使用
INSERT IGNORE或ON DUPLICATE KEY UPDATE(MySQL),或在 Redis 中SETNX做防重表(去重表),以业务主键作为唯一约束。
原子性操作(精确一次):
将 消费处理逻辑 与 Offset 提交 放在同一个事务里。例如:使用 Kafka 事务 API(
producer.sendOffsetsToTransaction),或将消息处理后存入 DB,同时将 Offset 存入同一张表,利用 DB 本地事务保证要么都成功,要么都回滚。
下游兜底(最终一致性):
如果下游是第三方接口(不支持幂等),使用状态机或版本号机制,忽略旧版本的处理结果。
enable.idempotence=true 让 Producer 具备"幂等性"——同一 Producer 因重试而重复发送的消息,Broker 只会写入一条。
它解决的是 "至少一次(at-least-once)"语义下,重试导致的重复消息问题。
没有幂等时的问题
Producer 发消息 → Broker 写入成功 → ACK 丢失(网络抖动)→ Producer 认为失败,重试 → Broker 又写一遍 → 消息重复。
Producer ──[msg1]──▶ Broker
◀──[ACK 丢失]── Broker ← 网络问题
──[msg1 重试]──▶ Broker
← Broker 收到两次 msg1
结果:msg1 被写入两次场景:
网络抖动、超时
Broker 重启
retries > 0且发生重试max.in.flight.requests.per.connection > 1时更明显
开启幂等后
Broker 侧维护 (PID, Partition, SeqNum) 去重表,同一 SeqNum 只接受一次。
Producer ──[msg1, PID=1, Seq=0]──▶ Broker
◀──[ACK 丢失]── Broker
──[msg1, PID=1, Seq=0]──▶ Broker
← Broker 发现 Seq=0 已存在
结果:丢弃,只写一次底层机制:PID + Sequence Number
1. Producer ID(PID)
Producer 启动时向 Broker 申请唯一 PID
每个 Producer 实例一个 PID
重启后 PID 会变(除非用事务)
2. Sequence Number(序列号)
每个 (PID, Topic-Partition) 维护一个单调递增序列号
从 0 开始,每条消息 +1
Broker 按序列号判断:
SeqNum = 期望值 → 正常写入
SeqNum < 期望值 → 重复,丢弃
SeqNum > 期望值 → 乱序,抛
OutOfOrderSequenceException
3. Broker 侧去重
Broker 为每个 (PID, Partition) 维护最近 5 个 batch 的序列号,用于去重。
开启后的约束(关键)
enable.idempotence=true 不是孤立的开关,它会强制/要求以下配置:
Kafka 0.11+ 的默认值:
enable.idempotence=true (3.0+ 默认;2.x 默认 false)
acks=all
retries=2147483647
max.in.flight.requests.per.connection=5所以 Kafka 3.0+ 默认就开启幂等,无需手动配。
我们系统遇到了消息积压,有几百万条消息没消费。你作为负责人,如何在不重启服务、不丢失数据的前提下,快速让消费者恢复正常并消化积压?
这个题考察应急处理能力和对 Kafka Rebalance 机制的深刻理解。
(绝不能只回答“增加消费者”,因为分区数固定,加消费者会闲置)
我的分步策略(三步走):
第一步:扩容分区(临时):
动态增加该 Topic 的分区数(
bin/kafka-topics.sh --alter)。注意:仅增加分区不会影响历史数据分布,但新消息会进入新分区。
第二步:横向扩容消费者(关键):
增加新的消费者实例(新机器),Group 重新 Rebalance,新消费者会接管新增的分区。
但是:如果原分区积压严重,原有消费者依然卡在旧分区的大量数据上。
第三步:临时创建“快速转发”队列(经典临时方案):
操作:创建一个新的临时 Topic(如
topic_emergency),并增加大量分区(例如 30 个分区)。启动一个临时消费者(应急程序),从原积压 Topic 快速拉取数据,不进行复杂业务逻辑,只做简单清洗,然后重新生产到
topic_emergency的新分区中。启动对应 30 个消费者的应急服务群,并行消费
topic_emergency,进行真正的业务处理。优点:隔离了积压影响,原服务不受影响,处理完后切回原 Topic。
评论