外观
Kafka 常见故障处置
按现象查。每条给:看什么、输出怎么读、根因在哪。
案例 1:消息积压持续增长
现象:监控上 lag 从几百涨到几十万,业务侧"数据延迟"。
bash
B=10.0.0.31:9092
K=/opt/kafka/bin
$K/kafka-consumer-groups.sh --bootstrap-server $B --describe --group order-grouptext
GROUP TOPIC PARTITION CURRENT-OFFSET LOG-END-OFFSET LAG CONSUMER-ID HOST CLIENT-ID
order-group order 0 120400 512400 392000 consumer-order-group-1-8f3a /10.0.0.41 consumer-1
order-group order 1 99800 499800 400000 consumer-order-group-2-a1b2 /10.0.0.42 consumer-2
order-group order 2 135500 535500 400000 consumer-order-group-3-c3d4 /10.0.0.43 consumer-3判断链条:
- 有活跃消费者(
CONSUMER-ID有值)但 lag 还在涨 → 消费速度 < 生产速度,是真实积压。 - 没有活跃消费者(全是
-)→ 消费服务挂了,先看进程。 - 消费者数 < 分区数 → 并行度不够。
bash
# 消费者数
$K/kafka-consumer-groups.sh --bootstrap-server $B --describe --group order-group --members
# 分区数
$K/kafka-topics.sh --bootstrap-server $B --describe --topic order | grep -c 'Partition:'text
GROUP CONSUMER-ID HOST CLIENT-ID #PARTITIONS
order-group consumer-order-group-1-8f3a /10.0.0.41 consumer-1 2
order-group consumer-order-group-2-a1b2 /10.0.0.42 consumer-2 2
order-group consumer-order-group-3-c3d4 /10.0.0.43 consumer-3 2
66 个分区、3 个消费者,每个 2 分区,并行度已满。要提吞吐只能:
| 手段 | 前提 | 代价 |
|---|---|---|
| 优化单条处理耗时 | 无 | 看日志找慢点(外部调用、同步写库) |
| 批量化处理 | 无 | 改代码 |
| 扩分区 + 扩消费者 | 不要求同 key 保序 | 加分区会打乱 key 顺序 |
| 消费逻辑异步化 | 位移要自己管 | 复杂度上升 |
不要靠 --to-latest 掩盖积压
reset-offsets --to-latest 会丢弃全部积压消息。除非业务确认这批数据可以跳过,否则等于主动丢数据。
案例 2:leader 频繁切换,ISR 一直抖动
现象:日志里大量 Partition order-3 marked as failed、Leader and ISR request,客户端偶尔超时。
bash
$K/kafka-topics.sh --bootstrap-server $B --describe --under-replicated-partitions
grep -Ei 'isr|leader|shrink|expand' /opt/kafka/logs/server.log | tail -30text
Topic: order Partition: 3 Leader: 1 Replicas: 1,2,3 Isr: 1
[2026-10-27 20:11:02,441] INFO [Partition order-3 broker=1] Shrinking ISR from 1,2,3 to 1,2 (kafka.cluster.Partition)
[2026-10-27 20:11:04,882] INFO [Partition order-3 broker=1] Shrinking ISR from 1,2 to 1 (kafka.cluster.Partition)
[2026-10-27 20:11:31,110] INFO [Partition order-3 broker=1] Expanding ISR from 1 to 1,2 (kafka.cluster.Partition)ISR 反复收缩又恢复,通常三个原因:
1. 副本拉取超时(最常见)
bash
grep -Ei 'replica.*(timeout|lag|fetch)' /opt/kafka/logs/server.log | tail -10text
WARN [ReplicaFetcher replicaId=2, leaderId=1] Error in response for fetch request (kafka.server.ReplicaFetcherThread)
java.net.SocketTimeoutException: Read timed out对策(broker 配置):
ini
replica.fetch.max.bytes=1048576
replica.socket.timeout.ms=30000
replica.fetch.wait.max.ms=500
replica.lag.time.max.ms=30000
num.replica.fetchers=4num.replica.fetchers 从默认 1 调到 4,对多分区场景提升明显。
2. broker GC 停顿
bash
jstat -gcutil $(jps | grep Kafka | awk '{print $1}') 1000 5text
S0 S1 E O M CCS YGC YGCT FGC FGCT GCT
0.00 0.00 12.30 45.20 94.10 91.20 842 8.234 3 6.120 14.354FGCT 6 秒、FGC 3 次,说明有 Full GC。堆太大或太大对象导致。Kafka 推荐 6~8G 堆,再大 GC 停顿反而变长(它主要靠页缓存,不需要大堆)。
ini
# /opt/kafka/bin/kafka-server-start.sh 里或环境变量
export KAFKA_HEAP_OPTS="-Xmx6G -Xms6G -XX:+UseG1GC -XX:MaxGCPauseMillis=20"3. 磁盘 IO 饱和
bash
iostat -x 1 3text
Device r/s w/s rkB/s wkB/s await %util
vda 120.4 1840.2 4816.0 147216.0 8.2 98.4%util 98%、await 8ms → 磁盘打满。Kafka 是顺序写,正常 await 应在 1~3ms。换 SSD、拆盘、降保留时长。
案例 3:磁盘写满,broker 挂掉
现象:broker 突然退出,日志 java.io.IOException: No space left on device。
bash
df -h /data
du -sh /data/kafka/logs
/opt/kafka/bin/kafka-log-dirs.sh --bootstrap-server $B --describe --topic-list order | head -20text
Filesystem Size Used Avail Use% Mounted on
/dev/vdb 500G 500G 0 100% /data
120G /data/kafka/logs先救火:删过期数据。
bash
# 临时缩短某个大 topic 的保留时间(会立刻触发清理)
$K/kafka-configs.sh --bootstrap-server $B --entity-type topics --entity-name order \
--alter --add-config retention.ms=3600000
# 手工触发一次清理检查(不用重启)
$K/kafka-configs.sh --bootstrap-server $B --entity-type topics --entity-name order \
--alter --add-config segment.ms=3600000等 5 分钟(log.retention.check.interval.ms)看磁盘是否下降。
如果还是不动,说明segment 粒度问题:一个 1GB 的 segment 只有整段过期才会被删。调小:
ini
log.segment.bytes=268435456 # 256MB注意 log.segment.bytes 是新建 segment 生效,已有的大 segment 不会自动切小。
别直接删 log.dirs 里的文件
rm -rf /data/kafka/logs/order-3 会让 broker 认为这个分区文件丢失,重建时可能触发数据不一致,甚至起不来。只能通过 retention 或 kafka-delete-records.sh 删。
长期措施:
| 措施 | 说明 |
|---|---|
设 retention.bytes | 每个分区上限,防止单 topic 吃满盘 |
| 加盘 | log.dirs 支持多目录,Kafka 会自己均衡 |
| 分层存储(Tiered Storage) | 3.6+ 支持把老数据放对象存储 |
监控 %util 和磁盘使用率 | 70% 就告警,别等 100% |
案例 4:时间不同步导致消息"消失"
现象:按时间消费 --offset-for-times 拿不到预期数据;或者消息刚写进去就被删了。
bash
timedatectl
chronyc tracking
date -utext
Local time: Tue 2026-10-27 20:31:04 CST
Universal time: Tue 2026-10-27 12:31:04 UTC
Time zone: Asia/Shanghai (CST, +0800)
NTP enabled: yes
NTP synchronized: no
Reference ID : 00000000 ()
Stratum : 0
System time : 0.000000000 seconds fast of NTP timeNTP synchronized: no 就是问题。三台 broker 时间差超过几十秒时:
retention.ms按segment 最后修改时间判断,时间快的机器可能提前删数据。- 按时间查偏移量算错。
- 事务超时、
log.roll.hours行为异常。
bash
# 开时间同步
timedatectl set-ntp true
chronyc sources -vtext
MS Name/IP address Stratum Poll Reach LastRx Last sample
===============================================================================
^* 10.0.0.1 3 6 377 32 -12us[ -45us] +/- 1.2ms^* 表示已同步。差值是微秒级就正常。
容器里时间不同步更隐蔽
宿主机同步了,容器默认共享宿主机的时钟,一般没问题。但如果容器有独立时间命名空间(某些安全沙箱),date 会漂移。部署时检查容器内 date 与宿主机差多少。
案例 5:broker 起不来
现象:systemctl start kafka 失败,或者起来几秒就退出。
bash
systemctl status kafka --no-pager
journalctl -u kafka -n 50 --no-pager常见五类错误:
| 日志 | 根因 | 处置 |
|---|---|---|
Cluster ID mismatch(KRaft) | meta.properties 的 cluster id 与配置不符 | 三台统一格式化,或换回原 log.dirs |
Log directory ... is not empty | 对已有数据的目录执行 format | 确认数据可弃后清空,否则恢复原目录 |
Address already in use | 端口被占(旧进程没杀干净) | ss -lntp | grep 9092,杀进程 |
Inconsistent clusterId | 混用了不同集群的元数据目录 | 恢复备份或重新格式化 |
java.lang.OutOfMemoryError: Map failed | 内存不足或 vm.max_map_count 太小 | 加内存;sysctl -w vm.max_map_count=262144 |
bash
ss -lntp | grep -E '9092|9093'
ps -ef | grep -i kafka | grep -v greptext
LISTEN 0 50 10.0.0.31:9092 0.0.0.0:* users:(("java",pid=19233,fd=140))有残留进程先 kill -15,等 30 秒再 kill -9。别直接 -9,会留下没关闭的日志文件和锁。
案例 6:确认消息是否丢失
现象:下游说少了数据,要定位是"没发"、"没写进去"还是"消费跳过了"。
第一步:确认 broker 上有没有
bash
# 看分区的最新位移
$K/kafka-get-offsets.sh --bootstrap-server $B --topic order --time -1text
order:0:512400
order:1:499800
order:2:535500和故障前的数字对比,没涨说明生产者没写进来。
第二步:确认消息真的在磁盘上
bash
$K/kafka-dump-log.sh --files /data/kafka/logs/order-0/*.log \
--print-data-log 2>/dev/null | grep -c 'payload:'或者按业务字段搜:
bash
$K/kafka-dump-log.sh --files /data/kafka/logs/order-0/*.log \
--print-data-log 2>/dev/null | grep 'orderId-88213'只能查还在保留期内的数据
超过 retention.ms 的数据已经被物理删除,dump-log 也找不到。要查历史只能靠下游数据库或归档。
第三步:确认消费没跳过
bash
$K/kafka-consumer-groups.sh --bootstrap-server $B --describe --group order-group如果 CURRENT-OFFSET 在故障期间跳变(比如从 1200 直接到 1400),说明有 seek() 或 reset-offsets 操作跳过了消息。
常见的丢数据原因
| 原因 | 特征 |
|---|---|
acks=1 + leader 挂 | 生产端显示成功,broker 上不存在 |
unclean.leader.election.enable=true | 日志里有 UncleanLeaderElectionsPerSec,旧数据被覆盖 |
| 消费端自动提交 + 异步处理 | CURRENT-OFFSET 超过实际处理进度 |
retention 太短 | 消息在消费前就被删了 |
| 生产者缓冲未 flush 就退出 | 最后一批没发出去 |
案例 7:生产者超时、消息太大
现象:TimeoutException: Expiring N record(s) for order-3:30000 ms has passed 或 RecordTooLargeException。
java
// 超时类
org.apache.kafka.common.errors.TimeoutException: Expiring 1 record(s) for order-3:
30000 ms has passed since batch creationExpiring 表示消息在客户端缓冲区待了 30 秒还没发出去。原因:
| 原因 | 判断 |
|---|---|
| broker 不可达 | kafka-broker-api-versions.sh 能否列出 |
| 分区 leader 不可用 | --unavailable-partitions 有输出 |
buffer.memory 满 | 生产者端 buffer-available-bytes 接近 0 |
| 消息太大被 broker 拒 | 日志里有 RecordTooLargeException |
消息太大要三层一起改:
bash
# broker
echo 'message.max.bytes=5242880' # 5MB
echo 'replica.fetch.max.bytes=5242880'
# topic
$K/kafka-configs.sh --bootstrap-server $B --entity-type topics --entity-name order \
--alter --add-config max.message.bytes=5242880
# 消费者
# fetch.max.bytes >= 5MB,max.partition.fetch.bytes >= 5MB不要为了大消息把上限调到几十 MB
单条消息 10MB 会让批量发送失效、复制变慢、内存压力大。正确做法是把大 payload 放对象存储,Kafka 里只传 URL 或 ID。
案例 8:消费者卡住不动,日志刷 rebalance
现象:lag 不降,日志每几分钟出现一次 Revoking previously assigned partitions。
bash
grep -Ei 'rebalance|revoking|max.poll|heartbeat' /var/log/order-service/app.log | tail -20text
2026-10-27 20:40:11 INFO [order-group] Revoking previously assigned partitions [order-0, order-1]
2026-10-27 20:40:11 INFO [order-group] (Re-)joining group
2026-10-27 20:45:31 WARN [order-group] consumer poll timeout has expired. This means the time between
subsequent calls to poll() was longer than the configured max.poll.interval.msmax.poll.interval.ms 超时。要么单批处理太慢,要么处理线程卡死。
bash
# 看线程栈,确认卡在哪
jstack <pid> | grep -A 20 'kafka-coordinator-heartbeat\|order-consumer'常见卡点:
- 同步调用下游 HTTP,对方超时 30 秒。
- 同步写数据库,锁等待。
- 大批量处理,
max.poll.records=500太大。
治标:
yaml
spring:
kafka:
consumer:
max-poll-records: 100
properties:
max.poll.interval.ms: 600000治本:把单批处理时间压到 1 分钟以内,或者改成"poll 后立即入本地队列 + 线程池处理 + 手动提交"。
异步化后不能再用自动提交
一旦处理异步化,poll() 返回时消息还没处理完。自动提交会提交未完成的位置,进程一挂全丢。必须手动 commitSync(),在异步任务完成后按水位提交。
案例 9:UnknownTopicOrPartitionException
现象:生产者或消费者报 topic 不存在,但 --list 里明明有。
bash
$K/kafka-topics.sh --bootstrap-server $B --list | grep '^order$'
$K/kafka-topics.sh --bootstrap-server $B --describe --topic ordertext
order
Topic: order TopicId: 7cQ3nR8vW1xY4zA6bC9dEf PartitionCount: 6 ReplicationFactor: 3 Configs:
Topic: order Partition: 0 Leader: 3 Replicas: 3,1,2 Isr: 3,1,2四个原因:
| 原因 | 判断 |
|---|---|
| 客户端连错集群 | 检查 bootstrap.servers,两台机器可能连了不同环境 |
| 分区数未就绪(刚创建) | 等 1~2 秒重试,或看 controller 日志 |
auto.create.topics.enable=false 且 topic 真不存在 | 拼写/大小写错误 |
| 元数据缓存过期 | 客户端重启,或等 metadata.max.age.ms |
刚创建就写常见于自动化脚本:kafka-topics.sh --create 返回成功,但 /brokers/topics 在 ZK/KRaft 里还没传播到所有 broker。加个重试即可。
快速对照表
| 现象 | 第一步 | 常见根因 |
|---|---|---|
| lag 增长 | consumer-groups --describe | 消费慢、消费者挂、并行度不足 |
| 客户端超时 | --unavailable-partitions | leader 不可用、ISR 不足 |
| ISR 抖动 | grep Shrinking ISR | 网络、GC、磁盘 IO |
| 磁盘满 | df -h + log-dirs | retention 配置、segment 太大 |
| broker 起不来 | journalctl -u kafka | 端口占用、cluster id、meta.properties |
| 消息重复 | 检查提交时机 | rebalance、处理完未提交、自动提交 |
| 消息丢失 | kafka-get-offsets + dump-log | acks=1、unclean 选举、retention 太短 |
| 消费卡住 | jstack + rebalance 日志 | max.poll.interval 超时 |
| topic 不存在 | --describe | 连错集群、拼写、元数据未传播 |