ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Kafka磁盘100%故障全解析:从应急处理到容量规划

Kafka磁盘100%故障全解析:从应急处理到容量规划 老实说Kafka的broker进程被磁盘使用率100%拖垮这件事在集群规模稍微上来一点之后基本就是“迟早要遇到”的问题。前两个月我这边刚处理完一单起因就是某个节点写日志的时候直接报No space left on device接着broker日志疯狂刷错误分区副本开始掉线消费端Lag肉眼可见地往上飙。这类故障表面上看着像是“磁盘满了”这种一句话就能解释的问题但真正复盘之后会发现背后牵扯到监控水位、保留策略、节点下线流程、日志段机制甚至Windows环境下防病毒扫描和文件句柄这一类边边角角的东西。这篇文章就把这次Kafka节点磁盘写满导致broker实例故障的完整排查过程、应急处理SOP、根因分析以及后续治理方案拆开讲清楚覆盖面从Kafka内部存储机制到Prometheus告警配置、从临时扩容到容量预估公式都会涉及适合正在维护Kafka集群的运维和开发同学参考也适合准备Kafka面试复盘的朋友当作一个完整case来理解。1. 故障模式与影响分析磁盘写满后Kafka内部到底发生了什么1.1 磁盘使用率100%时broker的真实表现Kafka的存储模型跟传统数据库差别很大它本质上就是靠顺序写日志文件来扛高吞吐。每个主题分区对应一个物理目录目录里面滚动生成日志段log segment每个段包含.log数据文件、.index偏移索引文件、.timeindex时间索引文件加上.snapshot这类辅助文件。写入请求先落PageCache再异步刷盘消费者按offset到对应segment里捞数据。这套设计在磁盘健康的时候很完美但一旦磁盘使用率到100%整个写入链路会瞬间停摆。我这次遇到故障时监视图上其实已经有预告磁盘使用率在前一天晚上从70%一路爬到100%过程大概持续了六七个小时但当时告警阈值设的是90%转紧急90%到100%之间没人看等于告警响了也白响。节点磁盘彻底写满之后/var/kafka-logs目录再也分配不出新的日志段文件所有新进来的消息全部卡在写入路径上。注意这里有个很容易被忽略的点broker进程本身不一定马上被杀掉它还在运行但所有Produce请求开始超时日志里反复出现Error writing to disk、java.io.IOException: No space left on device这类内容。更麻烦的是磁盘写满不只是影响本节点的写入还会让这个broker上的所有副本同步线程停摆。因为副本拉取的数据也要写盘磁盘没有空间follower副本的日志落后越来越大ISRIn-Sync Replicas列表开始缩水。如果这个broker恰好还是某个分区的Leader那leader副本也写不进去了消费者拉取不到新数据整个分区的可用性直接崩掉。1.2 从一张监控图拆解故障影响的传导链路复盘时我把故障前后6小时的监控曲线拉出来对比整个传导链路可以分成几个阶段T-6h到T-2h磁盘使用率从70%升到90%broker各项指标看起来正常只有磁盘曲线在爬坡。T-2h到T-30min磁盘使用率突破95%分区写入开始偶发延迟ISR出现收缩但客户端重试机制掩盖了部分故障业务侧只感觉到“偶尔卡一下”。T-30min到T0磁盘彻底100%Leader副本写入失败分区进入无Leader或Leader频繁切换状态Producer报NotLeaderForPartitionException或超时Consumer Lag快速拉高。T0之后如果超过replica.lag.time.max.ms默认30秒Controller会把掉队副本移出ISR如果该broker上所有副本都失效Controller会尝试把Leader迁移到其他节点但这时又可能因为新旧Leader数据差距过大出现截断日志truncate的情况数据一致性问题跟着冒出来。这里要特别留神的一点是不要以为“磁盘满了broker会直接挂掉然后一切重来”真实情况是进程半死不活集群整体还在对外提供服务但局部分区已经不可用了。这种状态比直接宕机更坑因为监控上显示broker还活着实际业务已经在报错了。1.3 业务影响与体验表现从我这次处理的现场来看磁盘写满造成的业务影响主要有三类第一类是消息生产阻塞。Kafka生产者端在max.block.ms内不断重试发送超时后抛出异常消息堆积在生产者内存里。如果生产端用的是同步发送模式业务写入接口直接报错。第二类是消费端数据延迟。消费者读不到新数据实时计算链路被卡住下游数据面的报表、特征计算全部延迟。第三类是消费位点偏移问题。分区Leader切换或截断日志后部分消费者可能出现OutOfRangeException需要人工重置offset才能恢复。从影响范围来看这个故障绝对不只是“坏了一台机器”这么简单它是一个能顺着Kafka的数据链路扩散到全链路的故障源。所以处理的时候第一优先级永远是“把磁盘空间腾出来让写入恢复”而不是去排查业务代码。2. 根因复盘Kafka节点磁盘被写满的典型元凶2.1 流量增长与保留策略不匹配算一笔账就明白了磁盘被写满最常见也最冤的一种原因就是业务流量一直在涨但Kafka的日志保留策略和磁盘容量压根没跟着调过。我这边这次事故其实就有这个因素。当时集群里有个核心业务主题写流量大概在8MB/s左右副本因子是3也就是说每秒实际占用磁盘约24MB。日志保留时间设置的是3天那我简单估算一下单分区每秒写入 8MB / 分区数。 假设这个主题拆了16个分区每分区每秒约500KB写入3个副本后约1.5MB/s。3天总数据量大约为 1.5MB/s × 86400秒/天 × 3天 × 16分区 ≈ 6.2TB。如果这个节点上还同时部署了其他主题的副本磁盘总量只有3TB那半夜磁盘被写满几乎是必然的。这类问题靠“事后扩容”只能救急治本的方式是给每个主题做流量与容量的预估然后反推保留时间、副本因子和存储盘大小。后文我会专门给一套估算公式。2.2 日志段与索引文件没有及时释放Kafka的日志清理不是实时的它靠后台线程周期触发主要参数是log.retention.check.interval.ms默认300000毫秒即5分钟。很多运维对“删除过期日志”有误解以为消息一到保留时间就会被删掉真实情况是每个日志段segment只有在完全过期之后才会被标记删除而一个segment是否“完全过期”要看它最后一条消息的时间戳。举个例子log.segment.bytes如果设成1GB一个分区在低流量时段可能很久才滚动一个新segment那最早的那个segment里的最后一条消息可能已经超过了保留时间但因为整个文件还没有滚动清理线程会一直等它变成“老段”才会动它。在这个等待窗口期内磁盘上堆着大量看起来应该过期但实际还没被删的文件。如果业务流量波动很大这种“延迟清理”效应特别明显。另外还有一类是segment内消息被标记为墓碑tombstone但没触发压缩compaction主题设置了cleanup.policycompact却没人消费墓碑消息导致大量占空间的墓碑残留。这种问题在日志主题如__consumer_offsets上尤其常见。2.3 主题级配置覆盖了Broker默认值这个坑我踩过不止一次。很多团队在创建主题的时候喜欢用kafka-topics.sh --config带上自定义的retention.ms和segment.bytes但改配置只对当时创建的主题生效topic级配置优先级高于broker级配置。结果是某个主题保留了超长时间的数据broker上其他主题在按默认策略滚动清理就这个主题无限增长把整块盘拖垮。我这次排查时发现一个消费位点记录主题__consumer_offsets的segment异常大就是这个原因。它其实是Kafka内部管理元数据的紧凑型主题但当时集群版本里offsets.retention.minutes和log.cleanup.policycompact的交互没吃透导致膨胀到几百GB。2.4 节点下线或扩容操作留下的孤儿副本运维操作也会埋雷。比如集群要下线一台旧broker正确的做法是先触发分区副本迁移kafka-reassign-partitions.sh等迁移完成再停进程切流量。但如果操作不规范比如直接在控制台上kill了进程或者迁移刚启动一半就执行了下线那这台机器上的分区副本会变成“孤儿副本”控制器会尝试把这些副本在其他节点重建但原来机器上的数据文件并不会自动清除。下次把这台机器重新加入集群之前残留的日志段文件会继续占用磁盘而且由于分区副本已经分配出去了这些残留文件严格来说已经没有任何broker在管理但物理文件依然在磁盘上躺着。我看过一台机器上这种孤儿副本占了1.2TB没人知道它们的存在因为它们既不显示在主题分区列表里也不参与任何副本同步。2.5 Windows环境下磁盘使用率100%的另类诱因听到“Kafka磁盘写满”大部分Linux用户会直接想到日志和数据文件但如果你的broker部署在Windows Server环境本次项目正好有热词提到win2019磁盘使用率那还需要排查另外两个别的原因。一个是Windows的SearchIndexer或Windows Defender实时扫描。Kafka写入大量小文件防病毒软件会实时监控文件变更拖慢写入速度严重的时候会让磁盘IO曲线长期满负荷磁盘使用率看着像100%但其实还有剩余空间是IO被吃满了。另一个是Windows系统开启的系统还原点会对NTFS卷自动创建还原点Kafka的数据目录一旦出现较大规模文件新增卷影复制服务会触发全盘快照这种情况下磁盘使用率可能一下就冲到100%。我朋友那边就遇到过这种案例单看Kafka数据文件大小才占磁盘40%但Windows的System Volume Information目录占了另外50%Kafka一扩容写入就把物理空间挤没了。这种故障如果用Linux思维去处理只会把Kafka配置反复调优基本没有效果。3. 应急处理SOP磁盘写满后先把服务拉起来再谈治理3.1 第一步扩容磁盘或快速清理可删除的日志文件故障发生时第一件事不是查代码也不是开会而是抢时间把磁盘空间腾出来。我个人的优先级排序是这样的如果能联系到云平台或虚拟化平台第一选择是直接给故障节点挂一块新盘扩到足够大然后重启broker。扩容之后Kafka自己会重新分配日志目录如果broker配置了多个log.dirs数据会自动落到新盘上恢复最快。如果扩容这条路走不通那就需要清理旧日志。清理之前先判断哪些可以删检查主题的retention.ms和retention.bytes配置如果业务上确认可以缩短保留时间直接改配置让后台线程去删除过期segment。查看各主题目录的mtime找到__consumer_offsets和其他内部主题是否有异常膨胀。如果是离线节点上的孤儿副本先确认节点已经不在任何分区副本列表里再删除对应目录。清理日志用一条命令就能完成# 查看当前磁盘占用占比最高的目录 du -sh /var/kafka-logs/* | sort -hr | head -30 # 确认主题保留策略 kafka-configs.sh --bootstrap-server localhost:9092 --entity-type topics --entity-name your_topic --describe # 临时把保留时间缩短到1小时触发立即清理 kafka-configs.sh --bootstrap-server localhost:9092 --entity-type topics --entity-name your_topic \ --alter --add-config retention.ms3600000改完配置后正常情况下5分钟内log.retention.check.interval.ms默认为5分钟后台线程就会删除过期段磁盘空间会慢慢释放。提示清理日志文件之前务必要确认主题的消费位点安全否则把消费者还没消费完的数据删了会造成永久性数据丢失。在删除动作前至少先检查消费组Lag确保current-offset已经远超将要删除的日志段末尾offset。3.2 第二步动态调整保留策略让broker自己打扫卫生如果磁盘上能删的旧日志不多或者不想承担直接删文件的风险那就走Kafka自己的清理机制。操作分两级Topic级调整适用单个主题异常膨胀的情况用上面提到的kafka-configs.sh动态修改retention.ms。这个操作是热生效的不需要重启broker。Broker级调整如果整个节点上所有主题都需要缩短保留时间可以改server.properties里的log.retention.hours如果设了log.retention.ms前者会被忽略这个优先级关系要记清楚。全局强制清理直接执行kafka-log-dirs.sh看哪个目录异常或者用kafka-reassign-partitions.sh把一部分分区迁移到其他节点从根上减负。有一个小技巧有时候只把retention.ms设小还不够因为日志段文件太大清理线程要等整个segment过期才动手。这时候可以把log.segment.bytes也调小比如从1GB改到256MB这样旧的超大segment会被尽快切分成多个小段小段迅速过期后就能被删掉空间释放速度会快很多。等磁盘恢复健康、流量也平稳之后记得再把配置改回原值。3.3 第三步确认ISR恢复与分区Leader迁移磁盘空间腾出来后Kafka的副本同步线程会自动恢复但要确认细节先看分区副本是否恢复同步# 查看主题分区状态 kafka-topics.sh --bootstrap-server localhost:9092 --describe --topic your_topic输出结果关注Isr列正常情况下Leader和Isr应该包含故障节点的broker id。如果ISR仍然只有两个副本说明故障节点的副本还在追数据给它一点时间补同步实时观察日志偏移差异避免过早投入生产流量。同时要查看集群是否有分区的Leader在故障期间被切走这些leader是否已经回迁。如果Controller没有自动让它们回迁可以考虑手动触发Leader重选举命令如下# 手动触发指定分区的leader选举 kafka-leader-election.sh --bootstrap-server localhost:9092 \ --topic your_topic --partition 0 --election-type PREFERRED到这里应急处理的核心环节就算走完了。最后把broker上的日志级别恢复成INFO观察15分钟确认没有No space left再次出现。注意完整的应急恢复流程里最重要的一条经验是——千万别一看到磁盘满了就kill -9 broker进程。Kafka在磁盘满的状态下强杀进程重启后需要重新加载所有日志段并执行日志恢复磁盘本来就已经满了恢复过程会因为写不了任何文件而陷入死循环最后可能连Controller的元数据都加载不了把局部故障升级成集群级故障。我见过有人这么干过后果是整个集群花了半天时间才从半瘫痪状态缓过来。4. 长期治理监控、告警与容量规划4.1 磁盘水位的三层监控模型应急处理只是治标真正要避免同类故障再次发生必须把监控和容量规划提到常态化。Kafka磁盘监控至少要做三层节点级监控直接采集每台broker物理磁盘的used%用Prometheus的node_filesystem_avail_byte或node_filesystem_size_bytes计算即可。这个指标最直观也最容易被忽视。主题级监控对每个主题按副本维度统计日志目录大小在Linux上可以用kafka_log_log_size这类指标JMX exporter自带在Windows下可以用脚本扫目录。这层监控的目的在于发现“某个主题独占磁盘”的异常增长模式比如compact主题膨胀。分区级监控统计单个分区的日志末端偏移和segment数量配合kafka.server:typeFetcherStats观测副本同步进度。我自己的监控体系用的是Prometheus node_exporter kafka_exporter Grafana再叠一层Alertmanager做告警。可视化工具方面Kafka ManagerYahoo开源版和Kafka Eagle都可以直接看到broker磁盘使用、分区分布、消费组Lag排查问题的时候很香但做告警还是建议用Prometheus那套。4.2 告警阈值与分级策略告警阈值不能只设一个“磁盘使用率90%”。从故障复盘的经验来看必须做四档分级等级阈值说明Info磁盘使用率70%正常水位仅记录趋势触发容量规划检查Warning磁盘使用率80%通知运维48小时内预估是否达到90%Critical磁盘使用率90%立刻告警评估是否需要迁移分区或扩容Emergency磁盘使用率95%触发紧急SOP优先腾空间为什么要这么做因为我们踩过“90%才开始告警”的亏。在Kafka集群里磁盘使用率到90%以后留给操作缓冲的时间窗口非常小。如果业务流量大最后10%可能一两个小时就被填满等你收到告警排查完故障已经发生了。所以我的习惯是80%就开始评估扩容或迁移90%直接进入操作状态。配置Alertmanager的PromQL示例groups: - name: kafka-disk.rules rules: - alert: KafkaDiskUsageHigh expr: | (1 - node_filesystem_avail_bytes{mountpoint/var/kafka-logs} / node_filesystem_size_bytes{mountpoint/var/kafka-logs}) * 100 80 for: 15m labels: severity: warning annotations: summary: Kafka broker磁盘使用率超过80%4.3 容量预估的计算方法容量规划其实就是一个公式反复用单节点磁盘需求 写入速率(byte/s) × 保留时长(秒) × 副本因子 / 节点数(可用写盘副本) × (1 压缩率余量) × (1 安全系数)举例集群总写入速率100MB/s保留3天259200秒副本因子3分布在10个broker上压缩余量按20%计算安全系数按30%计算总数据量 100MB/s × 259200s × 3 ≈ 77.76TB 落到每节点 ≈ 77.76TB / 10 ≈ 7.8TB 考虑压缩和buffer建议单节点磁盘需求 ≈ 7.8TB × 1.2 × 1.3 ≈ 12.2TB很多团队在实际规划中根本不按这个算拍脑袋买个2TB的盘装Kafka跑三个月就出问题。这里一定要按峰值流量算不能按平均值否则流量一冲上来就爆。4.4 节点下线与扩容的规范流程上面的故障如果追根溯源会发现相当一部分是运维操作流程不规范导致的。这里把Kafka集群的节点管理经验分享出来下线节点前用kafka-reassign-partitions.sh把该节点上所有分区副本迁移到其他节点确认所有分区ISR都完整再停broker进程。迁移期间要观察目的节点的磁盘水位防止“救火式迁移”把别的节点也拖满。扩容新节点后让分区副本自动平衡可以但最好是主动做一次reassign避免Controller自动平衡auto.leader.rebalance.enable在高峰期触发产生大量跨节点数据传输。定期执行孤儿副本清理。脚本扫描每个broker的日志目录与kafka-topics.sh --describe获取的副本目录列表比对将不在列表里的目录标记出来确认归属后再手动清理防患于未然。日志段参数统一规范化。创建主题时尽量使用broker默认的保留策略如果业务有特殊要求在主题配置里单独设置并写入配置管理记录避免出现“时间长了没人知道哪个主题改了配置”的黑洞。4.5 日志清理器自身的健康状况检查还要加一个容易被漏掉的监控点日志清理线程本身。Kafka的LogCleaner负责compact日志如果你的集群在跑compact策略主题必须监控kafka.server:typeLogCleanerManager相关指标。如果LogCleaner线程卡死或者频繁触发Failed to clean log日志那么即使磁盘使用率看起来在正常范围compact主题也会逐渐吞噬磁盘。这个线程一旦卡死最麻烦的是它不报错只是不清了磁盘占用缓慢爬坡一爬就是一个月等你发现时早就过了自己能处理的时间线。5. 常见问题排查速查表与排障心得5.1 磁盘相关故障定位速查表把这次排查过程中遇到的问题整理成一张速查表方便下次一出事能快速定位到方向现象可能原因排查手段磁盘使用率持续增长但保留策略正常日志段size过大延迟清理、孤儿副本残留du -sh逐目录扫、kafka-log-dirs.sh查副本归属删除日志后空间没有立即释放Windows下文件被占用、log.retention.check.interval.ms未触发重启broker释放句柄或等待周期触发Windows查句柄某个主题单独占掉一半磁盘topic级配置覆盖了broker默认、compact主题未触发压缩kafka-configs.sh --describe查看topic配置磁盘没满但IO持续100%Windows Defender扫描、查询侧磁盘缓存未命中排除数据目录、考虑SSD、观察iostat/wmicbroker日志报No space left但磁盘显示有空余inode耗尽或文件系统保留块设置df -i查inode、检查ext4 reserve分区副本一直在但ISR里没有日志回退/截断导致副本差异过大查看broker日志的Truncation记录必要时手动重新分配分区副本扩容新盘后分区没有落到新盘缺少log.dirs配置或未重启broker检查server.properties的log.dirs重启后用kafka-log-dirs.sh确认5.2 把“磁盘容量”当成一等指标来管排障方法论上我特别想强调一点Kafka集群的磁盘指标一定要当成一等公民来对待而不是“不宕机就不用管”。基于这次事故的经验我建议每个Kafka集群至少保证有连续两周的磁盘水位原始数据这样才能算出增长斜率做趋势预判。我自己的习惯是每周末把Grafana上所有broker的磁盘使用率曲线导出计算一下未来30天的预测水位。如果预测90%水位的日期在4周以内就提前扩容或者迁移。这套做法看起来笨但真的能救命因为大多数Kafka磁盘故障根本不是“突然发生的”而是“早就该处理但没人处理”的。5.3 最后分享一个实际操作中的体会其实这类故障处理多了之后我的一个直观感受是Kafka的broker故障里磁盘写满引发的宕机大概能排进前两名它的杀伤力不在于“机器挂了”而在于半死不活的状态特别容易误导排查方向。你盯着消费者、生产者、网络调半天结果发现只是磁盘少了几十GB。如果你现在正好在维护一套Kafka集群我强烈建议你把下面三件事这周就做了第一检查所有broker的磁盘使用率超过70%的记录到容量评估表里第二对所有主题跑一遍kafka-configs.sh --describe把不符合规范的topic级配置全部修正第三在Grafana上加一张磁盘水位趋势图配合80%告警阈值。这三件事做完大概率能避开下一次磁盘故障。还是那句话磁盘空间这种东西平时多看一眼比故障时熬一宿强太多了。
返回列表