flume快速上手

Flume定义

  • Flume最初是 Cloudera 公司推出的一个高可用、高可靠的,分布式的海量日志采集、聚合和传输的系统,于2009年被捐赠给了Apche基金会,成为Hadoop相关组件之一
  • Flume支持在日志系统中定制各类数据发送方,用于收集数据;同时Flume提供对数据进行简单处理,并写到各种数据接收方(可定制)的能力
  • Fluem基于流式架构,灵活简单

Flume基本架构

Flume的基本组件包括 Event 和 Agent

Event

  • Event 是Flume中具有有效负载的字节数据流和可选的字符串属性集,是Flume传送数据的基本单位
  • Event由 Header 和 Body 组成。
    • Header是一个Map存储字符串属性集,为K-V结构
    • Body是一个字节数组,存储字节数据

简单来说,Event其实就是Flume框架作者写的一个类似于类的东西

Agent

Flume运行的核心是 Agent。Flume 已 Agent 为最小的独立运行单位一个Agent 就是一个JVM(java虚拟机,Java Virtual Machine),它是一个完整的数据采集工具,包含三个核心组件,分别是

  • Source(数据源)
  • Channel(数据通道)
  • Sink(数据槽)

Agent主要功能:以事件的形式将数据从源头送至目的地

Source

  • Source 是负责接收数据到Flume Agent 的组件,即 Source是数据的接收端,将数据捕获后进行特殊的格式化,将数据封装到 Event 里,然后将Event推入到 Channel。

  • Source组件可以处理各种类型、各种格式的日志数据,如下

类型 简介
Netcat Source 监控某个端口,读取流经端口的每一个文本行数据
Exec Source Source启动的时候会运行一个设置好的Linux命令,该命令不断往标准输出(stdout)中输出数据,这些数据被打包成Event进行处理**(该source不支持断点续传)**
Spooling Directoy Source 监听指定目录,当该目录有新文件出现时,把文件的数据打包成 Event进行处理**(该source支持断点续传,但是时效性不太好)**
Syslog Source 读取 Sylog数据,产生Event,支持UDP和TCP两种协议
Stress Source 用于可以配置要发送的事件总数以及要传递的最大事件数,多用于负载测试
HTTP Source 基于 HTTP POST或 GET方式的数据源,支持JSON、BLOB表示形式
Avro Source 支持Avro RPC协议,提供了一个 Avro的接口,往设置的地址和端口发送Avro消息,Source就能接收到,例如,Log4j Appender通过Avro Source 将消息发送到Agent
Taildir Source 监听实时追加内容的文件
Thrift Souce 支持 Thrift 协议,提供一个 Thrift接口,类似Avro
JMS Source 从Java消息服务读取数据
Kafka Source 从Kafka消息队列中读取中数据,官方描述:Kafka Source 其实就是一个 Kafka Consumer
  • 每个Source 可以发送Event到多个Channel中

Sink

  • Sink 是不断地轮询 Channel 中事件(Event)并且批量地移除它们,并将这些事件(Event)批量写入到存储或索引系统、或者被发送到另一个 Flume Agent
  • Sink常见类型如下
类型 简介
HDFS Sink 将数据写入HDFS,默认格式为 SequenceFile
Logger Sink 将数据写入日志文件
Hive Sink 将数据写入Hive
File Roll Sink 将数据存储到本地文件系统,多用作数据收集
HBase Sink 将数据写入到HBase
Thrift Sink 将数据转换到Thrift Event后,发送到配置的RPC端口上
Avro Sink 将数据转换到Avro Event后,发送到配置的RPC端口上
Null Sink 丢弃所有的数据
ElasticSearch Sink 将数据发送到 ElasticSearch 集群上
Kite Dataset Sink 写数据到 Kite Dataset,试验性质
Kafka Sink 官方描述:Kafka Sink 能向Kafka 的topic 写入数据(Kafka Sink其实就是Kafka Producer 的实现)
  • 每个Sink只能从一个Channel中获取数据

Channel

  • Channel 是位于 Source 和 Sink 之间的组件,可以看做是数据的缓冲区(数据队列),它可以将事件暂存到内存中,也可以将事件持久化到本地磁盘上,直到 Sink处理完该事件。

  • Channel 允许 Source 和 Sink运作在不同的速率上

  • Channel 是线程安全的,可以同时处理几个 Source 的写入操作和几个Sink 的读取操作

  • Channel的常见类型如下 (其中 Memory、File是Flume自带的)

类型 简介
Memory Channel 数据存储到内存的队列中,可以实现高速的数据吞吐,Flume出现故障时,数据会丢失
File Channel 数据存储到磁盘文件中,可以持久化所有的Event,Flume出现故障时,数据不会丢失**(该File在内存中有索引机制,加快读取速率,并且索引会在磁盘做两次备份)**
JDBC Channel 数据持久化到数据库中
Kafka Channel 数据存储到Kafka集群中
Custom Channel 自定义Channel

小结

​ Flume 提供了大量内置的 Source、Channel、Sink 类型。不同类型的 Source、Channel、Sink 可以自由组合。组合方式基于用户设置的配置文件,非常灵活。例如,Channel可以把Event(事件)暂存在内存里,也可以将Event(事件)持久化到本地磁盘上;Channel 可以把日志写入HDFS、HBase,甚至另外一个 Source。

Flume 安装部署

1)上次压缩包

将 apache-flume-1.9.0-bin.tar.gz 压缩包上传至Linux 的 /opt/software目录下

2)解压压缩包

将 apache-flume-1.9.0-bin.tar.gz 解压到 /opt/software目录下

[root@kk01 software]# pwd
/opt/software
[root@kk01 software]# tar -zxvf apache-flume-1.9.0-bin.tar.gz -C /opt/software/

3)将apache-flume-1.9.0-bin 重命名为 flume

因为flume只是一个工具,它的版本没有那么重要,因此我们可以改名

[root@kk01 software]# mv apache-flume-1.9.0-bin flume

4)将 lib 目录下的 guava-11.0.2.jar 删除以兼容 Hadoop 3.2.2

