Kafka之Broker 磁盘写满 → 整个集群只读

一、现象深度解析

1.1 故障链路图
复制代码
磁盘写满 (100%)
     ↓
Broker 日志目录 Offline
     ↓
副本无法写入/同步
     ↓
ISR (In-Sync Replicas) 收缩
     ↓
可用副本数 < min.insync.replicas
     ↓
Producer 报 NOT_ENOUGH_REPLICAS
     ↓
Consumer 无新消息可消费 → 消费停滞
1.2 关键指标解读
指标 正常值 故障值 含义
Disk Usage < 70% 100% 磁盘空间耗尽
UnderReplicatedPartitions 0 > 0 副本不同步
OfflinePartitionsCount 0 > 0 分区不可用
Producer Error NOT_ENOUGH_REPLICAS 写入失败
1.3 为什么是"只读"而非"完全不可用"?

当磁盘写满时:

  • Consumer 仍可读取已有数据(读操作不占用新空间)
  • Producer 无法写入新消息(写操作需要磁盘空间)
  • 副本同步失败(Follower 无法拉取并存储新数据)

🔍 二、根因分析

2.1 Kafka 日志清理机制

Kafka 有两种日志清理策略:

复制代码
# 策略选择
log.cleanup.policy=delete    # 删除旧日志(默认)
log.cleanup.policy=compact   # 日志压缩(保留最新key)
2.2 删除策略的触发条件
复制代码
# 时间触发(默认开启)
log.retention.hours=168      # 保留7天

# 大小触发(容易被忽略!)
log.retention.bytes=-1       # -1表示无限制 ⚠️ 风险点

# 检查周期
log.retention.check.interval.ms=300000  # 5分钟检查一次
2.3 故障发生的数学模型
复制代码
磁盘写入速度 = 生产流量 × 副本数 × 压缩比
磁盘清理速度 = 过期日志量 ÷ 检查周期

当:写入速度 > 清理速度 且 无大小限制
结果:磁盘必然写满

真实场景示例:

复制代码
正常流量:100 MB/s × 3副本 = 300 MB/s
活动流量:500 MB/s × 3副本 = 1500 MB/s
清理速度:约 200 MB/s(受限于I/O和检查周期)
净增长:1500 - 200 = 1300 MB/s
1TB磁盘写满时间:1000GB ÷ 1.3GB/s ≈ 13分钟
2.4 为什么只依赖时间清理不够?
问题 说明
清理有延迟 检查周期5分钟,过期日志不会立即删除
突发流量 短时间内写入量远超预期
日志段未滚动 未到达 segment.ms 的日志段不会被清理
索引文件占用 .index.timeindex 文件也占空间

🚑 三、急救方案

3.1 第一步:快速诊断
复制代码
# 1. 确认磁盘使用情况
df -h /kafka/log

