Skip to content
进阶

一句话答案

ISR 是与 Leader 保持同步的副本集合,落后超时被踢出,acks=all 要求 ISR 全部确认才算发送成功。

核心要点

Offset 的本质:

每个 Consumer 消费到 Partition 的哪个位置,用一个 long 型偏移量(offset)记录
Kafka 将 offset 存储在内部 Topic:__consumer_offsets(50 个分区,按 groupId hash)

Consumer 重启后从上次提交的 offset 继续消费 → offset 决定了消息会不会丢 / 重复

自动提交(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 条被重复消费

手动提交(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();
}

Offset 重置策略(auto.offset.reset):

当 Consumer Group 第一次消费 / 已提交的 offset 已过期(日志被清理)时:

earliest:从最早的消息开始消费(不丢数据,但可能重复消费大量旧数据)
latest:从最新的消息开始消费(跳过历史数据,可能丢失未消费的旧消息)
none:找不到 offset 直接抛异常

生产建议:
  新上线的消费者用 earliest(确保不丢)
  实时处理场景用 latest(只要最新数据)

指定 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. Kafka 高吞吐三件套(Q1)是必背点:顺序写 + PageCache + 零拷贝(sendfile),每个都要能展开说清楚原理,不能只背名词
  2. 消息不丢失(Q3)要从三个维度回答:生产者(acks=-1)、Broker(副本数+min.insync.replicas)、消费者(手动 commit),三端缺一不可
  3. 幂等性(Q4)建议准备两个方案:数据库唯一索引(简单直接)+ Redis SETNX(高性能),根据面试场景灵活选用
  4. 延迟消息(Q8)是系统设计高频题,RocketMQ 原生支持延迟级别是加分点,同时要提到"定时任务宕机的兜底方案"(本地消息表 + 幂等补偿)
  5. KRaft 模式(Q11)是新趋势题,重点记住 ZK 四大痛点 + KRaft 四大优势,Kafka 4.0 已完全移除 ZK 是加分点
  6. Rebalance(Q12)要能说清触发条件和 Stop-The-World 问题,知道 CooperativeStickyAssignor 是优化方案
  7. Exactly-Once(Q13)分两层:幂等 Producer(单 Partition 去重)+ 事务(跨 Partition 原子性),面试时先说简单的幂等,再升级到事务
  8. Offset 管理(Q14)是消息丢失/重复的根源,核心结论:"先处理后提交"保证不丢但可能重复,配合幂等消费(Q4)实现 Exactly-Once
追问与易错

追问方向:

  • 这个概念在你的项目中是怎么应用的?
  • 和相关技术/方案相比有什么优劣?
  • 如果出了问题你会怎么排查?

易错点:

  • ❌ 只知道概念不知道原理——面试官会追问底层实现
  • ❌ 缺乏实际使用经验——结合项目场景回答更有说服力

💡 记忆锚点

ISR = 跟得上队伍的士兵名单:落后太多就踢出编队,acks=all要求名单上所有人都确认收到命令才算下达成功。Offset是每个消费者的书签,手动提交="读完再夹书签"防丢页,自动提交="定时夹书签"可能跳页或重读。