[root@kk01 software]# rm /opt/software/flume/lib/guava-11.0.2.jar

注意:删除guava-11.0.2.jar的服务器节点,一定要配置hadoop环境变量。否则会报如下异常。

Caused by: java.lang.ClassNotFoundException: com.google.common.collect.Lists
        at java.net.URLClassLoader.findClass(URLClassLoader.java:382)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
        at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
        ... 1 more

5)修改 conf 目录下的 log4j.properties 确定日志打印的位置

[root@kk01 software]# cd /opt/software/flume/conf/
[root@kk01 conf]# vim log4j.properties 

# 修改内容如下

# console表示同时将日志输出到控制台
flume.root.logger=INFO,LOGFILE,console
# 固定日志输出的位置
flume.log.dir=/opt/software/flume/logs
# 日志文件的名称
flume.log.file=flume.log

6)配置flume环境变量

[root@kk01 conf]# vim /etc/profile

# 在文件末尾加上如下内容

# flume环境变量
export FLUME_HOME=/opt/software/flume
export PATH=$PATH:$FLUME_HOME/bin

# 使环境变量生效
[root@kk01 conf]# source /etc/profile

7)将flume-env.ps1.template 重命名为 flume-env.ps1

在flume安装目录下的conf目录下有个 flume-env.ps1.template文件,需要将它重命名为flume-env.ps1,有两种做法:

​ (保守做法)将flume-env.ps1.template 文件 复制出 flume-env.ps1

​ (常规做法)将flume-env.ps1.template 文件 重命名为 flume-env.ps1

# 下面我们采取保守做法
/opt/software/flume/conf
[root@kk01 conf]# cp flume-env.ps1.template flume-env.ps1
[root@kk01 conf]# ll
total 20
-rw-r--r--. 1 nhk  nhk  1661 Nov 16  2017 flume-conf.properties.template
-rw-r--r--. 1 root root 1455 May 11 23:10 flume-env.ps1    # 我们复制出来的文件
-rw-r--r--. 1 nhk  nhk  1455 Nov 16  2017 flume-env.ps1.template
-rw-r--r--. 1 nhk  nhk  1568 Aug 30  2018 flume-env.sh.template
-rw-rw-r--. 1 nhk  nhk  3237 May 11 22:57 log4j.properties

8)将 flume-env.sh.template 重命名为 flume-env.sh

在flume安装目录下的conf目录下有个 flume-env.sh.template文件,需要将它重命名为flume-env.sh,有两种做法:

​ (保守做法)将flume-env.sh.template 文件 复制出 flume-env.sh

​ (常规做法)将flume-env.sh.template 文件 重命名为 flume-env.sh

# 下面我们采取保守做法
[root@kk01 conf]# cp flume-env.sh.template flume-env.sh
[root@kk01 conf]# ll
total 24
-rw-r--r--. 1 nhk  nhk  1661 Nov 16  2017 flume-conf.properties.template
-rw-r--r--. 1 root root 1455 May 11 23:10 flume-env.ps1
-rw-r--r--. 1 nhk  nhk  1455 Nov 16  2017 flume-env.ps1.template
-rw-r--r--. 1 root root 1568 May 11 23:12 flume-env.sh
-rw-r--r--. 1 nhk  nhk  1568 Aug 30  2018 flume-env.sh.template
-rw-rw-r--. 1 nhk  nhk  3237 May 11 22:57 log4j.properties

9)修改 flume-env.sh

[root@kk01 conf]# vim flume-env.sh

# 在文件内添加如下内容

export JAVA_HOME=/opt/software/jdk1.8.0_152

10)查看Flume版本信息

[root@kk01 conf]# flume-ng version
Flume 1.9.0
Source code repository: https://git-wip-us.apache.org/repos/asf/flume.git
Revision: d4fcab4f501d41597bc616921329a4339f73585e
Compiled by fszabo on Mon Dec 17 20:45:25 CET 2018
From source with checksum 35db629a3bda49d23e9b3690c80737f9

至此,Flume部署完成

Flume 数据流模型

​ Flume 常见的数据流模型有 单Agent数据流模型、多Agent串行数据流模型、多Agent汇聚数据流模型、单Agent多路数据流模型

1) 单Agent数据流模型

​ 单Agent数据流模型,一个Agent由一个Source、一个Channel、一个Sink组成

2)多Agent串行数据流模型

​ 假设有两个Agent,Agent1、Agent2,为了使数据在多个Agent(我们以两个为例)中流通,Agent1中的Sink 和 Agent2 中的 Source 需要是Avro类型,Agent1中的Sink 指向 Agent2 这的 Source的主机名(或IP地址)和端口

3)多Agent汇聚数据流模型

多Agent汇聚数据流模型是采集大量日志时常用的数据流模型。例如,将从数百个web服务器采集的日志数据发送给写入HDFS集群中的十几个Agent,此时就可以采用此模型。

4)单Agent多路数据流模型

单Agent多路数据流模型,一个Agent由一个Source、多个Channel、一个Sink组成。

一个Source接收Event,将 Event 发送到 多个Channel中,Channel对应的Sink处理各自Channel 内的 Event,然后将数据分别存储到指定的位置。

Source 将 Event 发送到 Channel 中可以采取两种不同的策略:

  • Replicating(复制通道选择器):即Source将每个Event发送到每个与它连接的 Channel 中,也就是将 Event复制多份发送到不同的 Channel 中

  • Multiplexing(多路复用通道选择器):即Source根据Hader中的一个键决定将 Event 发送到哪个Channel中

Flume 案例

官方文档 https://flume.apache.org/releases/content/1.9.0/FlumeUserGuide.html

演示 Netcat Source

1)安装netcat

[root@kk01 ~]# yum install -y nc

2)测试netcat

打开两个终端,第一个用于向9999端口发送消息,开启后终端会卡住,表示进入了等待输入消息的状态。

[root@kk01 ~]# nc -lk 9999

查看端口9999是否被占用(可选)

[root@kk01 ~]# netstat -nlp | grep 9999

