外观
一句话答案
ISR 是与 Leader 保持同步的副本集合,落后超时被踢出,acks=all 要求 ISR 全部确认才算发送成功。
核心要点
Offset 的本质:
每个 Consumer 消费到 Partition 的哪个位置,用一个 long 型偏移量(offset)记录
Kafka 将 offset 存储在内部 Topic:__consumer_offsets(50 个分区,按 groupId hash)
Consumer 重启后从上次提交的 offset 继续消费 → offset 决定了消息会不会丢 / 重复1
2
3
4
2
3
4
自动提交(Auto Commit):
java
props.put("enable.auto.commit", "true"); // 默认开启
props.put("auto.commit.interval.ms", "5000"); // 每 5s 自动提交一次
// 工作方式:每次 poll() 时检查是否到了提交间隔,到了就提交上次 poll 的 offset
// 问题1 — 消息丢失:
// 消费者 poll 了 100 条,处理到第 50 条时自动提交了 offset=100
// 此时消费者宕机 → 第 51~100 条消息丢失(offset 已经提交了,不会再消费)
// 问题2 — 重复消费:
// 消费者处理完了 100 条,还没到自动提交时间就宕机了
// 重启后从上次提交的 offset 开始 → 这 100 条被重复消费1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
手动提交(Manual Commit,推荐):
java
props.put("enable.auto.commit", "false");
// 同步提交(阻塞,确保提交成功)
while (true) {
ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));
for (ConsumerRecord<String, String> record : records) {
processMessage(record); // 先处理
}
consumer.commitSync(); // 处理完再提交(保证不丢失)
}
// 异步提交(非阻塞,性能好但可能提交失败)
consumer.commitAsync((offsets, exception) -> {
if (exception != null) {
log.error("Commit failed for offsets {}", offsets, exception);
}
});
// 最佳实践:正常用异步提交 + 关闭时同步提交兜底
try {
while (running) {
ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));
process(records);
consumer.commitAsync(); // 正常:异步提交(性能高)
}
} finally {
consumer.commitSync(); // 关闭前:同步提交(确保不丢)
consumer.close();
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
Offset 重置策略(auto.offset.reset):
当 Consumer Group 第一次消费 / 已提交的 offset 已过期(日志被清理)时:
earliest:从最早的消息开始消费(不丢数据,但可能重复消费大量旧数据)
latest:从最新的消息开始消费(跳过历史数据,可能丢失未消费的旧消息)
none:找不到 offset 直接抛异常
生产建议:
新上线的消费者用 earliest(确保不丢)
实时处理场景用 latest(只要最新数据)1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
指定 Offset 消费(Seek):
java
// 从指定 offset 开始消费(用于消息回溯、故障恢复)
consumer.seek(new TopicPartition("topic", 0), 1000); // 从 partition 0 的 offset 1000 开始
// 从指定时间戳开始消费
Map<TopicPartition, Long> timestampsToSearch = Map.of(tp, targetTimestamp);
Map<TopicPartition, OffsetAndTimestamp> result = consumer.offsetsForTimes(timestampsToSearch);
consumer.seek(tp, result.get(tp).offset());1
2
3
4
5
6
7
2
3
4
5
6
7
文档完
复习建议:
- Kafka 高吞吐三件套(Q1)是必背点:顺序写 + PageCache + 零拷贝(sendfile),每个都要能展开说清楚原理,不能只背名词
- 消息不丢失(Q3)要从三个维度回答:生产者(acks=-1)、Broker(副本数+min.insync.replicas)、消费者(手动 commit),三端缺一不可
- 幂等性(Q4)建议准备两个方案:数据库唯一索引(简单直接)+ Redis SETNX(高性能),根据面试场景灵活选用
- 延迟消息(Q8)是系统设计高频题,RocketMQ 原生支持延迟级别是加分点,同时要提到"定时任务宕机的兜底方案"(本地消息表 + 幂等补偿)
- KRaft 模式(Q11)是新趋势题,重点记住 ZK 四大痛点 + KRaft 四大优势,Kafka 4.0 已完全移除 ZK 是加分点
- Rebalance(Q12)要能说清触发条件和 Stop-The-World 问题,知道 CooperativeStickyAssignor 是优化方案
- Exactly-Once(Q13)分两层:幂等 Producer(单 Partition 去重)+ 事务(跨 Partition 原子性),面试时先说简单的幂等,再升级到事务
- Offset 管理(Q14)是消息丢失/重复的根源,核心结论:"先处理后提交"保证不丢但可能重复,配合幂等消费(Q4)实现 Exactly-Once
追问与易错
追问方向:
- 这个概念在你的项目中是怎么应用的?
- 和相关技术/方案相比有什么优劣?
- 如果出了问题你会怎么排查?
易错点:
- ❌ 只知道概念不知道原理——面试官会追问底层实现
- ❌ 缺乏实际使用经验——结合项目场景回答更有说服力
💡 记忆锚点
ISR = 跟得上队伍的士兵名单:落后太多就踢出编队,acks=all要求名单上所有人都确认收到命令才算下达成功。Offset是每个消费者的书签,手动提交="读完再夹书签"防丢页,自动提交="定时夹书签"可能跳页或重读。