TL;DR
- 场景:Flume TAILDIR 同时采集启动日志与事件日志,要求按日志内时间分区、按来源分目录落 HDFS。
- 结论:用自定义 Interceptor 抽取 logtime,并用 filegroups headers 或拦截器补齐 logtype,HDFS path 通过 %{header} 动态路由。
- 产出:一套可复用配置模板 + 常见坑位速查(类加载、占位符、滚动策略、权限与 positionFile)。
自定义拦截器
(续接上节,上节已经到了打包的部分)
上传结果
将刚才的打包上传到这个目录下: 我拷贝的是带依赖的:“flume-test-1.0-SNAPSHOT-jar-with-dependencies.jar”
/opt/servers/apache-flume-1.9.0-bin/lib/
测试效果
我们创建刚才说的conf文件:
vim /opt/wzk/flume-conf/flumetest1.conf
编写的内容如下图所示:
启动进行测试:
flume-ng agent --conf-file /opt/wzk/flume-conf/flumetest1.conf -name a1 -Dflume.roog.logger=INFO,console
启动结果如下图所示:
我们启动 telnet 来传入数据:
telnet h122.wzk.icu 9999
启动之后输入数据:
2020-07-30 14:18:47.339 [main] INFO com.ecommerce.AppStart - {"app_active":{"name":"app_active","json":{"entry":"1","action":"1","error_code":"0"},"time":1596111888529},"attr":{"area":"泰安","uid":"2F10092A9","app_v":"1.1.13","event_type":"common","device_id":"1FB872-9A1009","os_type":"4.7.3","channel":"DK","language":"chinese","brand":"iphone-9"}}
此时控制台的数据内容部分为:
4/08/27 15:31:31 INFO source.NetcatSource: Source starting
24/08/27 15:31:31 INFO source.NetcatSource: Created serverSocket:sun.nio.ch.ServerSocketChannelImpl[/172.16.1.130:9999]
24/08/27 15:32:09 INFO sink.LoggerSink: Event: { headers:{logtime=2020-07-30} body: 32 30 32 30 2D 30 37 2D 33 30 20 31 34 3A 31 38 2020-07-30 14:18 }
对应的截图如下图所示:
采集启动日志(使用自定义拦截器)
配置文件
新建一个配置文件:
vim /opt flume-log2hdfs2.conf
写入的内容如下所示:
a1.sources = r1
a1.sinks = k1
a1.channels = c1
# taildir source
a1.sources.r1.type = TAILDIR
a1.sources.r1.positionFile = /opt/wzk/conf/startlog_position.json
a1.sources.r1.filegroups = f1
a1.sources.r1.filegroups.f1 = /opt/wzk/logs/start/.*log
a1.sources.r1.interceptors = i1
a1.sources.r1.interceptors.i1.type = icu.wzk.CustomerInterceptor$Builder
# memorychannel
a1.channels.c1.type = memory
a1.channels.c1.capacity = 100000
a1.channels.c1.transactionCapacity = 2000
# hdfs sink
a1.sinks.k1.type = hdfs
a1.sinks.k1.hdfs.path = /user/data/logs/start/dt=%{logtime}/
a1.sinks.k1.hdfs.filePrefix = startlog.
a1.sinks.k1.hdfs.fileType = DataStream
# 配置文件滚动方式(文件大小32M)
a1.sinks.k1.hdfs.rollSize = 33554432
a1.sinks.k1.hdfs.rollCount = 0
a1.sinks.k1.hdfs.rollInterval = 0
a1.sinks.k1.hdfs.idleTimeout = 0
a1.sinks.k1.hdfs.minBlockReplicas = 1
# 向hdfs上刷新的event的个数
a1.sinks.k1.hdfs.batchSize = 1000
# 使用本地时间
# a1.sinks.k1.hdfs.useLocalTimeStamp = true
# Bind the source and sink to the channel
a1.sources.r1.channels = c1
a1.sinks.k1.channel = c1
内容的截图如下所示:
修改的内容如下:
- 给source增加自定义拦截器
- 去掉时间戳 a1.sinks.k1.hdfs.useLocalTimeStamp = true
- 根据header中的logtime写文件
测试运行
flume-ng agent --conf-file /opt/wzk/flume-conf/flume-log2hdfs2.conf -name a1 -Dflume.roog.logger=INFO,console
拷贝日志
修改日志的内容:
vim /opt/wzk/logs/start/test.log
继续写入如下的内容:
2020-07-30 14:18:47.339 [main] INFO com.ecommerce.AppStart - {"app_active":{"name":"app_active","json":{"entry":"1","action":"1","error_code":"0"},"time":1596111888529},"attr":{"area":"泰安","uid":"2F10092A9","app_v":"1.1.13","event_type":"common","device_id":"1FB872-9A1009","os_type":"4.7.3","channel":"DK","language":"chinese","brand":"iphone-9"}}
写入内容如下图所示:
测试效果
可以看到HDFS上,已经有了读出日志中的时间的内容:
采集启动日志和事件日志
本系统中要采集两种日志:
- 启动日志
- 事件日志
不同的日志放置在不同的目录下,要想一次拿到全部日志需要监控多个目录。
总体思路
- taildir 监控多个目录
- 修改自定义拦截器,不同来源的数据加上不同标志
- HDFS、Sink 根据标志写文件
Agent 介绍
Flume 是一个分布式、高可靠、可用来收集、聚合和传输大量日志数据的系统。在 Flume 的体系结构中,Agent 是一个关键的组件。每个 Agent 是一个独立的 JVM 进程,负责从数据源获取数据并将其传递到下游(如文件系统、数据库、或者另一个 Agent)
Agent 的核心组成部分
Flume Agent 的架构是高度模块化的,它由以下三个核心组件构成:
Source (源)
Source 是数据流的起点,负责接收外部数据。它支持多种数据传输协议和格式,能够从日志文件、网络端口、消息队列等数据源中接收事件。
支持的 Source 类型:
- Avro Source:接收来自其他 Flume Agent 的 Avro 格式数据。
- Syslog Source:处理 Syslog 消息。
- Exec Source:从本地命令或脚本的输出中读取数据。
- HTTP Source:通过 HTTP 接口接收数据。
- Spooling Directory Source:监控特定目录中的文件并读取内容。
Channel (通道)
Channel 是 Agent 的数据缓冲区域,用于在 Source 和 Sink 之间暂存事件。Channel 的设计保证了在数据流动中断(如网络故障)时,数据不会丢失。
常见的 Channel 类型:
- Memory Channel:将事件存储在内存中,速度快,但可能会丢失数据(如果 Agent 崩溃)。
- File Channel:将事件存储在磁盘文件中,提供高可靠性但性能较低。
- Kafka Channel:使用 Kafka 作为中转通道,适合分布式场景。
Sink (接收器)
Sink 是数据流的终点,负责将事件传递到下游存储或处理系统。
支持的 Sink 类型:
- HDFS Sink:将事件写入 Hadoop HDFS。
- Kafka Sink:将事件发送到 Kafka。
- Elasticsearch Sink:将事件写入 Elasticsearch。
- Logger Sink:将事件输出到日志。
- Avro Sink:将事件传递到另一个 Flume Agent 的 Avro Source。
Agent配置
vim /opt/wzk/flume-conf/flume-log2hdfs3.conf
写入的内容如下图所示:
a1.sources = r1
a1.sinks = k1
a1.channels = c1
# taildir source
a1.sources.r1.type = TAILDIR
a1.sources.r1.positionFile = /opt/wzk/conf/startlog_position.json
a1.sources.r1.filegroups = f1 f2
a1.sources.r1.filegroups.f1 = /opt/wzk/logs/start/.*log
a1.sources.r1.headers.f1.logtype = start
a1.sources.r1.filegroups.f2 = /opt/wzk/logs/event/.*log
a1.sources.r1.headers.f2.logtype = event
# 自定义拦截器
a1.sources.r1.interceptors = i1
a1.sources.r1.interceptors.i1.type = icu.wzk.LogTypeInterceptor$Builder
# memorychannel
a1.channels.c1.type = memory
a1.channels.c1.capacity = 100000
a1.channels.c1.transactionCapacity = 2000
# hdfs sink
a1.sinks.k1.type = hdfs
a1.sinks.k1.hdfs.path = /user/data/logs/%{logtype}/dt=%
{logtime}/
a1.sinks.k1.hdfs.filePrefix = startlog.
a1.sinks.k1.hdfs.fileType = DataStream
# 配置文件滚动方式(文件大小32M)
a1.sinks.k1.hdfs.rollSize = 33554432
a1.sinks.k1.hdfs.rollCount = 0
a1.sinks.k1.hdfs.rollInterval = 0
a1.sinks.k1.hdfs.idleTimeout = 0
a1.sinks.k1.hdfs.minBlockReplicas = 1
# 向hdfs上刷新的event的个数
a1.sinks.k1.hdfs.batchSize = 1000
# Bind the source and sink to the channel
a1.sources.r1.channels = c1
a1.sinks.k1.channel = c1
- filegroups:指定filegroups,可以有多个,以空格分割(taildir source可以同时监控多个目录中的文件)
- headers.filegroups.headerKey
给Event增加header Key,不同的filegroup,可配置不同的value。
错误速查
| 症状 | 根因定位 | 修复 |
|---|---|---|
| Flume 启动直接报 “Unknown/Unrecognized option” 或参数不生效 | agent 启动参数写错(如 -name / flume.roog.logger) | 看 flume-ng agent 输出的 usage/Unknown option;用 --name a1;logger 用 -Dflume.root.logger=INFO,console |
| 启动报 ClassNotFound:自定义拦截器找不到 | jar 未放到 Flume classpath 或未包含依赖 | grep -i classnotfound flume.log;确认 jar 是否在 $FLUME_HOME/lib;放入 /opt/servers/apache-flume-1.9.0-bin/lib/;优先使用 jar-with-dependencies |
Interceptor 生效但 %{logtime} 为空,HDFS 路径分区异常 | 未正确写 header(解析失败/字段缺失/正则不匹配) | 在 Sink 前加 LoggerSink 或 Debug 日志打印 headers;在拦截器里对缺失时间设置兜底;严格校验日志格式与时间提取规则 |
| HDFS 上迟迟不生成文件,或生成很慢 | rollInterval/idleTimeout/rollCount 配置导致“只按大小滚动”,低流量不触发 | 看 HDFS 目标目录是否出现 .tmp 或长时间无文件;看 sink 指标;低流量场景设置 hdfs.rollInterval=60 或 hdfs.idleTimeout=60;保留 rollSize 作为上限 |
| HDFS 写入报 Permission denied / lease 错误 | HDFS 目录权限/用户身份不匹配;Kerberos/代理用户未配置 | 看 Flume 日志中 org.apache.hadoop.security / Permission denied;给 Flume 运行用户授权目标目录;按集群启用 hdfs.kerberosPrincipal 等安全参数(如有) |
| TAILDIR 不采集或只采到一次,重启后从头/不续采 | positionFile 路径不可写/格式损坏/多 agent 共用同一文件 | 检查 positionFile 是否更新;看是否有 JSON 解析报错;确保目录可写;每个 agent 独立 positionFile;损坏时备份后重建 |
| 多目录采集时 logtype 丢失或混写 | headers 配置写错(headers.f1.logtype / filegroups 名不一致)或拦截器覆盖 | 在 LoggerSink 输出 headers 对比 start/event;保证 filegroups = f1 f2 与 headers.f1.* / headers.f2.* 完全一致;明确拦截器是否改写 logtype |
其他系列
🚀 AI篇持续更新中(长期更新)
AI炼丹日志-29 - 字节跳动 DeerFlow 深度研究框斜体样式架 私有部署 测试上手 架构研究,持续打造实用AI工具指南! AI研究-132 Java 生态前沿 2025:Spring、Quarkus、GraalVM、CRaC 与云原生落地
💻 Java篇持续更新中(长期更新)
Java-218 RocketMQ Java API 实战:同步/异步 Producer 与 Pull/Push Consumer MyBatis 已完结,Spring 已完结,Nginx已完结,Tomcat已完结,分布式服务已完结,Dubbo已完结,MySQL已完结,MongoDB已完结,Neo4j已完结,FastDFS 已完结,OSS已完结,GuavaCache已完结,EVCache已完结,RabbitMQ已完结,RocketMQ正在更新... 深入浅出助你打牢基础!
📊 大数据板块已完成多项干货更新(300篇):
包括 Hadoop、Hive、Kafka、Flink、ClickHouse、Elasticsearch 等二十余项核心组件,覆盖离线+实时数仓全栈! 大数据-278 Spark MLib - 基础介绍 机器学习算法 梯度提升树 GBDT案例 详解