第二个用于接收9999端口收到的消息

[root@kk01 ~]# nc localhost 9999

第一个终端发送消息

[root@kk01 ~]# nc -lk 9999
hello
world

第二个终端收到了消息

[root@kk01 ~]# nc localhost 9999
hello
world

此致,就验证了nc的可用性,说明它可以进行双向通信

按 ctrl+z 退出

3)创建 Flume Agent 文件 nc-flume-log.conf

​ 为方便之后的执行,该配置文件需要放在flume根目录的job(自己创建)的文件夹下,其他位置也行,后面命令相应变更即可。

[root@kk01 flume]# pwd
/opt/software/flume
[root@kk01 flume]# mkdir job
[root@kk01 flume]# cd job/
[root@kk01 job]# vim nc-flume-log.conf

nc-flume-log.conf 文件内容如下

# example.conf: A single-node Flume configuration

# Name the components on this agent
a1.sources = r1
a1.sinks = k1
a1.channels = c1

# Describe/configure the source
a1.sources.r1.type = netcat
a1.sources.r1.bind = localhost
a1.sources.r1.port = 9999

# Describe the sink
a1.sinks.k1.type = logger

# Use a channel which buffers events in memory
a1.channels.c1.type = memory
a1.channels.c1.capacity = 1000
a1.channels.c1.transactionCapacity = 100

# Bind the source and sink to the channel
a1.sources.r1.channels = c1
a1.sinks.k1.channel = c1

conf文件说明

a1 即agent,当前flume进程的名字
a1.sources.r1.type = netcat		表示 source 的类型
a1.sinks.k1.type = logger		表示 sink 类型
a1.channels.c1.type = memory	表示 channel 类型
a1.channels.c1.capacity = 1000	表示 1000条数据
a1.channels.c1.transactionCapacity = 100	表示当个事务的容量

4)启动 flume

# 方式一
bin/flume-ng agent --conf conf --conf-file example.conf --name a1 -Dflume.root.logger=INFO,console

# 方式二
bin/flume-ng agent -n $agent_name -c conf -f conf/flume-conf.properties.template

# 参数说明
--conf / -c 表示配置文件存储在conf/目录下
--name / -n 表示给 agent 起名为 a1
--conf-file / -f flume本次启动读取的配置文件是在 conf目录下的 flume-conf.properties.template 文件
-Dflume.root.logger=INFO,console 	
	-D 表示flume运行时动态修改flume.root.logger参数属性值,并将控制台日志打印级别设置为INFO。
		(日志级别包括 log、info、warn、error)
[root@kk01 flume]# 
[root@kk01 flume]# 
[root@kk01 flume]# pwd
/opt/software/flume
[root@kk01 flume]# bin/flume-ng agent -c conf/ -f job/nc-flume-log.conf -n a1 

# 此时如果没有报错信息的话,证明flume已经启动成功了

接下来可使用nc命令开启一个客户端,向9999端口号发送消息(需另外开一个终端)

[root@kk01 applog]# nc localhost 9999
hello
OK			# 这里的ok是由netcat source返回的
world
OK

向flume发送消息成功,flume会返回一个OK

Flume采集到消息并打印到控制台

2023-06-07 08:05:35,840 (SinkRunner-PollingRunner-DefaultSinkProcessor) [INFO - org.apache.flume.sink.LoggerSink.process(LoggerSink.java:95)] Event: { header
s:{} body: 68 65 6C 6C 6F                                  hello }2023-06-07 08:05:35,841 (SinkRunner-PollingRunner-DefaultSinkProcessor) [INFO - org.apache.flume.sink.LoggerSink.process(LoggerSink.java:95)] Event: { header
s:{} body: 77 6F 72 6C 64                                  world }

​ 可以看到,Flume将收集的消息封装成了Event 对象,其中包含 headers 和 body 两个部分,而信息的内容包含在body里。

演示(taildir source)实时监控目录下的多个追加文件

Taildir Source 适用于监听多个实时追加的文件,并能够实现断点续传功能。

下面演示的需求是:使用Flume 监听整个目录的实时追加的文件,并上传至HDFS

1)在conf 目录下创建配置文件 tailder-flume-hdfs.conf

我们在flume根目录下的job目录(我们自己创建的目录)下创建 tailder-flume-hdfs.conf

[root@kk01 job]# pwd
/opt/software/flume/job
[root@kk01 job]# touch file-flume-hdfs.conf
[root@kk01 job]# ll
total 4
-rw-r--r--. 1 root root   0 Jun  7 08:18 file-flume-hdfs.conf
-rw-r--r--. 1 root root 567 May 28 01:59 nc-flume-log.conf

tailder-flume-hdfs.conf 文件参考配置如下

# Name the components on this agent
a1.sources = r1
a1.sinks = k1
a1.channels = c1

# Describe/configure the source
a1.sources.r1.type = TAILDIR
a1.sources.r1.filegroups = f1 
a1.sources.r1.filegroups.f1 = /opt/software/flume/files/.* 
a1.sources.r1.positionFile = /opt/software/flume/taildir_position.json

# Describe the sink
a1.sinks.k1.type = logger

# Use a channel which buffers events in memory
a1.channels.c1.type = memory
a1.channels.c1.capacity = 1000
a1.channels.c1.transactionCapacity = 100

# Bind the source and sink to the channel
a1.sources.r1.channels = c1
a1.sinks.k1.channel = c1

注意:

​ 必须保证 taildir source 监控的目录是存在的,否则报错 Directory does not exist

2)创建被监控的目录

[root@kk01 flume]# mkdir files

3)测试

先开启flume

[root@kk01 flume]# bin/flume-ng agent -c conf/ -f job/file-flume-hdfs.conf -n a1

然后再打开一个终端,并在 files目录下创建了一些文件,并在文件内追加了一些内容

[root@kk01 flume]# cd files/
[root@kk01 files]# ll
total 0
[root@kk01 files]# echo hi >> 1.txt
[root@kk01 files]# echo hello >> 1.txt

我们可用看到flume将文件里的内容打印到了控制台

