0%

flume监控文件写入kafka

flume监控日志写到 Kafka,我为什么不用 log4j2 直连

日志采集有一个常见的争议:应用到底该直接把日志打到 Kafka,还是先写本地文件再让 Flume 采?

我之前也纠结过这个问题。后来线上出过一次事故——Kafka 集群抖动,log4j2 的 Kafka appender 直接把应用线程堵死了,服务全挂。从那以后我就认了:应用只管写文件,采集的事交给 Flume

这篇我就讲一下 Flume 监控文件写到 Kafka 的配置方法,以及几个我用下来觉得关键的参数。

为什么中间要加一层 Flume?

直接让应用通过 log4j2 写 Kafka,确实省事,配置几行就搞定。但带来的问题是:

  • Kafka 出问题会影响应用。Broker 不可用、网络抖动、topic 被删,应用的生产者线程会阻塞,业务接口跟着超时。
  • 应用要关心 Kafka 的配置。bootstrap.servers、序列化方式、acks 这些参数都塞到应用的配置文件里,跟业务配置混在一起。
  • 没法做数据清洗。log4j2 的 appender 只负责发,你想在写入前做格式转换、过滤敏感信息,基本没法搞。

加了 Flume 这一层之后:

  • 应用只写本地文件,write 完就结束,Kafka 死活跟它没关系
  • Flume 负责采、负责发、负责重试,采集挂了也不影响业务
  • 想清洗数据?加个拦截器就行,随时改随时生效

实战配置:从文件监控到 Kafka 写入

先用一个最简单的方案把流程跑通:Exec Source 监控一个文件,Memory Channel 暂存,Kafka Sink 发到 Kafka。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# 1. 定义组件名称(Agent、Source、Channel、Sink)  

#事件源名称
agent.sources = execSource
#通道名称
agent.channels = memoryChannel
#接收器名称
agent.sinks = kafkaSink

# 2. 配置 Source:监控本地文件(以 Exec Source 为例)
# For each one of the sources, the type is defined
agent.sources.execSource.type = exec
# 执行 tail -F 命令监控日志文件(实时追踪新增内容)
agent.sources.execSource.command = tail -F /var/log/app/app.log
# 命令执行失败后自动重启(确保高可用)
agent.sources.execSource.restart = true
# 重启间隔(毫秒)
agent.sources.execSource.restartThrottle = 3000

# 3. 配置 Channel:内存通道暂存数据
# The channel can be defined as follows.
# 事件源的通道,绑定通道
agent.channels.memoryChannel.type = memory
# 最大缓存事件数(根据内存调整)
agent.channels.memoryChannel.capacity = 1000
# 每次事务处理的最大事件数
agent.channels.memoryChannel.transactionCapacity = 100


# 4. 配置 Sink:写入 Kafka 主题
# Each sink's type must be defined
# kafka接收器配置
agent.sinks.kafkaSink.type = org.apache.flume.sink.kafka.KafkaSink
# Kafka 集群地址(多个 broker 用逗号分隔)
agent.sinks.kafkaSink.kafka.bootstrap.servers = localhost:9092
# 目标 Kafka 主题(需提前创建)
agent.sinks.kafkaSink.kafka.topic = flume-kafka
# 消息序列化方式(字符串序列化)
agent.sinks.kafkaSink.kafka.serializer.class = kafka.serializer.StringEncoder
# 生产者确认机制:1 表示至少一个副本写入成功(平衡可靠性和性能)
agent.sinks.kafkaSink.kafka.producer.acks = 1
# 批量写入大小(字节):积累到 16KB 再发送,减少网络请求
agent.sinks.kafkaSink.kafka.producer.batch.size = 16384
# 批量发送延迟(毫秒):若 500ms 内未达 batch.size,也触发发送
agent.sinks.kafkaSink.kafka.producer.linger.ms = 500

# 5. 绑定组件关系(核心!连接 Source → Channel → Sink)
#Specify the channel the sink should use
# Source 输出到 Channel
agent.sinks.kafkaSink.channel = memoryChannel

# Sink 从 Channel 读取数据
agent.sinks.kafkaSink.channel = memoryChannel


关键参数解析

配置文件中以下参数直接影响可靠性和性能,需重点关注:

组件 参数 作用与建议值
Source restart = true 命令失败后自动重启,确保监控不中断
Channel capacity = 10000 Memory Channel 的 capacity 设太小,高峰流量直接撑爆;设太大,占内存。我一般先估算每秒多少条日志,乘以 3-5 秒的缓冲量,比如每秒 200 条,capacity 设 1000 就够了。
Sink kafka.producer.acks = 1 可靠性配置:0(最快)、1(平衡)、-1(最可靠)
Sink batch.size + linger.ms batch.size:攒到多少字节再发,默认 16KB
linger.ms:攒不够 batch.size 的话,等多久强制发,默认 0 表示立刻发
这两个是”二选一触发”的关系。我一般把 batch.size 调大到 64KB,linger.ms设 500ms,吞吐能上来不少,延迟也就多个几百毫秒,日志采集完全能接受。

