一、Kafka基础介绍
1.1 什么是Kafka
Kafka是一个分布式流式处理平台,由LinkedIn开源,现为Apache顶级项目。核心定位:消息队列 + 日志系统 + 流处理平台。
发展历程:
- 2011年:LinkedIn开源,解决日志收集问题
- 2012年:加入Apache基金会,正式成为Apache项目
- 2014年:Kafka 0.8.2引入副本机制,可靠性大幅提升
- 2017年:Kafka 0.11引入幂等性和事务支持
- 2022年:KRaft模式GA,脱离ZooKeeper依赖
- 2024年:Kafka 3.7支持Tiered Storage分层存储
版本选择建议:
- 生产环境:2.8.x(KRaft模式成熟)
- 新项目:3.x(推荐KRaft)
1.2 核心角色详解
| 角色 | 说明 | 关键职责 |
|---|---|---|
| Broker | Kafka服务实例 | 存储消息、处理请求 |
| Producer | 消息生产者 | 发送消息到Topic |
| Consumer | 消息消费者 | 从Topic拉取消息 |
| Consumer Group | 消费者组 | 协同消费、负载均衡 |
| Topic | 消息主题 | 消息的逻辑分类 |
| Partition | 分区 | Topic的物理分片,并行基础 |
| Replica | 副本 | 数据冗余,高可用保障 |
| Controller | 集群控制器 | Leader选举、Topic管理 |
Broker架构:
一个Broker = 1个Kafka进程
监听9092端口(默认)
管理多个Partition
与其他Broker通信(副本同步)
1.3 工作原理(核心流程)
生产者发送消息
↓
Topic(逻辑概念,多个Partition)
↓
Partition(按key hash或轮询分配)
↓
顺序写入磁盘日志文件(append only)
↓
多副本同步(Leader + Follower)
↓
消费者组拉取消费(Pull模式)
消息流转详解:
-
Producer发送阶段
- 消息经过拦截器(ProducerInterceptor)
- 序列化(KeySerializer/ValueSerializer)
- 分区器(Partitioner)决定去哪个Partition
- 放入RecordAccumulator缓冲区
- Sender线程批量发送到Broker
-
Broker存储阶段
- 消息追加写到当前Segment的.log文件
- 同时创建稀疏索引(.index文件)
- Follower异步拉取同步
- ISR集合维护
-
Consumer消费阶段
- Consumer发送Fetch请求
- Broker返回对应Partition的消息
- Consumer处理消息
- 提交Offset到__consumer_offsets
1.4 为什么快(原理核心)
Kafka能够实现百万级消息/秒的吞吐量,核心在于以下6个技术优化:
1. 顺序写磁盘
传统随机写:磁头来回移动 → 耗时在寻道
顺序追加写:只移动一次 → 后续连续写入
实际测试:
- 随机写:约 100KB/s
- 顺序写:600MB/s+(普通机械硬盘)
Kafka顺序写原理:消息被追加到日志文件末尾,而不是更新文件的任意位置。这种方式使得磁盘顺序I/O性能接近内存。
2. 零拷贝 ( Zero-Copy )
传统数据拷贝(4次):
磁盘 → 内核缓冲区(DMA) → 用户缓冲区(CPU) → Socket缓冲区(CPU) → 网卡
第1次 第2次 第3次 第4次
Kafka零拷贝(2次):
磁盘 → 内核缓冲区(DMA) → 网卡
第1次 第2次
Java实现:
// 使用FileChannel的transferTo
FileChannel.transferTo(position, count, socketChannel);
底层调用Linux系统调用sendfile(),数据在内核空间直接流转,CPU完全不参与。
3. 页缓存( Page Cache )
Kafka不自己管理缓存,而是充分利用OS的页缓存:
- 写入:先写页缓存,后台异步刷盘
- 读取:优先查页缓存,未命中再读磁盘
- 优点:OS统一管理,内存利用率高
# Kafka不推荐大堆内存,因为依赖页缓存
# 建议:内存 - Kafka堆内存 = 页缓存可用内存
4. 批量处理
- 生产端:多条消息合并成一个Batch发送
- 消费端:一次Fetch返回多条消息
- 效果:减少网络往返次数
# 配置参数
batch.size=16384 (16KB)
linger.ms=10 (等待时间)
5. 消息压缩
压缩算法对比:
| 算法 | 压缩率 | 速度 | CPU消耗 |
|---|---|---|---|
| GZIP | 高 | 慢 | 高 |
| Snappy | 中 | 快 | 中 |
| LZ4 | 中 | 最快 | 低 |
| ZSTD | 最高 | 快 | 中 |
推荐:LZ4(均衡)或ZSTD(高压缩比)
6. 稀疏索引
- 每4KB数据创建一个索引项
- 索引文件也是顺序写
- 查找时二分定位,O(logN)
Partition目录结构:
├── 00000000000000.log # 数据文件
├── 00000000000000.index # 偏移量索引
├── 00000000000000.timeindex # 时间索引
└── 00000000000001.log # 新Segment
二、Kafka优势与不足
2.1 优势详解
| 优势 | 详细说明 |
|---|---|
| 高吞吐 | 百万级消息/秒,支撑大数据量场景 |
| 持久化 | 消息落盘,支持重放和回溯消费 |
| 分布式 | 原生支持水平扩展,集群模式 |
| 多消费者 | 消费组模式,支持发布-订阅 |
| 低延迟 | 毫秒级延迟,满足大多数场景 |
| 生态丰富 | Kafka Connect、Kafka Streams、Flink/Spark |
| 顺序写 | 磁盘顺序写,性能接近内存 |
| 副本机制 | 多副本冗余,Leader-Follower模式 |
高吞吐场景实测:
3台Broker × 8核CPU × 64GB内存 × SSD
→ 写入:100万消息/秒
→ 读取:200万消息/秒
2.2 不足与局限
| 不足 | 详细说明 | 应对方案 |
|---|---|---|
| 异步复制 丢消息 | 极端情况下可能丢消息 | acks=all + 多副本 |
| 重复消费 | At-least-once,需业务去重 | 幂等 + 唯一ID |
| 运维复杂 | 集群管理、分区调优 | 监控 + 自动化 |
| 跨分区无序 | 只能保证分区内有序 | 业务Key + 二次排序 |
| 顺序写瓶颈 | HDD顺序写有限 | 使用SSD |
| 不支持优先级 | 没有优先队列 | 独立Topic |
| 消息确认复杂 | 没有RabbitMQ的ACK机制 | 事务 + 手动提交 |
什么时候不用 Kafka :
- 极低延迟场景(微秒级):考虑RabbitMQ
- 简单队列需求:考虑Redis Stream
- 优先级消息:考虑RabbitMQ
- 消息量小且简单:考虑内存队列
三、使用场景与选型
3.1 典型使用场景
场景1: 异步处理 (最常用)
业务场景: 用户下单后,需要发送短信、邮件、通知库存服务
传统方式: 串行调用,耗时长
下单 → 落库(50ms) → 发短信(100ms) → 发邮件(80ms) → 通知库存(30ms) → 返回
总计:260ms+
Kafka 方式: 异步解耦
下单 → 落库(50ms) → 发送消息到Kafka → 返回
↓
消费者异步处理
(短信/邮件/库存)
代码示例:
// 下单服务
@Transactional
public OrderResult createOrder(Order order) {
// 1. 保存订单
orderMapper.insert(order);
// 2. 发送消息(异步,不阻塞)
kafkaTemplate.send("order-created", order.getId(), toJson(order));
return OrderResult.success(order.getId());
}
// 通知服务
@KafkaListener(topics = "order-created")
public void handleOrderCreated(ConsumerRecord<String, String> record) {
Order order = parse(record.value());
smsService.send(order.getPhone(), "订单已创建");
emailService.send(order.getEmail(), "订单确认");
inventoryService.lockStock(order.getItems());
}
场景2:系统 解耦
业务场景: 订单系统与库存系统、支付系统、物流系统解耦
传统方式:订单系统直接调用库存、支付、物流
问题:任一服务不可用 → 整个流程失败
Kafka方式:
订单系统 → Kafka → 库存服务
→ 支付服务
→ 物流服务
优势:各服务独立,任一服务故障不影响其他
场景3:流量削峰
业务场景: 秒杀活动,瞬间流量是正常的100倍
无Kafka:
秒杀请求 → 订单服务 → 数据库
问题:数据库被打挂
有Kafka:
秒杀请求 → Kafka(堆积) → 订单服务(匀速消费) → 数据库
优势: Kafka吸收峰值,数据库压力可控
场景4:日志收集
客户端 → Filebeat → Kafka → Logstash → Elasticsearch → Kibana
↓
实时计算(Flink)
典型部署:ELK/EFK架构中的Kafak
场景5:事件溯源
记录所有业务事件,支持回放:
// 订单事件
OrderCreatedEvent(orderId, amount, items)
OrderPaidEvent(orderId, payTime, payMethod)
OrderShippedEvent(orderId, expressNo)
OrderDeliveredEvent(orderId, deliveredTime)
3.2 为什么要用Kafka
| 场景 | 为什么选Kafka |
|---|---|
| 高并发日志收集 | 百万吞吐,支持海量设备接入 |
| 微服务 异步通信 | 解耦、削峰、事务支持、生态完善 |
| 大数据实时处理 | Spark/Flink/Kafka Streams原生支持 |
| 事件驱动架构 | Exactly-Once事务、持久化 |
| 消息持久化需求 | 顺序写+页缓存,性能不输内存队列 |
| 需要重放消息 | 支持任意Offset消费 |
| 需要多消费者 | 消费组模式,发布订阅 |
3.3 与其他消息队列对比
| 特性 | Kafka | RabbitMQ | RocketMQ | ActiveMQ |
|---|---|---|---|---|
| 吞吐量 | 百万级 | 万级 | 十万级 | 万级 |
| 持久化 | 强(磁盘) | 中 | 强 | 中 |
| 延迟 | 毫秒 | 微秒 | 毫秒 | 毫秒 |
| 分布式 | 原生 | 集群模式 | 原生 | 较弱 |
| 顺序消息 | 分区内 | 支持 | 支持 | 支持 |
| 事务 | 支持 | 支持 | 强 | 弱 |
| 路由 | 弱 | 强 | 中 | 中 |
| 生态 | 最丰富 | 丰富 | 阿里生态 | 一般 |
| 运维复杂度 | 中高 | 中 | 中 | 低 |
选型建议:
- 大数据、日志、流处理 → Kafka
- 业务消息、灵活性 → RabbitMQ
- 阿里技术栈、事务要求高 → RocketMQ
- 简单场景、 嵌入式 → ActiveMQ
四、常见问题总结
4.1 消息顺序问题
问题描述: 消费者收到的消息顺序与发送顺序不一致
原因分析:
- Kafka只保证分区内有序
- 多分区并行消费
- 生产者重试导致乱序
解决方案:
方案1:相同Key发到同一分区
// 用业务ID作为Key
kafkaTemplate.send("order-event", orderId, message);
// 同一订单的所有消息会发到同一分区
方案2:设置分区器
props.put("partitioner.class", "org.apache.kafka.clients.producer.internals.DefaultPartitioner");
props.put("partitioner.adaptive.partitioning.enable", true);
方案3:业务层排序
@KafkaListener(topics = "order-event")
public void consume(List<ConsumerRecord<String, String>> records) {
// 按消息时间戳排序
records.sort(Comparator.comparing(r -> extractTimestamp(r.value())));
// 顺序处理
for (ConsumerRecord<String, String> record : records) {
process(record.value());
}
}
4.2 消息重复问题
问题描述: 消费者收到两条相同的消息
原因分析:
- 生产者重试(网络抖动、超时)
- 消费者Rebalance
- 手动提交失败但消息已处理
解决方案:
方案1:幂等生产者
props.put("enable.idempotence", true);
// 自动去重:PID + SequenceNumber
// 原理:Broker记录每个PID的最新序列号,重复的丢弃
方案2:手动提交Offset
@KafkaListener(topics = "order")
public void consume(ConsumerRecord<String, String> record, Acknowledgment ack) {
try {
process(record.value());
} finally {
// 处理完再提交
ack.acknowledge();
}
}
方案3:业务去重(最可靠)
@KafkaListener(topics = "order")
public void consume(ConsumerRecord<String, String> record, Acknowledgment ack) {
String messageId = extractMessageId(record.value());
// Redis去重
if (Boolean.TRUE.equals(redisTemplate.opsForValue()
.setIfAbsent("msg:" + messageId, "1", Duration.ofHours(24)))) {
process(record.value());
}
ack.acknowledge();
}
方案4:数据库唯一约束
@Table(uniqueConstraints = @UniqueConstraint(columnNames = "messageId"))
public class OrderEvent {}
public void save(OrderEvent event) {
// 重复插入会抛异常
orderEventMapper.insert(event);
}
4.3 消息丢失问题
问题描述: 消息发出后,消费者没收到
原因分析:
- 生产者acks=0,网络抖动丢消息
- 生产者发送成功但Broker故障
- 消费者处理前Offset已提交
解决方案:
生产者配置:
props.put("acks", "all"); // 全副本确认
props.put("retries", 3); // 重试3次
props.put("retry.backoff.ms", 100); // 重试间隔
props.put("enable.idempotence", true); // 幂等
props.put("max.in.flight.requests.per.connection", 1); // 保证顺序
Broker配置:
replication.factor=3 # 3副本
min.insync.replicas=2 # 至少2个同步副本
unclean.leader.election.enable=false # 不允许非ISR选举
消费者配置:
props.put("enable.auto.commit", false); // 手动提交
// 先处理消息,再提交Offset
@KafkaListener(topics = "order")
public void consume(ConsumerRecord<String, String> record, Acknowledgment ack) {
try {
process(record.value());
ack.acknowledge();
} catch (Exception e) {
// 不 ack,消息会重试
log.error("处理失败", e);
}
}
4.4 消息积压问题
问题描述: 消费太慢,积压越来越多,LAG持续增长
原因分析:
- 消费者实例少,并发不够
- 消费者处理逻辑耗时太长
- 消费者有阻塞操作(DB、RPC)
- 生产量突然增大
解决方案:
方案1:增加 并发数
// Spring Kafka
@KafkaListener(topics = "order", concurrency = "10")
// concurrency <= 分区数
方案2:批量消费
@KafkaListener(topics = "order")
public void consume(List<ConsumerRecord<String, String>> records) {
// 批量处理
List<Order> orders = records.stream()
.map(r -> parse(r.value()))
.collect(Collectors.toList());
orderService.batchProcess(orders);
}
方案3:优化消费逻辑
// ❌ 错误:每条消息都查数据库
for (ConsumerRecord<String, String> record : records) {
User user = userService.getById(record.userId()); // N次DB
process(user, record);
}
// ✅ 正确:批量查询
Set<Long> userIds = records.stream()
.map(r -> extractUserId(r.value()))
.collect(Collectors.toSet());
Map<Long, User> userMap = userService.getByIds(userIds); // 1次DB
for (ConsumerRecord<String, String> record : records) {
User user = userMap.get(extractUserId(record.value()));
process(user, record);
}
方案4: 异步处理
@KafkaListener(topics = "order")
public void consume(ConsumerRecord<String, String> record) {
// 快速提交,异步处理
CompletableFuture.runAsync(() -> process(record.value()));
}
方案5:紧急跳过积压(慎用)
# 跳过所有积压,从最新开始消费
kafka-consumer-groups.sh --bootstrap-server localhost:9092 \
--group my-group --reset-offsets \
--topic order --to-latest --execute
4.5 Rebalance问题
问题描述: 频繁Rebalance,消息消费经常中断
触发条件:
- 消费者加入/离开组
- 订阅的Topic变化
- Topic分区数变化
原因分析:
- 消费者处理时间过长,超时被踢出
- 网络抖动导致心跳失败
- 消费者频繁启停
解决方案:
调大超时配置:
props.put("session.timeout.ms", 60000); // 原来10s → 60s
props.put("heartbeat.interval.ms", 20000); // 心跳间隔
props.put("max.poll.interval.ms", 300000); // 5分钟
使用静态成员:
// 消费者重启不会触发Rebalance
props.put("group.instance.id", "consumer-1");
优化处理逻辑:
// ❌ 错误:同步阻塞处理
@KafkaListener(topics = "order")
public void consume(ConsumerRecord<String, String> record) {
// DB操作很慢
processWithDB(record.value());
}
// ✅ 正确:快速拉取,异步处理
@KafkaListener(topics = "order")
public void consume(ConsumerRecord<String, String> record) {
CompletableFuture.runAsync(() -> processWithDB(record.value()));
}
4.6 分区数设置问题
问题描述: 分区数不合理,导致消费不均或资源浪费
原则:
-
分区数 = 消费者数(理想)
- 3个分区 → 最多3个消费者并行
- 10个分区 → 10个消费者最优
-
分区数 ≤ Broker数 × 磁盘数
- 6 Broker × 2磁盘 = 12分区(上限)
-
分区数决定 并行度
- 期望100万/秒 → 单消费者10万/秒 → 需要10个分区
计算公式:
分区数 = min(期望吞吐量 / 单消费者吞吐, Broker数 * 磁盘数)
# 创建Topic时指定
kafka-topics.sh --create \
--topic order \
--partitions 12 \ # 分区数
--replication-factor 3 \ # 副本数
--bootstrap-server localhost:9092
动态增加分区:
# 增加分区(不能减少)
kafka-topics.sh --alter \
--topic order \
--partitions 20 \
--bootstrap-server localhost:9092
五、线上问题处理
5.1 问题分类与处理
| 问题类型 | 现象 | 原因 | 处理方案 |
|---|---|---|---|
| 消息积压 | records-lag持续增长 | 消费能力不足 | 增加并发、优化逻辑、临时扩容 |
| 重复消费 | 短时间内消费同样消息 | 自动提交/Rebalance | 手动提交、业务去重 |
| 消息丢失 | 消费不到 | acks配置低/Broker故障 | acks=all、多副本、手动提交 |
| 消费延迟 | fetch-latency高 | 网络/磁盘IO | 增加fetch.size、优化磁盘 |
| Rebalance | 消费中断 | 心跳超时/处理超时 | 调大超时、异步处理 |
| Partition不均 | 部分消费者负载高 | Key分布不均 | 重新均衡、Key加盐 |
5.2 常用排查命令
# 1. 查看消费组状态(最常用)
kafka-consumer-groups.sh --bootstrap-server localhost:9092 \
--group my-group --describe
# 输出示例:
# GROUP TOPIC PARTITION CURRENT-OFFSET LOG-END-OFFSET LAG
# my-group order 0 1000 1200 200
# my-group order 1 800 1200 400
# my-group order 2 900 1200 300
# 2. 查看Topic详情
kafka-topics.sh --describe --topic order --bootstrap-server localhost:9092
# 3. 查看消费滞后
# LAG = LOG-END-OFFSET - CURRENT-OFFSET
# LAG > 1000 需要关注
# LAG > 10000 告警
# 4. 重置Offset到最新(跳过积压)
kafka-consumer-groups.sh --bootstrap-server localhost:9092 \
--group my-group --reset-offsets \
--topic order --to-latest --execute
# 5. 重置Offset到最早
kafka-consumer-groups.sh --bootstrap-server localhost:9092 \
--group my-group --reset-offsets \
--topic order --to-earliest --execute
# 6. 重置到指定时间
kafka-consumer-groups.sh --bootstrap-server localhost:9092 \
--group my-group --reset-offsets \
--topic order \
--to-datetime "2026-04-15T10:00:00.000" --execute
# 7. 指定Offset消费
kafka-consumer-groups.sh --bootstrap-server localhost:9092 \
--group my-group --reset-offsets \
--topic order \
--to-offset 1000 --execute
# 8. 查看消息(测试环境)
kafka-console-consumer.sh --topic order \
--from-beginning --max-messages 10 \
--bootstrap-server localhost:9092
# 9. 查看Broker状态
kafka-broker-api-versions.sh --bootstrap-server localhost:9092
# 10. 查看ISR
kafka-topics.sh --describe --topic order --include-unstable --bootstrap-server localhost:9092
5.3 监控指标(必监控)
Producer指标:
| 指标 | 说明 | 告警阈值 |
|---|---|---|
| request-latency-ms | 请求延迟 | > 100ms |
| out-of-order-rate | 乱序率 | > 0 |
| error-rate | 发送错误率 | > 0 |
| batch-size-avg | 平均批次大小 | 异常波动 |
| records-per-request-avg | 每请求消息数 | 过小说明效率低 |
Consumer指标:
| 指标 | 说明 | 告警阈值 |
|---|---|---|
| records-lag | 消费延迟 | > 1000 |
| fetch-rate | 拉取频率 | 持续为0 |
| commit-latency-ms | 提交延迟 | > 1000ms |
| records-consumed-rate | 消费速率 | 突然为0 |
Broker指标:
| 指标 | 说明 | 告警阈值 |
|---|---|---|
| UnderReplicatedPartitions | 未同步分区 | > 0 |
| OfflineLogDirectories | 离线目录 | > 0 |
| ActiveControllerCount | 控制器状态 | != 1 |
| LeaderElectionRate | 选举频率 | 突然升高 |
5.4 生产环境配置
生产者配置:
bootstrap.servers=broker1:9092,broker2:9092,broker3:9092
acks=all
retries=3
retry.backoff.ms=100
enable.idempotence=true
max.in.flight.requests.per.connection=1
batch.size=32768
linger.ms=10
buffer.memory=67108864
compression.type=lz4
max.block.ms=30000
request.timeout.ms=30000
delivery.timeout.ms=120000
消费者配置:
bootstrap.servers=broker1:9092,broker2:9092,broker3:9092
group.id=my-consumer-group
enable.auto.commit=false
auto.offset.reset=earliest
max.poll.records=500
max.poll.interval.ms=300000
session.timeout.ms=45000
heartbeat.interval.ms=15000
fetch.min.bytes=1
fetch.max.wait.ms=500
Broker配置:
# 副本配置
replication.factor=3
min.insync.replicas=2
unclean.leader.election.enable=false
# 日志配置
log.retention.hours=168
log.retention.bytes=-1
log.segment.bytes=1073741824
log.index.size.max.bytes=10485760
log.flush.interval.messages=10000
log.flush.interval.ms=1000
# 网络配置
num.network.threads=3
num.io.threads=8
socket.send.buffer.bytes=102400
socket.receive.buffer.bytes=102400
socket.request.max.bytes=104857600
六、面试场景
6.1 基础概念题
Q1:Kafka为什么这么快?
答:5大核心优化
- 顺序写磁盘:消息追加到日志文件末尾,避免磁盘寻道,顺序写可达600MB/s
- 零拷贝:使用sendfile系统调用,数据从磁盘直接到网卡,2次拷贝 vs 传统4次
- 页缓存:利用OS页缓存,读取优先命中缓存,写入先到缓存异步刷盘
- 批量处理:多条消息合并发送,减少网络往返
- 压缩传输:GZIP/LZ4/ZSTD压缩,减少网络传输量
Q2:Kafka的Offset机制?
答:消费者通过Offset追踪消费进度
-
Offset作用:标记消费者消费到哪条消息
-
Offset存储:保存在__consumer_offsets主题
-
提交方式:
- 自动提交:后台定时提交,可能丢消息
- 手动提交:处理完后提交,更可靠
-
重置策略:
- earliest:从头开始
- latest:从最新开始
Q3:Producer发送流程?
答:6个组件组成的发送管道
App调用send()
↓
ProducerInterceptor(拦截器,可自定义处理)
↓
Serializer(序列化,Key/Value转字节)
↓
Partitioner(分区器,决定去哪个Partition)
↓
RecordAccumulator(缓冲区,批量收集)
↓
Sender(发送线程)
↓
Broker(接收并存储)
Q4:Consumer Group机制?
答:消费组是Kafka的核心消费模型
- 同一组内:一个分区只能被一个消费者消费(避免重复)
- 不同组间:互不影响,每个组都能消费完整消息(发布-订阅)
- Rebalance:成员变化时重新分配分区
- 并行度:分区数 = 最大并行消费者数
6.2 进阶问题
Q5:如何保证消息顺序?
答:3层保证
- 单分区内有序:Kafka保证同一分区内消息有序
- 相同Key发到同一分区:
kafkaTemplate.send("order", orderId, message);
- 业务层二次排序:按时间戳在消费者端排序
Q6:如何保证消息不丢失?
答:生产者+Broker+消费者三方配置
生产者:
props.put("acks", "all"); // 全副本确认
props.put("retries", 3); // 重试
props.put("enable.idempotence", true); // 幂等
Broker:
replication.factor=3
min.insync.replicas=2
消费者:
props.put("enable.auto.commit", false);
// 手动提交
Q7:如何保证消息不重复?
答:3道防线
- 幂等生产者(单分区有效):
props.put("enable.idempotence", true);
// 原理:PID + SequenceNumber自动去重
- 事务(跨分区):
kafkaTemplate.executeInTransaction(ops -> {
ops.send("topic1", data1);
ops.send("topic2", data2);
return true;
});
- 业务去重(最终保障):
// Redis记录已处理消息ID
if (redis.setIfAbsent(msgId, 1)) {
process(message);
}
Q8:ISR机制?
答:In-Sync Replicas
- 定义:与Leader保持同步的副本集合
- 同步标准:replica.lag.time.max.ms内追上了Leader
- 动态调整:落后太多的副本会移出ISR
- Leader选举:只能从ISR中选举
- 配置:
min.insync.replicas=2 # 最小ISR数
Q9:Rebalance过程?
答:5步流程
- 触发:消费者加入/离开/心跳超时
- JoinGroup:所有消费者向Coordinator发送JoinGroup
- 选Leader:Coordinator从组中选一个消费者作为Leader
- 分配:Leader制定分配方案(Range/RoundRobin/Sticky)
- Sync:Coordinator将方案分发给所有成员
Q10:Kafka和RabbitMQ区别?
答:4个维度对比
| 维度 | Kafka | RabbitMQ |
|---|---|---|
| 架构 | 分区+副本 | 交换机+队列 |
| 吞吐 | 百万级 | 万级 |
| 顺序 | 分区内有序 | 支持 |
| 适用 | 大数据、日志 | 业务消息、灵活路由 |
6.3 场景设计题
Q11:如何设计一个消息队列?
答:架构思路
- 存储层:顺序写日志 + 稀疏索引 + 分段
- 通信层:Reactor多路复用 + 批量传输
- 分布式:副本同步 + Leader选举 + 分区分配
- 消费层:Pull模式 + 消费组 + Offset管理
- 可靠性:ACK机制 + 重试 + 幂等 + 事务
Q12:消息积压如何处理?
答:4步解决
-
诊断:查看LAG,确认是消费慢还是生产快
-
临时扩容:
- 增加消费者并发
- 批量消费
- 异步处理
-
优化逻辑:
- 减少DB查询(批量查询)
- 减少阻塞(异步IO)
- 减少计算(预计算)
-
紧急处理:
- 跳过积压(重置Offset)
- 临时扩容消费者
6.4 源码相关(高级)
Q13:Kafka高性能的源码级优化?
答:核心类分析
-
LogSegment:
- append()方法:顺序追加
- roll()方法:分段创建新Segment
-
RecordAccumulator:
- batches()方法:按Partition分组
- ready()方法:判断哪些批次可以发送
-
ConsumerNetworkClient:
- poll()方法:拉取消息 -wakeable()方法:唤醒等待
七、使用心得与体会
7.1 最佳实践
1. 分区设计
// 根据业务ID分区,保证同一业务数据有序
kafkaTemplate.send("order", orderId, message);
// 分区数计算
// 分区数 = 期望吞吐量 / 单消费者吞吐
// 例如:期望60万/秒,单消费者6万/秒 → 分区10
2. 消息设计
// 消息体包含必要字段
{
"messageId": "msg_123456", // 唯一ID,用于去重
"bizId": "order_789", // 业务ID,用于分区
"timestamp": 1713168000000, // 时间戳,用于排序
"version": 1, // 版本号,用于幂等
"data": {...} // 业务数据
}
3. 消费者设计
// 幂等消费模板
@KafkaListener(topics = "order", groupId = "my-group")
public void consume(ConsumerRecord<String, String> record, Acknowledgment ack) {
String messageId = extractMessageId(record.value());
// 1. 检查是否已处理(防重)
if (processedCache.contains(messageId)) {
ack.acknowledge();
return;
}
// 2. 处理消息
process(record.value());
// 3. 标记已处理
processedCache.add(messageId);
// 4. 提交Offset
ack.acknowledge();
}
4. 异常处理
// 死信队列处理
@KafkaListener(topics = "order")
public void consume(ConsumerRecord<String, String> record, Acknowledgment ack) {
try {
process(record.value());
ack.acknowledge();
} catch (Exception e) {
// 发送到死信队列
kafkaTemplate.send("order.DLQ", record.key(), record.value());
ack.acknowledge(); // 仍然确认,避免无限重试
}
}
7.2 避坑指南
1. 避免消息乱序
✅ 相同业务ID用相同Key
✅ 消费者按顺序处理
❌ 大量相同Key导致分区倾斜
2. 避免重复消费
✅ 业务层做去重
✅ Redis记录已处理消息ID
✅ 数据库唯一约束
❌ 依赖自动提交
3. 避免消息丢失
✅ 生产者:acks=all
✅ Broker:多副本
✅ 消费者:手动提交
❌ acks=0追求极致性能
4. 避免Rebalance
✅ 合理设置session.timeout
✅ 处理逻辑要快(< 5分钟)
✅ 使用静态成员
❌ 频繁启停消费者
5. 避免分区倾斜
✅ Key要分散(加盐)
❌ 大量相同Key
✅ 定期检查分区分布
7.3 运维经验
1. 容量规划
磁盘容量 = 日均消息量 × 消息大小 × 保留天数 × 副本数
示例:
- 日均消息:1000万条
- 消息大小:1KB
- 保留7天
- 3副本
容量 = 1000万 × 1KB × 7天 × 3
= 1000 × 10000 × 1 × 7 × 3
≈ 200GB/天
≈ 1.4TB/周
2. 集群部署建议
- 跨机房部署(容灾)
- 磁盘用SSD(性能)
- 内存32GB+(页缓存)
- 3 Broker起步(副本)
- Controller单独部署(大集群)
3. 监控告警
必监控:
-
3. 监控告警
必须监控的核心指标:
- 消费滞后(records-lag):>1000告警
- 未同步分区(UnderReplicatedPartitions):>0告警
- 请求延迟(request-latency-ms):>100ms告警
- 磁盘使用率:>80%告警
- 副本同步状态:定期检查
4. 日常运维
日常任务:
- 定期检查消费滞后
- 定期检查副本状态
- 定期清理过期日志
- 定期检查磁盘使用
- 定期备份配置
故障处理:
- Broker挂掉:检查日志、重启服务
- 磁盘满:清理日志或扩容
- 频繁Rebalance:检查消费者健康
- 消息积压:增加消费者或优化消费逻辑
5. 性能调优经验
瓶颈诊断流程:
1. 查看监控指标定位瓶颈
2. 分析是生产端还是消费端
3. 检查网络/磁盘/CPU/内存
4. 针对性优化配置
常见调优手段:
- 消息大 → 增大batch.size
- 吞吐低 → 增加分区数
- 延迟高 → 用SSD/优化网络
- 积压多 → 增加消费者
八、Spring Boot整合
8.1 快速集成
<!-- pom.xml -->
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
<version>3.2.0</version>
</dependency>
8.2 配置详解
spring:
kafka:
bootstrap-servers: localhost:9092
# 生产者配置
producer:
acks: all # 必须确认
retries: 3 # 重试次数
batch-size: 16384 # 批次大小
linger-ms: 10 # 等待时间
buffer-memory: 33554432 # 缓冲区
compression-type: lz4 # 压缩类型
key-serializer: org.apache.kafka.common.serialization.StringSerializer
value-serializer: org.apache.kafka.common.serialization.StringSerializer
# 消费者配置
consumer:
group-id: my-group # 消费组ID
auto-offset-reset: earliest # 从头消费
enable-auto-commit: false # 手动提交
max-poll-records: 500 # 每次拉取数
key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
# 监听器配置
listener:
ack-mode: manual # 手动确认
concurrency: 5 # 并发数
type: single # 单条消费
8.3 生产者最佳实践
@Service
public class OrderProducer {
@Autowired
private KafkaTemplate<String, String> kafkaTemplate;
/**
* 发送消息(异步回调)
*/
public void send(String topic, String key, String value) {
ListenableFuture<SendResult<String, String>> future =
kafkaTemplate.send(topic, key, value);
future.addCallback(new ListenableFutureCallback<SendResult<String, String>>() {
@Override
public void onSuccess(SendResult<String, String> result) {
log.info("发送成功: topic={}, partition={}, offset={}",
result.getRecordMetadata().topic(),
result.getRecordMetadata().partition(),
result.getRecordMetadata().offset());
}
@Override
public void onFailure(Throwable ex) {
log.error("发送失败: {}", ex.getMessage());
}
});
}
/**
* 发送消息(同步等待)
*/
public SendResult<String, String> sendSync(String topic, String key, String value) {
try {
return kafkaTemplate.send(topic, key, value).get(10, TimeUnit.SECONDS);
} catch (Exception e) {
throw new RuntimeException("发送失败", e);
}
}
/**
* 事务消息
*/
public void sendInTransaction(String topic, String key, String value) {
kafkaTemplate.executeInTransaction(operations -> {
operations.send(topic, key, value);
operations.send(topic + "-backup", key, value);
return true;
});
}
}
8.4 消费者最佳实践
@Service
public class OrderConsumer {
/**
* 基本消费
*/
@KafkaListener(topics = "order", groupId = "order-group")
public void consume(ConsumerRecord<String, String> record) {
log.info("收到消息: key={}, value={}", record.key(), record.value());
try {
Order order = parseOrder(record.value());
processOrder(order);
} catch (Exception e) {
log.error("处理失败: {}", e.getMessage());
// 不ack,消息会重试
}
}
/**
* 带确认的消费
*/
@KafkaListener(topics = "order-ack", groupId = "order-ack-group")
public void consumeWithAck(ConsumerRecord<String, String> record,
Acknowledgment ack) {
try {
process(record.value());
ack.acknowledge(); // 确认
} catch (Exception e) {
log.error("处理失败", e);
// 不ack,消息会重试
}
}
/**
* 批量消费
*/
@KafkaListener(topics = "order-batch", groupId = "order-batch-group")
public void consumeBatch(List<ConsumerRecord<String, String>> records) {
log.info("批量收到: {}条", records.size());
List<Order> orders = records.stream()
.map(r -> parseOrder(r.value()))
.collect(Collectors.toList());
// 批量处理
orderService.batchProcess(orders);
}
/**
* 错误处理
*/
@KafkaListener(topics = "order-error", groupId = "order-error-group",
errorHandler = "kafkaErrorHandler")
public void consumeWithError(ConsumerRecord<String, String> record) {
process(record.value());
}
}
/**
* 全局错误处理器
*/
@Component
public class KafkaErrorHandler implements KafkaListenerErrorHandler {
@Autowired
private KafkaTemplate<String, String> kafkaTemplate;
@Override
public Object handleError(Message<?> message, ListenerExecutionFailedException e) {
log.error("消费异常: {}", message.getPayload(), e);
// 发送到死信队列
kafkaTemplate.send("order.DLQ", message.getPayload().toString());
return null;
}
}
8.5 高级特性
1. 消息拦截器
@Component
public class KafkaProducerInterceptor implements ProducerInterceptor<String, String> {
@Override
public ProducerRecord<String, String> onSend(ProducerRecord<String, String> record) {
// 添加时间戳
record.headers().add("timestamp", String.valueOf(System.currentTimeMillis()).getBytes());
return record;
}
@Override
public void onAcknowledgement(RecordMetadata metadata, Exception exception) {
if (exception != null) {
log.error("发送失败", exception);
}
}
@Override
public void close() {}
}
2. 消息过滤器
@KafkaListener(topics = "order", groupId = "filter-group")
public void consume(ConsumerRecord<String, String> record) {
// 业务过滤
if (shouldSkip(record.value())) {
return;
}
process(record.value());
}
3. 定时任务结合
@Component
public class ScheduledProducer {
@Autowired
private KafkaTemplate<String, String> kafkaTemplate;
@Scheduled(cron = "0 0 9 * * ?") // 每天9点
public void sendDailyReport() {
kafkaTemplate.send("daily-report", generateReport());
}
}
九、总结
9.1 核心要点
Kafka学习路径:
1. 基础概念:Topic、Partition、Offset、Consumer Group
2. 原理:顺序写、零拷贝、页缓存、批量处理
3. 可靠性:ACK、幂等、事务、副本
4. 顺序:Key分区、分区内有序
5. 实战:生产者、消费者、Spring Boot
6. 运维:监控、调优、问题排查
9.2 学习资源
官方文档:kafka.apache.org
书籍:《Kafka权威指南》
源码:GitHub apache/kafka
社区:Kafka mailing list