2023-06-07 08:30:58,729 (SinkRunner-PollingRunner-DefaultSinkProcessor) [INFO - org.apache.flume.sink.LoggerSink.process(LoggerSink.java:95)] Event: { header
s:{} body: 68 69                                           hi }2023-06-07 08:31:13,746 (SinkRunner-PollingRunner-DefaultSinkProcessor) [INFO - org.apache.flume.sink.LoggerSink.process(LoggerSink.java:95)] Event: { header
s:{} body: 68 65 6C 6C 6F                                  hello }

此时我们停止flume(ctrt + c)

接着在files目录下创建了文件,并追加了内容

[root@kk01 files]# echo flume >> 2.txt
[root@kk01 files]# echo hadoop >> 1.txt
[root@kk01 files]# 

再次开启 flume,我们发现flume在控制台打印了刚刚关闭flume之后在 files目录下新增的内容,这说明flume 的 taildir source 拥有断点续传功能

[root@kk01 flume]# bin/flume-ng agent -c conf/ -f job/file-flume-hdfs.conf -n a1
....
2023-06-07 08:31:49,692 (SinkRunner-PollingRunner-DefaultSinkProcessor) [INFO - org.apache.flume.sink.LoggerSink.process(LoggerSink.java:95)] Event: { header
s:{} body: 66 6C 75 6D 65                                  flume }2023-06-07 08:31:49,693 (SinkRunner-PollingRunner-DefaultSinkProcessor) [INFO - org.apache.flume.sink.LoggerSink.process(LoggerSink.java:95)] Event: { header
s:{} body: 68 61 64 6F 6F 70                               hadoop }2023-06-07 

它是如何实现断点续传功能的呢?答案显然是在 positionFile = /opt/software/flume/taildir_position.json

[root@kk01 flume]# ll
total 172
....

-rw-r--r--.  1 root root   140 Jun  7 08:41 taildir_position.json
drwxr-xr-x.  2 root root    68 May 11 22:44 tools
[root@kk01 flume]# cat taildir_position.json 
[{"inode":214408104,"pos":6,"file":"/opt/software/flume/files/2.txt"},{"inode":214408106,"pos":16,"file":"/opt/software/flume/files/1.txt"}]

显然 taildir_position.json 文件中就存储了关于断点续传的信息, pos表示的显然就是字节偏移量了

4)再次修改 tailder-flume-hdfs.conf 文件

修改后如下

# Describe/configure the source
a1.sources.r1.type = TAILDIR
a1.sources.r1.filegroups = f1 f2
a1.sources.r1.filegroups.f1 = /opt/software/flume/files/.* 
a1.sources.r1.filegroups.f2 = /opt/software/flume/files2/.* 
a1.sources.r1.positionFile = /opt/software/flume/taildir_position.json

# Describe the sink
a1.sinks.k1.type = hdfs
a1.sinks.k1.hdfs.path = /flume/%Y%m%d/%H
a1.sinks.k1.hdfs.filePrefix = log-
a1.sinks.k1.hdfs.rollInterval = 10
a1.sinks.k1.hdfs.rollSize = 134217728
a1.sinks.k1.hdfs.rollCount = 0

a1.sinks.k1.hdfs.useLocalTimeStamp = true 

# Use a channel which buffers events in memory
a1.channels.c1.type = memory
a1.channels.c1.capacity = 1000
a1.channels.c1.transactionCapacity = 100

# Bind the source and sink to the channel
a1.sources.r1.channels = c1
a1.sinks.k1.channel = c1

参数说明

a1.sinks.k1.hdfs.filePrefix = log-		表示文件前缀名
a1.sinks.k1.hdfs.rollInterval = 10		表示没10s将 .tmp文件 转为正式文件
a1.sinks.k1.hdfs.rollSize = 134217728	表示每个文件大小为最大为 134217728byte即 128MB
a1.sinks.k1.hdfs.rollCount = 0			表示每个文件最多存储多少条数据,0表示不启用该参数

5)再次启动Flume测试

启动Flume之前,先启动 Hadoop集群,因为我们这里演示的是Hadoop发送文件

[root@kk01 flume]# bin/flume-ng agent -c conf/ -f job/file-flume-hdfs.conf -n a1

再另一个终端,输入一些内容

[root@kk01 files]# echo hello666 >> 1.txt
[root@kk01 files]# echo hello666 >> 1.txt

查看Hadoop,发现生成了文件

[root@kk01 flume]# hadoop fs -ls /flume
Found 1 items
drwxr-xr-x   - root supergroup          0 2023-06-07 09:18 /flume/20230607
[root@kk01 flume]# hadoop fs -ls /flume/20230607
Found 1 items
drwxr-xr-x   - root supergroup          0 2023-06-07 09:18 /flume/20230607/09
[root@kk01 flume]# hadoop fs -ls /flume/20230607/09
Found 2 items
-rw-r--r--   3 root supergroup        143 2023-06-07 09:18 /flume/20230607/09/log-.1686143881090
-rw-r--r--   3 root supergroup        143 2023-06-07 09:18 /flume/20230607/09/log-.1686143925185

Taildir 说明

Taildir source 维护了一个 json 格式的 position File,其会定期的往 position File 中更新每个文件读取到的最新位置,因此能够实现断点续传功能。 position File 的格式如下

[{"inode":214408104,"pos":6,"file":"/opt/software/flume/files/2.txt"},{"inode":214408106,"pos":34,"file":"/opt/software/flume/files/1.txt"},{"inode":5672023,
"pos":6,"file":"/opt/software/flume/files2/1.txt"}]

注:

​ Linux 中存储文件元数据的区域就叫做 inode,每个 inode 都有一个号码,操作系统用 inode 号码来识别不同的文件,Unix/Linux 系统内部不使用文件名,而使用inode号码来识别文件。Taildir Source 使用 inode 和文件的全路径一起识别同一个文件名,所以修改文件名之后如果表达式也能匹配得上,会重新读取一份文件得数据。

Flume事务

