Logstash WebHDFS 异常导致历史日志补读与 OOM

Logstash WebHDFS 异常导致历史日志补读与 OOM

脱敏说明

本文档已对内部域名、主机名、Kafka topic、业务日志文件名和真实目录路径做脱敏处理,保留排查方法、故障链路和处置思路。

故障背景

业务发现埋点数据量从 2026-05-30 开始明显变少,但 Nginx 原始日志量没有明显下降。现场有 5 台 Logstash 节点共同采集 Nginx 埋点日志并写入 Kafka,同时也通过 webhdfs output 写入 HDFS。

本次重点排查的是 <logstash-instance> 采集进程:

bash 复制代码
--path.data=/opt/logstash/data/<logstash-instance>
--http.port=9601

故障现象

  • 5 台节点上的 Logstash 进程和端口都存在,但 Kafka 侧埋点数据量变少。
  • logstash-plain.log 中大量出现 WebHDFS failover 日志:
text 复制代码
Failing over from <hdfs-namenode-1>:9870 to <hdfs-namenode-2>:9870.
Failing over from <hdfs-namenode-2>:9870 to <hdfs-namenode-1>:9870.
  • file input 出现日志轮转告警:
text 复制代码
Rotation In Progress - inode change detected and original content is not fully read
  • 重启 <logstash-node-1><logstash-instance> 进程后,Logstash 会继续追读 2026-05-302026-05-31 的历史大文件,并快速向 Kafka/HDFS 输出大量数据。
  • 重启后进程运行一段时间再次挂掉,日志中出现:
text 复制代码
KafkaProducer.send() failed: org.apache.kafka.common.errors.TimeoutException: Failed to update metadata after 60000 ms
java.lang.OutOfMemoryError: Java heap space

后续 WebHDFS 侧还出现 HDFS 文件 lease 冲突:

text 复制代码
AlreadyBeingCreatedException
because this file lease is currently owned by DFSClient...
Maybe you should increase retry_interval or reduce number of workers. Retrying...

排查过程

1. 确认是否查错 Logstash 进程

最初查看的是 9600 端口,发现 pipeline 只有少量事件:

text 复制代码
events.in=5
events.out=5

后确认生产主采集进程实际是 9601

bash 复制代码
curl -s http://127.0.0.1:9601/_node/stats/pipelines?pretty

9601 的统计显示 <logstash-node-1> 的主进程仍在处理大量事件,说明不能只看进程是否存在,也不能看错端口。

2. 用 Logstash API 判断 input/output 是否还在增长

9601 连续采样后发现:

text 复制代码
events.in       <value_before> -> <value_after>
events.filtered <value_before> -> <value_after>
events.out      <value_before> -> <value_after>

说明主进程仍在读入、过滤、输出,单看这段时间并不是完全停采。

3. 检查 file input 与 sincedb

针对 2026-05-302026-06-01 的大文件,使用 inode 和 sincedb offset 对账:

bash 复制代码
for d in 2026-05-30 2026-05-31 2026-06-01; do
  for f in \
    /data/nginx/log/<app-v2-access-log>-*"$d"* \
    /data/nginx/log/<app-v3-access-log>*"$d"*
  do
    [ -f "$f" ] || continue
    inode=$(ls -li "$f" | awk '{print $1}')
    size=$(wc -c < "$f" | tr -d ' ')
    recs=$(grep -R "^${inode} " /opt/logstash/data/plugins/inputs/file/sincedb-* 2>/dev/null)
    if [ -z "$recs" ]; then
      echo "NOT_FOUND date=${d} inode=${inode} size=${size} file=${f}"
    else
      echo "$recs" | while read -r rec; do
        offset=$(echo "$rec" | awk '{print $4}')
        sincedb_file=$(echo "$rec" | awk -F: '{print $1}')
        if [ "$offset" != "$size" ]; then
          echo "ABNORMAL date=${d} inode=${inode} size=${size} offset=${offset} file=${f} sincedb=${sincedb_file}"
        fi
      done
    fi
  done
