一、现象深度解析
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恢复情况 |
| 复盘 | 更新容量规划 | 避免同类问题再次发生 |
所以呢朋友们,吃一堑长一智:永远不要只依赖单一维度的日志清理策略,时间+大小的双保险是生产环境的标配。