​ Flume事务包括Put事务和Take事务。Flume事务保证了数据在Source - Channel,以及Channel - Sink,这两个阶段传世时不会丢失,需要注意的是,Take事务可能导致数据重复。

Put事务

​ 首先 Source 会采集一批数据,封装为event,缓存达到 batch data 的最大容量时(batch data的大小取决于配置参数batch size的值),Flume开启事务:

  • doPut():将这批event写入到临时缓冲区 putList,putList是一个LinkedBlockingDeque,大小取决于配置Channel 的参数 transaction capacity 的大小。

  • doCommit():检查channel内存队列是否足够合并,内存队列的大小由 Channel 的 capacity 参数控制, Channel的容量内存队列足够的时候,提交event成功。

  • doRollback(): channel内存队列空间不够时,回滚,这里会将整个putList中的数据都扔掉,然后给Source返回一个ChannelException 异常,告诉Source数据没有采集上。Source会重新采集这批数据,然后开启新的事务。

Take事务

  • doTake():sink将数据剪切取到临时缓冲区takeList,takeList 也是一个LinkedBlockingDeque,
    大小取决于配置 Channel的 参数 transaction capacity 的大小,同时也拷贝一份放入写往HDFS的IO流中。

  • doCommit():如果event全部发送成功,就清除takeList。

  • doRollback():如果发送过程中出现异常,回滚,将takeList中的全部 event 归还给Channel。这个操作可能导致数据重复,如果已经写入一半的event到了HDFS,但是回滚时会向channel归还整个takeList中的event,后续再次开启事务向HDFS写入这批event时候,就出现了数据重复。

​ Flume的事务仅能保证两个传输阶段的数据不丢,但是如果channel选用的是memory channel,那么由于memory channel将数据存储在内存中,一旦channel发生异常,数据仍然可能丢失,但采用File channel时,数据传输到channel时会落盘,再结合事务,会保证整体上数据不会丢失,但是仍然可能会在take事务阶段发生数据重复。

Flume内部原理

  • 1**)Source从外部接收数据,交给Channel Processor**(channel处理器)进行处理event
  • 2)Channel Processor将 event传递给拦截器链,通过拦截器对event进行过滤清洗:比如时间拦截,分类等
  • 3)经过拦截器处理的数据经过channel选择器,发往不同的channel;channel选择器有两种:
    • 一类Replicating Channel Selector,会将source中的event发往所有channel,能够冗余副本,提高可用性
    • 另一类Multiplexing Channel Selector:会根据event中header中的某个value将数据发到对应的channel
  • 4)最后sink处理器处理每个channel中的事件

重要组件:

  • ChannelSelector

ChannelSelector 的作用就是选出 Event 将要被发往哪个 Channel。其共有两种类型,分别是 Replicating(复制)和 Multiplexing(多路复用)

ReplicatingSelector 会将同一个 Event 发往所有的 Channel, Multiplexing 会根据相应的原则,将不同的 Event 发往不同的 Channel。

  • SinkProcessor(很少使用,了解即可)

SinkProcessor 共 有 三 种 类 型 , 分 别 是 DefaultSinkProcessor(默认一对一) 、LoadBalancingSinkProcessor(负载均衡) 和 FailoverSinkProcessor(故障转移

DefaultSinkProcessor 对 应 的 是 单 个 的 Sink , LoadBalancingSinkProcessor 和FailoverSinkProcessor 对应的是 Sink Group, LoadBalancingSinkProcessor 可以实现负载均衡的功能, FailoverSinkProcessor 可以实现故障转移的功能。

复制 Replicating 演示

需求:flume1 使用 TailDirSource监控本地目录,使用Replicating Channel Selector生成两个Channel,一个用于flume2将其采集到HDFS、另一个用于flume3采集到本地的其他目录

1)在flume根目录的job目录下创建group1目录

[nhk@kk01 job]$ pwd
/opt/software/flume/job
[nhk@kk01 job]$ mkdir group1
[nhk@kk01 job]$ cd group1/

2)在group1目录创建三个flume配置文件

[nhk@kk01 group1]$ touch flume1.conf
[nhk@kk01 group1]$ touch flume2.conf
[nhk@kk01 group1]$ touch flume3.conf

flume1.conf 配置文件如下

# 1.定义组件
a1.sources = r1
a1.sinks = k1 k2
a1.channels = c1 c2 

# 2.配置sources
a1.sources.r1.type = TAILDIR
a1.sources.r1.filegroups = f1 f2
a1.sources.r1.filegroups.f1 = /opt/software/flume/files1/.*files.* 
a1.sources.r1.filegroups.f2 = /opt/software/flume/files2/.*log.* 
a1.sources.r1.positionFile = /opt/software/flume/taildir_position.json

# 将数据流复制给所有 channel(默认参数,可以不写)
a1.sources.r1.selector.type = replicating

# 3.配置channels
a1.channels.c1.type = memory
a1.channels.c1.capacity = 1000
a1.channels.c1.transactionCapacity = 100

a1.channels.c2.type = memory
a1.channels.c2.capacity = 1000
a1.channels.c2.transactionCapacity = 100

# 4.配置sinks
a1.sinks.k1.type = avro
a1.sinks.k1.hostname = kk01
a1.sinks.k1.port = 4141

a1.sinks.k2.type = avro
a1.sinks.k2.hostname = kk01
a1.sinks.k2.port = 4142

# 5.组装
a1.sources.r1.channels = c1 c2
a1.sinks.k1.channel = c1
a1.sinks.k2.channel = c2

flume2.conf 配置文件如下

# 1.定义组件
a1.sources = r1
a1.sinks = k1 
a1.channels = c1  

# 2.配置sources
a1.sources.r1.type = avro
a1.sources.r1.bind = kk01
a1.sources.r1.port = 4141

# 3.配置channels
a1.channels.c1.type = memory
a1.channels.c1.capacity = 1000
a1.channels.c1.transactionCapacity = 100