done

排查发现 <logstash-node-1> 上存在多个历史文件 offset 小于文件大小,例如:

text 复制代码
<app-v2-access-log>-2026-05-30
<app-v3-access-log>-2026-05-30
<app-v2-access-log>-2026-05-31
<app-v3-access-log>-2026-05-31

这说明部分历史文件在该节点上没有被完整追完。

4. 确认重启后为什么再次挂掉

重启后,Logstash 会按照 sincedb 中未完成的 offset 继续追历史大文件。由于文件体积很大,短时间内产生大量补写流量。

同时日志显示 Kafka metadata 获取失败和 WebHDFS lease 冲突:

text 复制代码
Failed to update metadata after 60000 ms
AlreadyBeingCreatedException
java.lang.OutOfMemoryError: Java heap space

结合当前队列类型为 memory,可以判断为 output 写不出去或变慢时,事件堆积在 JVM heap 中,最终 OOM。

根因分析

本次问题不是单纯的 Kafka 丢数据,而是多个因素叠加:

  1. Logstash file input 使用日期通配符采集历史文件,例如:
ruby 复制代码
path => "/data/nginx/log/<app-v2-custom-event-log>-*"
start_position => "beginning"

这会让 Logstash 同时关注历史日期文件。一旦历史文件发生轮转、移动、覆盖或 inode 变化,就可能出现 Rotation In Progress

  1. WebHDFS output 持续 failover 或 lease 冲突,导致 output 阻塞或反复 retry。

  2. Kafka 曾出现 metadata 更新超时,导致 Kafka output 也存在阻塞风险。

  3. 生产进程使用 memory queue,output 写不出去时,事件会堆积到 JVM heap。

  4. 重启服务后,Logstash 根据 sincedb 继续补读 2026-05-302026-05-31 的历史大文件,瞬时吞吐过大,最终触发 OOM。

故障链路可以概括为:

text 复制代码
WebHDFS/Kafka 输出异常
  -> Logstash output 阻塞或变慢
  -> file input 追不上历史大文件
  -> 日期文件轮转/inode 变化
  -> sincedb offset 未追到文件末尾
  -> 重启后继续补读大量历史数据
  -> WebHDFS lease 冲突 + Kafka/HDFS 写入压力过大
  -> memory queue 堆积
  -> Java heap OOM

处置过程

1. 先修复 Kafka 节点配置

排查过程中发现 Kafka producer 曾报 metadata 获取失败。先修正 Kafka broker 节点配置,避免 Kafka output 继续阻塞。

2. 降低 Logstash 启动压力

尝试通过启动参数降低并发和批量大小:

bash 复制代码
export LS_JAVA_OPTS="-Xms8g -Xmx8g"

nohup /opt/logstash/bin/logstash \
  -f /opt/logstash/conf.d/<logstash-instance>/ \
  --path.data=/opt/logstash/data/<logstash-instance> \
  --http.port=9601 \
  -w 1 \
  -b 10 \
  -l /opt/logstash/logs/<logstash-instance> &

但由于 WebHDFS 仍会 append HDFS 文件并触发 lease 冲突,单纯加大内存或降低 worker 只能缓解,无法彻底解决。

3. 最终止血方案:取消所有 WebHDFS output

最终采用的恢复方式是:从 Logstash 配置中删除所有 webhdfs { ... } output 块,仅保留 Kafka output

处理后每个分支只保留类似:

ruby 复制代码
elseif [type] == "<event-type-request>" {
    kafka {
        bootstrap_servers => "<kafka-broker-1>:9092,<kafka-broker-2>:9092,<kafka-broker-3>:9092"
        topic_id => "<kafka-topic-request>"
        acks => "1"
        compression_type => snappy
        linger_ms => 5
        batch_size => 2000
        max_request_size => 4242880
        codec => plain { format => "%{message}" }
    }
}

对于原本只有 WebHDFS、没有 Kafka 的空分支,直接删除该分支,避免 Logstash 配置语法残留。