启动 Flume Agent命令

执行以下命令启动 Flume,开始监控文件并写入 Kafka:

1
2
3
4
5
flume-ng agent \  
-c /usr/local/Cellar/flume/1.9.0_1/libexec/conf \ # Flume 配置目录(含 flume-env.sh)
-f conf/flume-file-to-kafka.conf \ # 自定义配置文件路径
--name agent \ # Agent 名称(需与配置文件中一致)
-Dflume.root.logger=INFO,console # 可选:控制台输出日志,便于调试

验证数据写入 Kafka

通过 Kafka 命令行工具验证数据是否成功写入:

方法 1:消费 Kafka 主题
1
2
3
4
5
# 启动 Kafka 消费者,监听 flume-kafka 主题  
kafka-console-consumer.sh \
--bootstrap-server localhost:9092 \
--topic flume-kafka \
--from-beginning # 从头消费所有数据

若配置正确,消费者会实时输出日志文件中的新增内容。

方法 2:查看 Kafka 日志文件

Kafka 消息物理存储在日志文件中,可通过以下命令查看:

1
2
3
4
# 查看主题分区日志(需替换实际日志路径)  
kafka-run-class kafka.tools.DumpLogSegments \
--files /usr/local/var/lib/kafka-logs/flume-kafka-0/00000000000000000000.log \
--print-data-log

输出中 payload 字段即为 Flume 写入的日志内容,例如:

1
payload: "2024-07-22 10:00:00 [INFO] User login success: user_id=123"  

生产环境别用这套配置,得换几个组件

1. 替换 Source 为 Taildir Source

Flume 挂了再起来,tail -F 会从文件开头重新读,数据重复,生产环境建议使用 Taildir Source 监控文件,支持断点续传:

1
2
3
4
5
6
7
# 替换 Source 配置为 Taildir Source  
agent.sources.execSource.type = TAILDIR
# 监控的文件路径(支持通配符)
agent.sources.execSource.filegroups = log1
agent.sources.execSource.filegroups.log1 = /var/log/app/*.log
# 偏移量记录文件(重启后从断点继续)
agent.sources.execSource.positionFile = /var/flume/taildir_position.json

这个 Source 会把每个文件的读取进度记到 positionFile 里,重启之后接着读,不重不漏。

2. 使用 File Channel 增强可靠性

Memory Channel 在 Flume 崩溃时会丢失数据,对可靠性要求高的场景建议使用 File Channel

1
2
3
4
5
# 替换 Channel 配置为 File Channel  
agent.channels.memoryChannel.type = file
agent.channels.memoryChannel.checkpointDir = /var/flume/checkpoint # 元数据目录
agent.channels.memoryChannel.dataDirs = /var/flume/data # 数据存储目录(多路径用逗号分隔)
agent.channels.memoryChannel.capacity = 100000 # 最大事件数

3. Kafka 生产者参数调优

根据业务需求调整 Kafka 生产者参数,平衡性能与可靠性:

1
2
3
4
5
6
7
# 提高吞吐量:增大批量发送大小和缓冲区  
agent.sinks.kafkaSink.kafka.producer.batch.size = 65536 # 64KB
agent.sinks.kafkaSink.kafka.producer.buffer.memory = 67108864 # 64MB

# 网络优化:设置超时时间
agent.sinks.kafkaSink.kafka.producer.retries = 3 # 重试次数
agent.sinks.kafkaSink.kafka.producer.request.timeout.ms = 30000 # 请求超时

另外:Flume 启动的时候不会帮你创建 topic(会报错 Topic flume-kafka does not exist),得提前建好

1
kafka-topics.sh --bootstrap-server localhost:9092 --create --topic flume-kafka --partitions 3 --replication-factor 1  

4. 日志清洗与转换

Flume 的拦截器可以在 Source 往 Channel 发之前,对 Event 做加工。加时间戳是最常见的用法:

1
2
3
# 配置拦截器:添加时间戳头信息  
agent.sources.execSource.interceptors = timestampInterceptor
agent.sources.execSource.interceptors.timestampInterceptor.type = org.apache.flume.interceptor.TimestampInterceptor$Builder

这样每个 Event 的 Header 里会多一个 timestamp 字段,下游消费的时候可以用来做时间窗口聚合。

如果你想过滤掉 DEBUG 级别的日志,或者脱敏手机号,也可以写自定义拦截器,实现 Interceptor 接口就行。

5. 保证日志文件权限

错误提示:Permission denied: /var/log/app/app.log
解决:确保 Flume 进程对监控文件有读权限,或修改文件权限:

1
chmod 644 /var/log/app/app.log  

欢迎关注我的其它发布渠道