# 4.配置sinks
a1.sinks.k1.type = hdfs
a1.sinks.k1.hdfs.path = /flume1/%Y%m%d/%H
a1.sinks.k1.hdfs.filePrefix = log-
# 实际开发使用时间需要调整为1h,学习使用10s
a1.sinks.k1.hdfs.rollInterval = 10
a1.sinks.k1.hdfs.rollSize = 134217728
a1.sinks.k1.hdfs.rollCount = 0

# 是否使用本地时间戳	
a1.sinks.k1.hdfs.useLocalTimeStamp = true 

# 5.组装
a1.sources.r1.channels = c1 
a1.sinks.k1.channel = c1

flume3.conf 配置文件如下

# 1.定义组件
a1.sources = r1
a1.sinks = k1 
a1.channels = c1  

# 2.配置sources
a1.sources.r1.type = avro
a1.sources.r1.bind = kk01
a1.sources.r1.port = 4142

# 3.配置channels
a1.channels.c1.type = memory
a1.channels.c1.capacity = 1000
a1.channels.c1.transactionCapacity = 100

# 4.配置sinks
a1.sinks.k1.type = file_roll
a1.sinks.k1.sink.directory = /opt/software/flume/flume3datas

# 5.组装
a1.sources.r1.channels = c1 
a1.sinks.k1.channel = c1

注意:

​ 需要确保目录存在,否则会报错

[nhk@kk01 flume]$ mkdir files1
[nhk@kk01 flume]$ mkdir files2
[nhk@kk01 flume]$ mkdir flume3datas

3)启动3个kk01的终端,分别代表 flume1、flume2、flume3,并启动

[nhk@kk01 flume]$ bin/flume-ng agent -n a1 -c conf/ -f job/group1/flume1.conf
[nhk@kk01 flume]$ bin/flume-ng agent -n a1 -c conf/ -f job/group1/flume2.conf
[nhk@kk01 flume]$ bin/flume-ng agent -n a1 -c conf/ -f job/group1/flume3.conf

4)往flies1目录写数据

[nhk@kk01 files1]$ pwd
/opt/software/flume/files1
[nhk@kk01 files1]$ echo hello >> files1

5)查看变化情况

flume1打印日志

2023-06-23 03:43:08,537 (PollableSourceRunner-TaildirSource-r1) [INFO - org.apache.flume.source.taildir.ReliableTaildirEventReader.openFile(ReliableTaildirEv
entReader.java:290)] Opening file: /opt/software/flume/files1/files1, inode: 210532613, pos: 0

flume2打印日志

2023-06-23 03:43:21,598 (hdfs-k1-roll-timer-0) [INFO - org.apache.flume.sink.hdfs.BucketWriter.doClose(BucketWriter.java:438)] Closing /flume1/20230623/03/lo
g-.1687506191543.tmp2023-06-23 03:43:21,623 (hdfs-k1-call-runner-9) [INFO - org.apache.flume.sink.hdfs.BucketWriter$7.call(BucketWriter.java:681)] Renaming /flume1/20230623/03/l
og-.1687506191543.tmp to /flume1/20230623/03/log-.1687506191543

flume3打印日志

2023-06-23 03:43:15,657 (New I/O worker #1) [INFO - org.apache.avro.ipc.NettyServer$NettyServerAvroHandler.handleUpstream(NettyServer.java:171)] [id: 0xe4313
927, /192.168.188.128:40568 => /192.168.188.128:4142] CONNECTED: /192.168.188.128:40568

查看 flume3datas目录

[nhk@kk01 flume]$ cd flume3datas/
[nhk@kk01 flume3datas]$ ll
total 4
-rw-rw-r--. 1 nhk nhk 0 Jun 23 03:42 1687506137550-1
-rw-rw-r--. 1 nhk nhk 0 Jun 23 03:42 1687506137550-2
-rw-rw-r--. 1 nhk nhk 6 Jun 23 03:43 1687506137550-3

此致,该演示完成

多路复用 Multiplexing 及 拦截器 演示

需求:使用flume采集服务器本地日志,需要按照日志类型的不同,将不同类型的日志发往不同的分析系统

​ 我们以端口数据模拟日志,以数字(单个)和字母(单个)模拟不同类型的日志,我们需要自定义 interceptor 区分数字和字母,将其发往不同的分析系统(channel)

Multiplexing 的原理:

根据 event中Header的某个key的值,将不同的event发送到不同的channel中,所以我们需要自定义一个 interceptor ,为不同类型的event的 Header中的key赋予不同的值。

1)编写flume拦截器

创建flume-interceptor 模块,在pom.xml中导入依赖

<dependencies>
	<dependency>
		<groupId>org.apache.flumegroupId>
		<artifactId>flume-ng-coreartifactId>
		<version>1.9.0version>
	dependency>
dependencies>

2)创建 MyInterceptor类

在 com.clear.flume中创建 MyInterceptor类,并实现Interceptor 接口

package com.clear.flume;

import org.apache.flume.Context;
import org.apache.flume.Event;
import org.apache.flume.interceptor.Interceptor;

import java.util.List;
import java.util.Map;

/**
 * 1.继承flume拦截器接口
 * 2.重写4个抽象方法
 * 3.编写静态内部类 builder
 */
public class MyInterceptor implements Interceptor {

    // 初始化方法
    @Override
    public void initialize() {

    }

    // 处理方法:处理单个event
    @Override
    public Event intercept(Event event) {
        // 需求:在 event 的头添加标记
        // 提供给 channel selector 选择发送到不同的channel
        Map<String, String> headers = event.getHeaders();
        String log = new String(event.getBody());

        // 判断log的开头第一个字符
        // 如果是字母发送到 channel1
        // 如果是数字发送到 channel2
        char c = log.charAt(0);
        if (c >= '0' && c <= '9') {
            // c为数字
            headers.put("type", "number");
        } else if (c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z') {
            // c为字母
            headers.put("type", "letter");
        }
        // 因为头信息属性是一个引用类型,直接修改对象即可
        // 也可以不调用set方法
        // event.setHeaders(headers);
        return event;
    }

    // 处理方法:处理多个event
    // 系统会调用这个方法
    @Override
    public List<Event> intercept(List<Event> events) {
        for (Event event : events) {
            intercept(event);
        }
        return events;
    }