4. 配置校验后再启动

修改后先执行配置校验:

bash 复制代码
/opt/logstash/bin/logstash \
  -f /opt/logstash/conf.d/<logstash-instance>/ \
  --path.data=/opt/logstash/data/<logstash-instance> \
  --config.test_and_exit

确认配置通过后,再启动 <logstash-instance> 进程。

恢复验证

恢复后重点观察:

bash 复制代码
curl -s http://127.0.0.1:9601/_node/stats/pipelines?pretty

关注:

text 复制代码
events.in
events.filtered
events.out
kafka output events.in/out

同时观察日志中是否还出现:

text 复制代码
OutOfMemoryError
Failed to update metadata
AlreadyBeingCreatedException
webhdfs write caused an exception

取消 WebHDFS output 后,Logstash 不再和 HDFS 文件 lease 冲突,Kafka 链路恢复为主要输出路径。

后续优化建议

1. Kafka 与 HDFS 解耦

不要让同一个 Logstash pipeline 同时承担实时 Kafka 和 HDFS 落盘。建议链路改为:

text 复制代码
Nginx 日志 -> Logstash -> Kafka
Kafka -> 独立消费任务 -> HDFS

这样 HDFS 抖动不会影响实时 Kafka 埋点。

2. 历史补数单独执行

历史文件不要交给生产 Logstash 进程直接追。建议单独启动 backfill 进程:

text 复制代码
独立配置
独立 path.data
独立 sincedb_path
只读明确日期和明确文件
必要时写临时 topic

3. 调整日志采集路径

实时采集尽量只 tail 当前正在写入的文件,避免使用过宽的日期通配符长期匹配历史文件。

4. 增加节点级可观测性

Kafka 消息目前只写 %{message},无法区分是哪台 Logstash 写入。后续可考虑:

  • 通过 Logstash API 采集每台节点的 events.in/out 指标;
  • 或在 debug topic 中增加 %{host}%{type} 标记;
  • 不建议直接改生产 topic 消息格式,除非确认下游能兼容新增字段。

5. 启用 persistent queue

如果仍需承受 output 抖动,可以评估启用持久化队列:

bash 复制代码
--queue.type persisted
--path.queue=/opt/logstash/data/<logstash-instance>/queue
--queue.max_bytes=8gb

但这只能缓冲压力,不能替代 Kafka/HDFS 解耦。

经验总结

  • Logstash 进程和端口正常,不代表采集链路正常,需要看 events.in/out 增量。
  • 多个 output 在同一个 pipeline 中没有事务一致性,任意一个 output 阻塞都可能拖慢整体。
  • start_position => beginning 不表示每次重启都从头读,它只对 sincedb 未记录的新 inode 生效。
  • 重启 Logstash 会继续根据 sincedb 追未读完的历史文件,历史数据量大时可能形成补写风暴。
  • 事故止血时应优先保障 Kafka 实时链路,HDFS 落盘应拆分或延后补偿。
相关推荐
WarPigs2 小时前
C# EntityFramework笔记
数据库·c#
thisiszdy2 小时前
<C++&C#> lambda表达式
java·c++·c#
叶帆3 小时前
【YFIOs】用C#开发硬件之串口通信
开发语言·c#
xiaoshuaishuai83 小时前
C# Avaloniaui ListBox样式及用法
开发语言·c#
真实的菜3 小时前
Java 微服务优雅停机:从踩坑到最佳实践
java·微服务·linq
rockey62715 小时前
AScript之事件处理脚本
c#·.net·script·动态脚本
蛋蛋的学习记录19 小时前
C#窗体应用中使用EasyModbusCore通讯
服务器·c#·tcp
ShyanZh19 小时前
Markitdown 多格式文档智能解析实战指南
开发语言·c#
周杰伦fans1 天前
C# CAD 二次开发:无需启动 AutoCAD 实现 DWG 转 DXF 的完整技术指南
开发语言·c#