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-30、2026-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-30 到 2026-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 丢数据,而是多个因素叠加:
- Logstash
fileinput 使用日期通配符采集历史文件,例如:
ruby
path => "/data/nginx/log/<app-v2-custom-event-log>-*"
start_position => "beginning"
这会让 Logstash 同时关注历史日期文件。一旦历史文件发生轮转、移动、覆盖或 inode 变化,就可能出现 Rotation In Progress。
-
WebHDFS output 持续 failover 或 lease 冲突,导致 output 阻塞或反复 retry。
-
Kafka 曾出现 metadata 更新超时,导致 Kafka output 也存在阻塞风险。
-
生产进程使用 memory queue,output 写不出去时,事件会堆积到 JVM heap。
-
重启服务后,Logstash 根据 sincedb 继续补读
2026-05-30、2026-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 落盘应拆分或延后补偿。