    // 关闭
    @Override
    public void close() {

    }

    public static class Builder implements Interceptor.Builder{

        // 创建一个拦截器对象
        @Override
        public Interceptor build() {
            return new MyInterceptor();
        }

        // 配置方法
        @Override
        public void configure(Context context) {
        }
    }
}

3)打包模块并上传至服务器

将打包好的jar包上传至 flume根目录下的lib目录

[nhk@kk01 lib]$ pwd
/opt/software/flume/lib
[nhk@kk01 lib]$ ll | grep flume-interceptor
-rw-r--r--. 1 nhk nhk    3511 Jun 23 04:15 flume-interceptor-1.0-SNAPSHOT.jar

4)编写配置文件

在${FLUME_HOME}/job目录下创建group2目录,创建flume1.conf、flume3.conf、flume3.conf

[nhk@kk01 job]$ mkdir group2
[nhk@kk01 job]$ cd group2
[nhk@kk01 group2]$ touch flume1.conf
[nhk@kk01 group2]$ touch flume2.conf
[nhk@kk01 group2]$ touch flume3.conf

flume1.conf 配置文件如下

# 1.定义组件
a1.sources = r1
a1.sinks = k1 k2
a1.channels = c1 c2 

# 2.配置sources
a1.sources.r1.type = netcat
a1.sources.r1.bind = localhost
a1.sources.r1.port = 44444

a1.sources.r1.selector.type = multiplexing
# 填写标记的key
a1.sources.r1.selector.header = type
# 为数字发往flume3(即value对应的channel)
a1.sources.r1.selector.mapping.number = c2
# 为字母发往flume2
a1.sources.r1.selector.mapping.letter = c1
# 如果匹配不上走的channel
a1.sources.r1.selector.default = c2

a1.sources.r1.interceptors = i1
a1.sources.r1.interceptors.i1.type = com.clear.flume.MyInterceptor$Builder

# 3.配置channels
a1.channels.c1.type = memory
a1.channels.c1.capacity = 1000
a1.channels.c1.transactionCapacity = 100

a1.channels.c2.type = memory
a1.channels.c2.capacity = 1000
a1.channels.c2.transactionCapacity = 100

# 4.配置sinks
a1.sinks.k1.type = avro
a1.sinks.k1.hostname = kk01
a1.sinks.k1.port = 4141

a1.sinks.k2.type = avro
a1.sinks.k2.hostname = kk01
a1.sinks.k2.port = 4142

# 5.组装
a1.sources.r1.channels = c1 c2
a1.sinks.k1.channel = c1
a1.sinks.k2.channel = c2

flume2.conf 配置文件如下

# 1.定义组件
a1.sources = r1
a1.sinks = k1 
a1.channels = c1  

# 2.配置sources
a1.sources.r1.type = avro
a1.sources.r1.bind = kk01
a1.sources.r1.port = 4141

# 3.配置channels
a1.channels.c1.type = memory
a1.channels.c1.capacity = 1000
a1.channels.c1.transactionCapacity = 100

# 4.配置sinks
a1.sinks.k1.type = logger

# 5.组装
a1.sources.r1.channels = c1 
a1.sinks.k1.channel = c1

flume3.conf 配置文件如下

# 1.定义组件
a1.sources = r1
a1.sinks = k1 
a1.channels = c1  

# 2.配置sources
a1.sources.r1.type = avro
a1.sources.r1.bind = kk01
a1.sources.r1.port = 4142

# 3.配置channels
a1.channels.c1.type = memory
a1.channels.c1.capacity = 1000
a1.channels.c1.transactionCapacity = 100

# 4.配置sinks
a1.sinks.k1.type = logger

# 5.组装
a1.sources.r1.channels = c1 
a1.sinks.k1.channel = c1

5)启动flume

启动3个kk01的终端,分别代表 flume1、flume2、flume3,并启动

[nhk@kk01 flume]$ bin/flume-ng agent -n a1 -c conf/ -f job/group2/flume3.conf 
[nhk@kk01 flume]$ bin/flume-ng agent -n a1 -c conf/ -f job/group2/flume2.conf 
[nhk@kk01 flume]$ bin/flume-ng agent -n a1 -c conf/ -f job/group2/flume1.conf

6)启动nc工具并输入内容

在打开一个kk01的终端,启动nc

[nhk@kk01 flume]$ nc localhost 44444
abs
OK
ssf
OK
123
OK
344
OK
244
OK
hhe
OK

7)观察flume2、flume3

flume2打印日志

2023-06-23 04:50:31,589 (SinkRunner-PollingRunner-DefaultSinkProcessor) [INFO - org.apache.flume.sink.LoggerSink.process(LoggerSink.java:95)] Event: { header
s:{type=letter} body: 61 62 73                                        abs }2023-06-23 04:50:31,590 (SinkRunner-PollingRunner-DefaultSinkProcessor) [INFO - org.apache.flume.sink.LoggerSink.process(LoggerSink.java:95)] Event: { header
s:{type=letter} body: 73 73 66                                        ssf }2023-06-23 04:50:33,499 (SinkRunner-PollingRunner-DefaultSinkProcessor) [INFO - org.apache.flume.sink.LoggerSink.process(LoggerSink.java:95)] Event: { header
s:{type=letter} body: 68 68 65                                        hhe }

flume3打印日志

2023-06-23 04:50:33,854 (SinkRunner-PollingRunner-DefaultSinkProcessor) [INFO - org.apache.flume.sink.LoggerSink.process(LoggerSink.java:95)] Event: { header
s:{type=number} body: 31 32 33                                        123 }2023-06-23 04:50:33,854 (SinkRunner-PollingRunner-DefaultSinkProcessor) [INFO - org.apache.flume.sink.LoggerSink.process(LoggerSink.java:95)] Event: { header
s:{type=number} body: 33 34 34                                        344 }2023-06-23 04:50:33,854 (SinkRunner-PollingRunner-DefaultSinkProcessor) [INFO - org.apache.flume.sink.LoggerSink.process(LoggerSink.java:95)] Event: { header
s:{type=number} body: 32 34 34                                        244 }