# 2. 查看哪些Topic占用最多
du -sh /kafka/log/* | sort -rh | head -20

# 3. 检查Broker状态
bin/kafka-broker-api-versions.sh --bootstrap-server localhost:9092

# 4. 查看UnderReplicated分区
bin/kafka-topics.sh --bootstrap-server localhost:9092 \
  --describe --under-replicated-partitions
3.2 第二步:紧急扩容:首选
云环境扩容步骤
复制代码
# 1. 云控制台扩容磁盘(无需停机)
# 2. 扩展文件系统
xfs_growfs /kafka/log    # XFS文件系统
# 或
resize2fs /dev/vdb1      # EXT4文件系统

# 3. 验证
df -h /kafka/log

⚠️ 注意: 扩容后Kafka通常会自动恢复,无需重启Broker

3.3 第三步:调整Retention配置(核心)
复制代码
# 正确做法:动态调整Topic级别配置
bin/kafka-configs.sh --bootstrap-server localhost:9092 \
  --entity-type topics \
  --entity-name critical-topic \
  --alter \
  --add-config log.retention.hours=1

# 批量调整所有Topic(谨慎使用)
for topic in $(bin/kafka-topics.sh --bootstrap-server localhost:9092 --list); do
  bin/kafka-configs.sh --bootstrap-server localhost:9092 \
    --entity-type topics \
    --entity-name $topic \
    --alter \
    --add-config log.retention.hours=2
done
3.4 第四步:触发日志清理
复制代码
# 方法1:修改配置后等待自动清理(5分钟检查周期)

# 方法2:强制触发日志清理(Kafka 2.8+)
bin/kafka-log-dirs.sh --bootstrap-server localhost:9092 \
  --describe \
  --topic-list critical-topic

# 方法3:重启Broker(最后手段,会触发Leader选举)
systemctl restart kafka
3.5 ❌ 绝对禁止的操作
复制代码
# ❌ 禁止直接删除日志文件
rm -f /kafka/log/topic-0/*.log

# 原因:
# 1. 破坏日志段完整性,导致offset不连续
# 2. 索引文件(.index)与日志文件不匹配
# 3. consumer offset可能指向已删除的数据
# 4. 可能导致Broker启动失败或数据损坏
# 5. ISR状态混乱,可能触发数据丢失
3.6 如果已经误删了怎么办?
复制代码
# 1. 停止Broker
systemctl stop kafka

# 2. 删除元数据检查点(让Broker重新构建)
rm -f /kafka/log/replication-offset-checkpoint
rm -f /kafka/log/recovery-point-offset-checkpoint

# 3. 启动Broker(会触发日志恢复)
systemctl start kafka

# 4. 监控日志恢复进度
tail -f /kafka/logs/server.log

四、预防方案

4.1 双保险配置策略
复制代码
# server.properties 全局配置

# ========== 时间保留 ==========
log.retention.hours=72           # 保留3天

# ========== 大小保留(关键!)==========
log.retention.bytes=53687091200  # 单partition最大50GB
log.segment.bytes=1073741824     # 单日志段1GB,便于清理

# ========== 清理触发 ==========
log.retention.check.interval.ms=300000  # 5分钟检查
log.cleaner.enable=true                 # 启用清理器

# ========== 副本策略 ==========
min.insync.replicas=2          # 最小同步副本数
unclean.leader.election.enable=false  # 禁止非ISR副本成为Leader

# ========== 磁盘保护 ==========
log.segment.delete.delay.ms=60000     # 删除后等待60秒
4.2 Topic级别差异化配置
复制代码
# 重要Topic:更长保留时间
bin/kafka-configs.sh --bootstrap-server localhost:9092 \
  --entity-type topics --entity-name order-topic \
  --alter --add-config log.retention.hours=168

# 日志Topic:更短保留时间
bin/kafka-configs.sh --bootstrap-server localhost:9092 \
  --entity-type topics --entity-name app-log \
  --alter --add-config log.retention.hours=24

# 限制单个Topic最大空间
bin/kafka-configs.sh --bootstrap-server localhost:9092 \
  --entity-type topics --entity-name big-data-topic \
  --alter --add-config log.retention.bytes=107374182400
4.3 监控告警体系
复制代码
# Prometheus 监控规则示例
groups:
  - name: kafka-disk-alerts
    rules:
      # 预警级别(80%)
      - alert: KafkaDiskUsageWarning
        expr: kafka_log_dir_size_used / kafka_log_dir_size_total > 0.8
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "Kafka磁盘使用率超过80%"
          
      # 危急级别(90%)
      - alert: KafkaDiskUsageCritical
        expr: kafka_log_dir_size_used / kafka_log_dir_size_total > 0.9
        for: 2m
        labels:
          severity: critical
        annotations:
          summary: "Kafka磁盘使用率超过90%,立即处理!"
          
      # 副本异常
      - alert: KafkaUnderReplicatedPartitions
        expr: kafka_server_underreplicatedpartitions > 0
        for: 1m
        labels:
          severity: critical
4.4 自动化防护脚本
复制代码
#!/bin/bash
# kafka-disk-protection.sh

DISK_THRESHOLD=85
KAFKA_HOME=/opt/kafka
BOOTSTRAP_SERVER=localhost:9092

# 检查磁盘使用率
DISK_USAGE=$(df $KAFKA_HOME/log | tail -1 | awk '{print $5}' | sed 's/%//')

if [ $DISK_USAGE -gt $DISK_THRESHOLD ]; then
    echo "磁盘使用率 ${DISK_USAGE}%,触发自动保护"
    
    # 获取占用最大的Topic
    TOPIC=$(du -sh $KAFKA_HOME/log/* | sort -rh | head -1 | awk '{print $2}' | xargs basename)
    
    # 自动缩短保留时间
    $KAFKA_HOME/bin/kafka-configs.sh --bootstrap-server $BOOTSTRAP_SERVER \
        --entity-type topics --entity-name $TOPIC \
        --alter --add-config log.retention.hours=4
    
    # 发送告警
    curl -X POST https://pagerduty.com/webhook \
        -d "topic=$TOPIC&disk_usage=$DISK_USAGE"
fi
4.5 容量规划建议
复制代码
单Broker磁盘容量规划公式:

所需磁盘 = (日均写入量 × 保留天数 × 副本数 × 安全系数) + 缓冲空间

示例计算:
- 日均写入:500 GB
- 保留天数:3天
- 副本数:3
- 安全系数:1.5(应对流量峰值)
- 缓冲空间:20%

所需磁盘 = 500 × 3 × 3 × 1.5 × 1.2 ≈ 8.1 TB

建议配置:10 TB 磁盘

五、进阶优化建议

5.1 日志压缩策略

对于特定场景使用 compact 策略:

复制代码
# __consumer_offsets Topic(Kafka内部使用)
bin/kafka-configs.sh --bootstrap-server localhost:9092 \
  --entity-type topics --entity-name __consumer_offsets \
  --alter --add-config cleanup.policy=compact

# 配置变更日志Topic
bin/kafka-configs.sh --bootstrap-server localhost:9092 \
  --entity-type topics --entity-name config-change-topic \
  --alter --add-config cleanup.policy=compact
5.2 多磁盘配置
复制代码
# 使用多块磁盘分散IO压力
log.dirs=/disk1/kafka,/disk2/kafka,/disk3/kafka

# 配合RAID或云盘使用
# 推荐:每块磁盘独立挂载,Kafka自动均衡
5.3 分区均衡
复制代码
# 检查分区分布是否均匀
bin/kafka-reassign-partitions.sh --bootstrap-server localhost:9092 \
  --verify --reassignment-json-file reassignment.json

# 重新分配分区到磁盘使用率低的Broker
bin/kafka-reassign-partitions.sh --bootstrap-server localhost:9092 \
  --execute --reassignment-json-file reassignment.json

六、故障处理检查清单

复制代码
□ 1. 确认磁盘使用率(df -h)
□ 2. 识别占用最大的Topic(du -sh)
□ 3. 检查UnderReplicated分区状态
□ 4. 优先尝试云磁盘扩容
□ 5. 调整log.retention.hours配置
□ 6. 等待日志清理完成(监控磁盘使用率下降)
□ 7. 验证Producer/Consumer恢复正常
□ 8. 复盘根因,完善监控告警
□ 9. 更新容量规划文档
□ 10. 组织故障复盘会议
阶段 关键动作 注意事项
预防 双保险配置+监控告警 log.retention.bytes 必须设置
发现 80%阈值告警 留出处理时间窗口
急救 扩容优先,调配置其次 禁止直接删除日志文件
恢复 验证生产消费正常 监控ISR恢复情况
复盘 更新容量规划 避免同类问题再次发生

所以呢朋友们,吃一堑长一智:永远不要只依赖单一维度的日志清理策略,时间+大小的双保险是生产环境的标配。

相关推荐
星辰_mya2 小时前
Kafka Consumer Group Rebalance 频繁
分布式·kafka
Coder_Boy_2 小时前
以厨房连锁故事为引,梳理Java后端全技术脉络(JVM到云原生,总结篇)
java·jvm·spring boot·分布式·spring·云原生
崎岖Qiu2 小时前
Redis Set 实战:基于「并、差、交集」的分布式场景应用
数据库·redis·分布式·后端
Drifter_yh10 小时前
【黑马点评】Redisson 分布式锁核心原理剖析
java·数据库·redis·分布式·spring·缓存
百锦再20 小时前
Java的TCP和UDP实现详解
java·spring boot·tcp/ip·struts·spring cloud·udp·kafka
EmmaXLZHONG21 小时前
分布式系统概念与设计笔记(Notes of Distributed Systems Concepts and Design)
笔记·分布式·网络协议·计算机网络
indexsunny1 天前
互联网大厂Java面试实战:Spring Boot与微服务在电商场景的应用
java·spring boot·微服务·面试·kafka·prometheus·电商
时艰.1 天前
分布式事务在电商项目中的应用
java·分布式
百锦再1 天前
Spring Boot Web 后端开发注解核心
开发语言·spring boot·python·struts·spring cloud·kafka·maven