通过观察得知,nc工具输入的字母都发往了flume2、输入的数字都发往了flume3

聚合 演示

需求:

kk01上的flume1监控文件/opt/software/flume/files1/.*files.*

kk02上的flume2监控某个端口的数据流

flume1、flume2的数据都发送给kk03上的flume3,flume3将数据打印至控制台

1)kk01上编写配置文件

在${FLUME_HOME}/job目录下创建group3目录,创建flume1.conf

[nhk@kk01 job]$ mkdir group3
[nhk@kk01 job]$ cd group3
[nhk@kk01 group3]$ touch flume1.conf

flume1.conf配置文件内容如下

# 1.定义组件
a1.sources = r1
a1.sinks = k1
a1.channels = c1 

# 2.配置sources
a1.sources.r1.type = TAILDIR
a1.sources.r1.filegroups = f1
a1.sources.r1.filegroups.f1 = /opt/software/flume/files1/.*files.*  
a1.sources.r1.positionFile = /opt/software/flume/taildir_position3.json

# 3.配置channels
a1.channels.c1.type = memory
a1.channels.c1.capacity = 1000
a1.channels.c1.transactionCapacity = 100

# 4.配置sinks
a1.sinks.k1.type = avro
a1.sinks.k1.hostname = kk03
a1.sinks.k1.port = 4141

# 5.组装
a1.sources.r1.channels = c1 
a1.sinks.k1.channel = c1

2)kk02上编写配置文件

在${FLUME_HOME}/job目录下创建group3目录,创建flume2.conf

[nhk@kk02 job]$ mkdir group3
[nhk@kk02 job]$ cd group3/
[nhk@kk02 group3]$ touch flume2.conf

flume2.conf配置文件内容如下

# 1.定义组件
a1.sources = r1
a1.sinks = k1
a1.channels = c1

# 2.配置sources
a1.sources.r1.type = netcat
a1.sources.r1.bind = localhost
a1.sources.r1.port = 44444

# 3.配置channels
a1.channels.c1.type = memory
a1.channels.c1.capacity = 1000
a1.channels.c1.transactionCapacity = 100

# 4.配置sinks
a1.sinks.k1.type = avro
a1.sinks.k1.hostname = kk03
a1.sinks.k1.port = 4141

# 5.组装
a1.sources.r1.channels = c1
a1.sinks.k1.channel = c1

3)kk03上编写配置文件

在${FLUME_HOME}/job目录下创建group3目录,创建flume3.conf

[nhk@kk03 job]$ mkdir group3
[nhk@kk03 job]$ cd group3/
[nhk@kk03 group3]$ touch flume3.conf

flume2.conf配置文件内容如下

# 1.定义组件
a1.sources = r1
a1.sinks = k1 
a1.channels = c1 

# 2.配置sources
a1.sources.r1.type = avro
a1.sources.r1.bind = kk03
a1.sources.r1.port = 4141	

# 3.配置channels
a1.channels.c1.type = memory
a1.channels.c1.capacity = 1000
a1.channels.c1.transactionCapacity = 100

# 4.配置sinks
a1.sinks.k1.type = logger

# 5.组装
a1.sources.r1.channels = c1
a1.sinks.k1.channel = c1

4)启动flume

在kk03启动flume3

[nhk@kk03 flume]$ bin/flume-ng agent -n a1 -c conf/ -f job/group3/flume3.conf

在kk01启动flume1

[nhk@kk01 flume]$ bin/flume-ng agent -n a1 -c conf/ -f job/group3/flume1.conf 

在kk02启动flume2

[nhk@kk02 flume]$ bin/flume-ng agent -n a1 -c conf/ -f job/group3/flume2.conf 

5)测试

在kk02启动nc工具,并发送数据

[nhk@kk02 ~]$ nc localhost 44444
hello world
OK

结果在kk03上的flume3打印了如下日志

2023-06-23 05:14:32,423 (SinkRunner-PollingRunner-DefaultSinkProcessor) [INFO - org.apache.flume.sink.LoggerSink.process(LoggerSink.java:95)] Event: { header
s:{} body: 68 65 6C 6C 6F 20 77 6F 72 6C 64                hello world }

在kk01上的 /opt/software/flume/files1目录追加内容

[nhk@kk01 files1]$ echo 666 >> files1 

结果在kk03上的flume3打印了如下日志

2023-06-23 05:16:30,483 (SinkRunner-PollingRunner-DefaultSinkProcessor) [INFO - org.apache.flume.sink.LoggerSink.process(LoggerSink.java:95)] Event: { header
s:{} body: 36 36 36                                        666 }

此致,该演示完成

Flume 的可高性保证

Flume的一些组件(如 Spooling Directory Source、File Channel)能保证 Agent出问题后数据不丢失。

负载均衡

​ Source 里的 Event 流经 Channel,进入 Sink组,在Sink组内部根据负载均衡算法(round_robin、random)选择sink,后续可以选择不同机器上的Agent实现负载均衡。

故障转移

​ 配置一组Sink,这组Sink组成一个Sink故障转移处理器,当有一个Sink处理失败,Flume将这个Sink放到一个地方,设定冷却时间,待其可以正常处理Event时再取回。

​ Event 通过一个 Channel 流向一个Sink组,在Sink组内部根据优先级选择具体的Sink,失败后再转向另一个Sink。

Flume 拦截器

​ Flume拦截器负责修改和删除 Event,每个Source 可以配置多个拦截器,形参拦截器链(Interpretor Chain)。

​ Flume自带的拦截器主要有时间拦截器(TimeStamp Interceptor)、主机拦截器(Host Intercepor)、静态拦截器(Static Interceptor)

  • 时间拦截器主要用于在 Event 的 Header 中加入时间戳
  • 主机拦截器主要用于在 Event 的 Header 中加入主机名(或者主机地址)
  • 静态拦截器主要用于在 Event 的 Header 中加入固定的 Key 和 Value。

你可能感兴趣的:(flume,flume)