记录一下日志采集框架flume的相关内容,flume是由Cloudera开发,后面贡献给了Apache,是一个分布式的、稳定的,用于日志采集、汇聚和传输的系统,现在用的一般是1.x版本,老版本的因为用得少暂时不考虑。
基本概念
包括agent和event。
Agent
以下是数据流模型图,source+channel+sink组成一个agent,一个agent完成从数据源获取数据,到写出数据到目的地,它本质上是一个java进程。
(1)source,source不是数据采集的发源地,是用于对接数据源的,它是从发源地如web server采集源数据,或者从上一个agent sink的结果采集数据,可以理解为水管。
(2)channel,是一个数据临时存储区域,当数据在sink被消费后,才从channel删除,这样可以保证数据传输的安全和可靠性,可以理解成一个蓄水水箱。
(3)sink,将数据发给目的地,可以是hdfs,也可以是下一个agent采集数据的发源地等,可以理解为水轮头。
Event
Event是flume中传输的基本单位,一条消息会被封装成event对象,它本质上是一个json字符串,携带headers、body和消息体,消息是放在body里的,ETL写自定义拦截器时需要跟这两个打交道。
Flume复杂流动
上图是一个单级流动,除了单级流动flume还支持多级流动、扇入流动和扇出流动。
多级流动
如图是两个agent串联的多级流动,上一级的sink和下一级的source均是avro类型,上一级配置sink指向下一级souce的hostname和port。
扇入流
如图是扇入类型,官方文档有介绍一种应用场景,即几百个web服务器生产的日志,都汇入到十几个附属于存储子系统上的agent上。上图也可以配置完成,在agent1、agent2和agent3的sink中,指向agent4中source的hostname和port,最后agent4经过一个channel,将event统一写出到hdfs。
扇出流
如图是扇出类型,可以复制或多路输出,如果是复制则一个event将进入到三个channel,如果是多路输出,则event只会进入到符合条件的channel。这些都是通过agent中来配置的。
各大组件配置使用
三大组件包括source、channel和sink,每个组件根据业务场景的不同,会有多种type供选择,如何配置它们可以参考官方文档http://flume.apache.org/releases/content/1.9.0/FlumeUserGuide.html,注意版本,新版本会添加一些功能,如type为taildir的source,就是1.7更新后添加,1.7版本也是生产环境比较稳定的版本并且和1.x的老版本兼容。
接下来就是写配置文件了,不同的场景配置文件大同小异,主体结构是一样的,都是配置三大组件,并连接source和channel,channel和sink。
source
souce根据type的不同有多种选择,包括netcat、avro、thrift、exec、jms、spooling、directory、sequence generator、syslog、http、legacy、taildir自定义等。
(1)netcat source
netcat用于监听一个指定的端口的tcp请求数据,配置如下。
# 配置Agent,需要给Agent指定一个名字,取名a1 # 需要绑定Source,并给Source取名 a1.sources=s1 # 绑定channel,并且给Channel取名 a1.channels=c1 # 绑定Sink,并且给Sink取名 a1.sinks=k1 # 配置netcat source a1.sources.s1.type=netcat a1.sources.s1.bind=hadoop01 a1.sources.s1.port=8090 # 配置Channel 暂时用memory a1.channels.c1.type=memory # 内存空间有限,限定最大10000条event a1.channels.c1.capacity=10000 # 批次sink的数量是100条 a1.channels.c1.transactionCapacity=100 # 配置sink,日志的形式打印 a1.sinks.k1.type=logger # 将Source和Channel进行绑定 a1.sources.s1.channels=c1 # 将Channel和Sink进行绑定 a1.sinks.k1.channel=c1
配置好后,需启动agent,并加载上面的配置文件,
# 配置文件写在data目录下,启动flume
# agent用于启动一个agent a1
# -c 用于加载原生配置
# -f 用户加载指定配置
# info,console代表控制台打印日志 [root@hadoop01 /home/software/apache-flume-1.6.0-bin/data]# ../bin/flume-ng agent -n a1 -c ../conf/ -f ./basic.conf -Dflume.root.logger=info,console ...略 # 另外开启一个窗口,通过nc访问 [root@hadoop01 ~]# nc hadoop01 8090 hello buddy OK # 原窗口监听到nc 2020-01-21 10:03:38,709 (lifecycleSupervisor-1-3) [INFO - org.apache.flume.source.NetcatSource.start(NetcatSource.java:164)] Created serverSocket:sun.nio.ch.ServerSocketChannelImpl[/192.168.200.140:8090] # 接收到日志信息,可以看出event由headers、body和消息体组成 2020-01-21 10:05:04,970 (SinkRunner-PollingRunner-DefaultSinkProcessor) [INFO - org.apache.flume.sink.LoggerSink.process(LoggerSink.java:94)] Event: { headers:{} body: 68 65 6C 6C 6F 20 62 75 64 64 79 hello buddy }
(2)avro source
avro source接收的是avro序列化后的数据,然后反序列化后继续传输, 可以实现以上三种复杂流动。接下来使用avro client发送数据,测试接收。
# 配置agent a1.sources=s1 a1.channels=c1 a1.sinks=k1 # 配置avro source a1.sources.s1.type=avro # 监听本机 a1.sources.s1.bind=0.0.0.0 a1.sources.s1.port=8090 # 配置channel,使用memory类型 a1.channels.c1.type=memory a1.channels.c1.capacity=1000 a1.channels.c1.transactionCapacity=100 # 配置sink,使用logger打印日志 a1.sinks.k1.type=logger # 连接channel和sink、source a1.sources.s1.channels=c1 a1.sinks.k1.channel=c1
配置好后,先启动flume agent,加载上面的文件。
[root@hadoop01 /home/software/apache-flume-1.6.0-bin/data]# ../bin/flume-ng agent -n a1 -c ../conf/ -f ./avro.conf -Dflume.root.logger=info,console
然后另开一个窗口启动avro client,并测试能否接收到序列化后的数据。数据需要提前准备好。
# avrotest.txt是提前准备好的文件 [root@hadoop01 /home/software/apache-flume-1.6.0-bin/data]# ../bin/flume-ng avro-client -H 0.0.0.0 -p 8090 -F ../testdata/avrotest.txt -c ../conf/
查看启动了agent的控制台,发现接收到了反序列化后的数据。
(3) exec source
exec source接收的是linux命令的结果,如cat、tail和ls等命令,并将结果可以输出。
# 配置agent a1.sources=s1 a1.channels=c1 a1.sinks=k1 # 配置exec source,这两个参考flume官方文档来配,是必选项 a1.sources.s1.type=exec a1.sources.s1.command=ls / # 配置channel a1.channels.c1.type=memory a1.channels.c1.capacity=10000 a1.channels.c1.transactionCapacity=100 # 配置sink a1.sinks.k1.type=logger # 连接source channel和sink a1.sources.s1.channels=c1 a1.sinks.k1.channel=c1
配置好后,先启动flume agent,加载上面的文件。
[root@hadoop01 /home/software/apache-flume-1.6.0-bin/data]# ../bin/flume-ng agent -n a1 -c ../conf/ -f ./exec.conf -Dflume.root.logger=info,console
执行完后可以看出输出了'ls /'的结果。
(4)sequence generator source
这个source主要用于测试,可以产生不断自增的序列数字, 从0起步。如果数据流目的地最终能收到序列数字就说明能打通。
# 配置agent a1.sources=s1 a1.channels=c1 a1.sinks=k1 # 配置sequence generator source,必选项只有一个 a1.sources.s1.type=seq # 配置channel a1.channels.c1.type=memory a1.channels.c1.capacity=10000 a1.channels.c1.transactionCapacity=100 # 配置sink a1.sinks.k1.type=logger # 连接source、channel和sink a1.sources.s1.channels=c1 a1.sinks.k1.channel=c1
配置好后,先启动flume agent,加载上面的文件。
[root@hadoop01 /home/software/apache-flume-1.6.0-bin/data]# ../bin/flume-ng agent -n a1 -c ../conf/ -f ./seqsouce.conf -Dflume.root.logger=info,console
启动后,开始快速产生序列,打印到控制台。
(5)spooling directory source
spooling directory source比较常用,它可以监视某个目录下的新文件,并写入到channel,写完后的文件会重命名添加complete标签到文件名。另外建议文件名字不要重复,可以防止报错。
# 配置agent a1.sources=s1 a1.channels=c1 a1.sinks=k1 # 配置spooling directory source,需要指定监视的目录/home/flumedata a1.sources.s1.type=spooldir a1.sources.s1.spoolDir=/home/flumedata # 配置channel a1.channels.c1.type=memory a1.channels.c1.capacity=10000 a1.channels.c1.transactionCapacity=100 # 配置sink a1.sinks.k1.type=logger # 连接source、channel和sink a1.sources.s1.channels=c1 a1.sinks.k1.channel=c1
配置好后,先启动flume agent,加载上面的文件。
[root@hadoop01 /home/software/apache-flume-1.6.0-bin/data]# ../bin/flume-ng agent -n a1 -c ../conf/ -f ./spool.conf -Dflume.root.logger=info,console
给指定目录扔一个文件。
[root@hadoop01 /home]# mv book.txt ./flumedata/
查看agent的控制台,发现获取到了新增的内容。
(6)HTTP source
http source 可以接受http post和get请求,其中get请求不稳定只能用在测试,另外考虑到get请求一次只能携带64KB的数据如果是图片上传也不适用,因此一般处理post请求。http请求通过一个可插拔的'handler'(需实现HTTPSourceHandler接口)可以转换为event数组。另外参考官方文档,所有的这些转换后的event会在一次事务中提交到channel,可以提高file channel的效率。如果使用handler出现异常,会抛出http异常状态码。
可插拔handler有JSONHandler和BlobHandler。
6-a.JSONHandler可以处理json格式的event,支持UTF-8、UTF-16和UTF-32字符集,它将接受一个event数组,将其转换为flume event,转换的编码基于request请求中指定的字符集,如果没指定就默认是UTF-8,json格式的event如下所示,是一个json数组,每个json包含headers和body。
[{
"headers" : {
"timestamp" : "434324343",
"host" : "random_host.example.com"
},
"body" : "random_body"
},
{
"headers" : {
"namenode" : "namenode.example.com",
"datanode" : "random_datanode.example.com"
},
"body" : "really_random_body"
}]
6-b.BlobHandler一般用于上传的请求,如上传PDF或JPG文件,它可以将其转换为包含请求参数和BLOB(binary large object)的event。但是这种处理器不宜处理非常大的对象因为内存大小有限,内存一次性储存一整个BLOB将可能空间不够。
下面简单的配置一个http source。
# 配置一个agent a1.sources=s1 a1.channels=c1 a1.sinks=k1 # 配置http source a1.sources.s1.type=http a1.sources.s1.port=8848 # 配置channel a1.channels.c1.type=memory a1.channels.c1.capacity=10000 a1.channels.c1.transactionCapacity=100 # 配置sink a1.sinks.k1.type=logger # 连接source、channel和sink a1.sources.s1.channels=c1 a1.sinks.k1.channel=c1
配置好后,先启动flume agent,加载上面的文件。
[root@hadoop01 /home/software/apache-flume-1.6.0-bin/data]# ../bin/flume-ng agent -n a1 -c ../conf/ -f ./httpsource.conf -Dflume.root.logger=info,console
另开一个窗口,使用curl来发送post请求,也可以使用火狐浏览器发送post请求。
# -X指定请求类型 # -d 指定JSON格式数据 [root@hadoop01 /home]# curl -X POST -d '[{"headers":{"year":"2019"},"body":"hello my brother"},{"headers":{"year":"2020"},"body":"wedding,money and bigdata"}]' http://0.0.0.0:8848
agent控制台有接收到http请求的数据,并转换为event格式。
channel
channel也有多种选择,一般有memory、file、jdbc和内存溢出channel。
(1)memory channel
这种channel前面用的都是它,特点就是速度快,但是会丢数据,适用于可以丢失数据的场景。
一般一条event日志大小1-2kb,生产环境下,capacity一般配置10万-30万,这样占用大小是超过1G,而transactionCapacity一般配置1000-3000。
# 指定type必须为memory a1.channels.c1.type=memory # channel中能存储的event的最大数量 a1.channels.c1.capacity=10000 # channel一次事务中从source获取或者向sink发送的event数量 a1.channels.c1.transactionCapacity=100
(2)file channel
这种channel的特点是速度会慢,但是可靠性有保证,数据不易丢失。
a1.sources=s1 a1.channels=c1 a1.sinks=k1 a1.sources.s1.type=netcat a1.sources.s1.bind=hadoop01 a1.sources.s1.port=8088 # channel类型必须为file a1.channels.c1.type=file # 配置缓存文件目录 a1.channels.c1.dataDirs=/home/check_dir # 配置检查点文件目录 a1.channels.c1.checkpointDir=/home/checkpoint_dir a1.sinks.k1.type=logger a1.sources.s1.channels=c1 a1.sinks.k1.channel=c1
配置好后,先启动flume agent,加载上面的文件。
[root@hadoop01 /home/software/apache-flume-1.6.0-bin/data]# ../bin/flume-ng agent -n a1 -c ../conf/ -f ./filechannel.conf -Dflume.root.logger=info,console
另外开启一个窗口,发送tcp数据到配置的ip和端口。
[root@hadoop01 /home]# nc hadoop01 8088 messi herry ronald
agent上会打印日志,只是速度会明显慢于memory channel。
flume会自动生成配置的文件夹。
刚才tcp数据,会写入到check_dir目录下的log-1文件里。
(3)jdbc channel
这个channel,从flume1.3版本开始没啥大的改动,采用derby为缓存,限于derby数据库的劣势如单线程、产生大量文件夹、局部查询的特点,比较鸡肋,可以不管。
# channel指定类型为jdbc
a1.channels.c1.type = jdbc
(4)内存溢出channel
这个channel有点类似shuffle中的环形缓冲区的意思,目前2020年1月份官方提示还是处在实验阶段,可以尝鲜,生产不宜使用。
# 配置内存溢出channel a1.channels.c1.type = SPILLABLEMEMORY a1.channels.c1.memoryCapacity = 10000 a1.channels.c1.overflowCapacity = 1000000 a1.channels.c1.byteCapacity = 800000 a1.channels.c1.checkpointDir = /mnt/flume/checkpoint a1.channels.c1.dataDirs = /mnt/flume/data
sink
sink根据目的地的不同,可以选择logger、file roll、hdfs、avro、thrift、ipc、file、null、hbase、solr、kafka、自定义等。
(1)logger sink
前面的sink都是这种类型,打印的info级别的日志到控制台,便于调试。从上面的日志结果来看,它的展示内容不完整,因为默认只展示16个字节数据,可以配置。
# 配置sink logger a1.sinks.k1.type=logger # 配置展示log的字节数,默认16个 a1.sinks.k1.maxBytesToLog=20
(2)file roll sink
这个sink会每隔一段时间将采集的日志写入到文件,可以用于文件合并。
# 配置agent a1.sources=s1 a1.channels=c1 a1.sinks=k1 # 配置source a1.sources.s1.type=netcat a1.sources.s1.port=8088 a1.sources.s1.bind=hadoop01 # 配置memory a1.channels.c1.type=memory a1.channels.c1.capacity=10000 a1.channels.c1.transactionCapacity=100 # 配置sink为file roll a1.sinks.k1.type=file_roll # 数据储存目录 a1.sinks.k1.sink.directory=/home/fileroll_sink # 采集数据生成新文件的频率,默认30,如果设置0将只写入到一个文件中 a1.sinks.k1.sink.rollInterval=3600 a1.sources.s1.channels=c1 a1.sinks.k1.channel=c1
配置好后,先启动flume agent,加载上面的文件。
[root@hadoop01 /home/software/apache-flume-1.6.0-bin/data]# ../bin/flume-ng agent -n a1 -c ../conf/ -f ./filerollsink.conf -Dflume.root.logger=info,console
这个需要提前新建一个文件夹。另外开启一个窗口发送tcp数据后,会将数据写入到文件夹目录下。
# 发送tcp数据 [root@hadoop01 /home/fileroll_sink]# nc hadoop01 8088 hello buddy OK # 产生日志文件 [root@hadoop01 /home/fileroll_sink]# ll total 4 -rw-r--r--. 1 root root 12 Jan 21 16:31 1579595421201-1 # 查看,内容就是tcp发送的数据 [root@hadoop01 /home/fileroll_sink]# cat 1579595421201-1 hello buddy
(3)hdfs sink
这个sink是将数据写入到hdfs,实际较为常用。目前支持text和序列化文件格式并且可使用压缩算法,能通过设置rollInterval、rollSize和rollCount的值,自定义hdfs关闭和创建新文件的周期。flume的数据sink保存到hdfs,支持分区或分桶的方式保存文件,这个比较常见的就是根据时间戳来分区保存文件。如果保存文件的文件名需要用日期来命名区分,这个时候可以使用转义字符(可参考官方文档),sink到hdfs后目录或文件名会自动根据转义字符来替换名字,常见的就是根据日期命名了,如'%Y-%m-%d'就是按照年月日命名。使用flume sink到hdfs需要安装hdfs,flume需要用到hadoop的相关jar包来和hdfs集群来通信。
以下是一个简单的hdfs sink的示例。
# 配置agent a1.sources=s1 a1.channels=c1 a1.sinks=k1 # 配置source,为测试方便使用netcat a1.sources.s1.type=netcat a1.sources.s1.port=8848 a1.sources.s1.bind=hadoop01 # 配置sink a1.channels.c1.type=memory a1.channels.c1.capacity=10000 a1.channels.c1.transactionCapacity=100 # 配置hdfs sink a1.sinks.k1.type=hdfs # 配置文件保存地址 a1.sinks.k1.hdfs.path=hdfs://hadoop01:9000/flume # 文件滚动生成的时间间隔 a1.sinks.k1.hdfs.rollInterval=3600 # 默认为sequenceFile,文件类型设置为DataSteam,输出不支持压缩。如果要支持压缩使用CompressedStream a1.sinks.k1.hdfs.fileType=DataStream # 连接source、channel和sink a1.sources.s1.channels=c1 a1.sinks.k1.channel=c1
配置好后,先启动flume agent,加载上面的文件。
[root@hadoop01 /home/software/apache-flume-1.6.0-bin/data]# ../bin/flume-ng agent -n a1 -c ../conf/ -f ./hdfssink.conf -Dflume.root.logger=info,console
另外开启一个窗口,发送tcp数据。
# 发送数据
[root@hadoop01 /home/fileroll_sink]# nc hadoop01 8848 buddy this is hdfs sink
OK
agent控制台可以看到sink成功。
查看文件内容,发现已写入文件,并且有一个.tmp后缀,因为文件正在in use,当文件关闭后后缀将移除。
# 查看内容 [root@hadoop01 /home/software/apache-flume-1.6.0-bin/data]# hadoop fs -cat /flume/FlumeData.1579607270857 20/01/21 19:50:47 WARN util.NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable # 就是发送的tcp数据 buddy this is hdfs sink
(4)avro
avro sink是实现复杂数据流的基础,上一个agent发送avro序列化数据后,下一个agent通过source再接收avro序列化数据,再反序列化后继续传输。
4-1.实现多级流动
按照下面配置,可以实现多级流动,最后将文件写入到hdfs。
agent1在hadoop1上设置,设置如下。
# 多级流动 a1.sources=s1 a1.channels=c1 a1.sinks=k1 a1.sources.s1.type=netcat a1.sources.s1.port=8090 a1.sources.s1.bind=hadoop01 a1.channels.c1.type=memory a1.channels.c1.capacity=10000 a1.channels.c1.transactionCapacity=100 a1.sinks.k1.type=avro a1.sinks.k1.hostname=hadoop02 a1.sinks.k1.port=8888 a1.sources.s1.channels=c1 a1.sinks.k1.channel=c1
agent2在hadoop2上设置,设置如下。
# 多级流动 a1.sources=s1 a1.channels=c1 a1.sinks=k1 a1.sources.s1.type=avro a1.sources.s1.port=8888 a1.sources.s1.bind=hadoop02 a1.channels.c1.type=memory a1.channels.c1.capacity=10000 a1.channels.c1.transactionCapacity=100 a1.sinks.k1.type=avro a1.sinks.k1.hostname=hadoop03 a1.sinks.k1.port=8888 a1.sources.s1.channels=c1 a1.sinks.k1.channel=c1
agent3在hadoop3上设置,设置如下。
# 多级流动 a1.sources=s1 a1.channels=c1 a1.sinks=k1 a1.sources.s1.type=avro a1.sources.s1.port=8888 a1.sources.s1.bind=hadoop03 a1.channels.c1.type=memory a1.channels.c1.capacity=10000 a1.channels.c1.transactionCapacity=100 a1.sinks.k1.type=hdfs a1.sinks.k1.hdfs.path=hdfs://hadoop01:9000/flume a1.sinks.k1.hdfs.rollInterval=3600 a1.sinks.k1.hdfs.fileType=DataStream a1.sources.s1.channels=c1 a1.sinks.k1.channel=c1
按照hadoop03、hadoop02和hadoop01的顺序启动agent,然后在hadoop01使用nc来发送一条tcp数据,测试hdfs是否有写入。
# 发送的数据
[root@hadoop01 /home/fileroll_sink]# nc hadoop01 8090 this is multiflow test by avro OK
检查发现hdfs有写入成功,并且内容ok。
[root@hadoop01 /home/fileroll_sink]# hadoop fs -ls /flume 20/01/21 20:25:52 WARN util.NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable Found 2 items -rw-r--r-- 1 root supergroup 24 2020-01-21 19:49 /flume/FlumeData.1579607270857 # 写入成功 -rw-r--r-- 3 root supergroup 31 2020-01-21 20:25 /flume/FlumeData.1579616023729.tmp # 查看发现写入tcp数据内容 [root@hadoop01 /home/fileroll_sink]# hadoop fs -cat /flume/FlumeData.1579616023729.tmp 20/01/21 20:26:11 WARN util.NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable this is multiflow test by avro
4-2.实现扇出
按照下面配置,可以实现一个扇出案例。
agent1在hadoop1上设置,设置如下,使用了两种不同的channel。
# 扇出流动 a1.sources=s1 a1.channels=c1 c2 a1.sinks=k1 k2 a1.sources.s1.type=netcat a1.sources.s1.port=8888 a1.sources.s1.bind=hadoop01 # c1 a1.channels.c1.type=memory a1.channels.c1.capacity=10000 a1.channels.c1.transactionCapacity=100 # c2 a1.channels.c2.type=file a1.channels.c2.dataDirs=/home/flumedata # k1 a1.sinks.k1.type=avro a1.sinks.k1.hostname=hadoop02 a1.sinks.k1.port=8888 # k2 a1.sinks.k2.type=avro a1.sinks.k2.hostname=hadoop03 a1.sinks.k2.port=8888 # c1 a1.sources.s1.channels=c1 c2 a1.sinks.k1.channel=c1 a1.sinks.k2.channel=c2
agent2在hadoop2上设置,设置如下。
# 扇出流动 a1.sources=s1 a1.channels=c1 a1.sinks=k1 a1.sources.s1.type=avro a1.sources.s1.port=8888 a1.sources.s1.bind=hadoop02 # c1 a1.channels.c1.type=memory a1.channels.c1.capacity=10000 a1.channels.c1.transactionCapacity=100 # k1 a1.sinks.k1.type=logger # c1 a1.sources.s1.channels=c1 a1.sinks.k1.channel=c1
agent3在hadoop3上设置,设置如下。
# 扇出流动 a1.sources=s1 a1.channels=c1 a1.sinks=k1 a1.sources.s1.type=avro a1.sources.s1.port=8888 a1.sources.s1.bind=hadoop03 # c1 a1.channels.c1.type=memory a1.channels.c1.capacity=10000 a1.channels.c1.transactionCapacity=100 # k1 a1.sinks.k1.type=logger # c1 a1.sources.s1.channels=c1 a1.sinks.k1.channel=c1
按照hadoop03、hadoop02和hadoop01的顺序启动agent,然后在hadoop01使用nc来发送一条tcp数据,测试hadoop02和hadoop03控制台是否有打印。
# 发送tcp数据 [root@hadoop01 /home/software/apache-flume-1.6.0-bin/data]# nc hadoop01 8888 this is fan out flow test by avro OK
发送完成后,hadoop02和hadoop03的控制台都有输出。
4-3.实现扇入
按照下面配置,可以实现一个扇入案例。
agent1在hadoop1上设置。
# 扇入流动 a1.sources=s1 a1.channels=c1 a1.sinks=k1 a1.sources.s1.type=netcat a1.sources.s1.port=8888 a1.sources.s1.bind=0.0.0.0 a1.channels.c1.type=memory a1.channels.c1.capacity=10000 a1.channels.c1.transactionCapacity=100 a1.sinks.k1.type=avro a1.sinks.k1.hostname=hadoop03 a1.sinks.k1.port=8888 a1.sources.s1.channels=c1 a1.sinks.k1.channel=c1
agent2在hadoop2上设置,和hadoop01一样。
agent3在hadoop3上设置。
# 扇入流动 a1.sources=s1 a1.channels=c1 a1.sinks=k1 a1.sources.s1.type=avro a1.sources.s1.port=8888 a1.sources.s1.bind=hadoop03 a1.channels.c1.type=memory a1.channels.c1.capacity=10000 a1.channels.c1.transactionCapacity=100 a1.sinks.k1.type=logger a1.sources.s1.channels=c1 a1.sinks.k1.channel=c1
按照hadoop03、hadoop02和hadoop01的顺序启动agent,然后hadoop01和hadoop02节点发送tcp数据包,测试hadoop03能否接收到。
# hadoop01发送tcp数据 [root@hadoop01 /home/software/apache-flume-1.6.0-bin/data]# nc hadoop01 8888 from 01 node OK # hadoop02发送tcp数据 [root@hadoop02 /home/software/apache-flume-1.6.0-bin/data]# nc hadoop02 8888 from 02 node OK
hadoop01和hadoop02发送tcp数据后,hadoop03都接收到了。
souce selector
选择器selector是souce的子组件,要理解它可以从上面的扇出来理解。
当不设置时,选择器默认是replicating模式,即复制模式,source获取到数据后会复制一份,然后发送到两个节点。
如果要根据event消息头的不同分发到不同的节点,需要将选择器设置为multiplexing,即路由模式,并在配置文件中指定区分的基准。
以下是使用了选择器的souce,修改上面扇出类型的agent1的配置。
# 扇出流动 a1.sources=s1 a1.channels=c1 c2 a1.sinks=k1 k2 # source使用http,好模拟 a1.sources.s1.type=http a1.sources.s1.port=8888 # 配置选择器,选择类型为多路复用模式,即路由模式 a1.sources.s1.selector.type=multiplexing # 选择器识别的消息头 a1.sources.s1.selector.header=name # 消息头为messi的发给c1 channel # 消息头为ronald的发给c2 channel a1.sources.s1.selector.mapping.messi=c1 a1.sources.s1.selector.mapping.ronald=c2 # 没有匹配到的发送c2 # a1.sources.s1.selector.default=c2 # c1 a1.channels.c1.type=memory a1.channels.c1.capacity=10000 a1.channels.c1.transactionCapacity=100 # c2 a1.channels.c2.type=file a1.channels.c2.dataDirs=/home/flumedata # sink1 a1.sinks.k1.type=avro a1.sinks.k1.hostname=hadoop02 a1.sinks.k1.port=8888 # sink2 a1.sinks.k2.type=avro a1.sinks.k2.hostname=hadoop03 a1.sinks.k2.port=8888 # 连接source、channel和sink,有几个sink就有几个channel a1.sources.s1.channels=c1 c2 a1.sinks.k1.channel=c1 a1.sinks.k2.channel=c2
参考扇出,按照hadoop03、hadoop02和hadoop01的顺序启动agent,只是hadoop01加载的是新的配置文件。启动后hadoop01上curl模拟发送post请求,分别携带不同的请求头信息,测试是否发送不同的节点。
# 启动后发送post请求 [root@hadoop01 /home/software/apache-flume-1.6.0-bin/data]# curl -X POST -d '[{"headers":{"name":"messi"},"body":"i am messi"}]' http://hadoop01:8888 [root@hadoop01 /home/software/apache-flume-1.6.0-bin/data]# curl -X POST -d '[{"headers":{"name":"ronald"},"body":"i am ronald"}]' http://hadoop01:8888
可以看到,两个post请求分别发送到了不同的节点。
souce interceptor
拦截器也是source的子组件,它可以对数据进行改变。通过拦截器,可以根据自己的需求来过滤数据,并且拦截器也可以构成拦截器链,类似spring中的拦截器,也是责任链模式。
(1)timestamp interceptor
配置时间戳拦截器,event消息头里将包含毫秒为单位的时间戳,以下是配置示例。
a1.sources=s1 a1.channels=c1 a1.sinks=k1 a1.sources.s1.type=netcat a1.sources.s1.bind=0.0.0.0 a1.sources.s1.port=8090 # 配置时间戳拦截器,可以配置多个,空格隔开 a1.sources.s1.interceptors=i1 a1.sources.s1.interceptors.i1.type=timestamp a1.channels.c1.type=memory a1.channels.c1.capacity=10000 a1.channels.c1.transactionCapacity=100 a1.sinks.k1.type=logger a1.sources.s1.channels=c1 a1.sinks.k1.channel=c1
启动agent后,发送tcp数据包,发现event消息头里有时间。
上面hdfs sink有提到可以按照日期来命名文件或文件夹,这个可以配合拦截器,实现按天保存数据。
a1.sources=s1 a1.channels=c1 a1.sinks=k1 a1.sources.s1.type=netcat a1.sources.s1.bind=0.0.0.0 a1.sources.s1.port=8090 # 配置时间戳拦截器 a1.sources.s1.interceptors=i1 a1.sources.s1.interceptors.i1.type=timestamp a1.channels.c1.type=memory a1.channels.c1.capacity=10000 a1.channels.c1.transactionCapacity=100 a1.sinks.k1.type=hdfs # 按天输出到文件夹,使用转义字符 a1.sinks.k1.hdfs.path=hdfs://hadoop01:9000/flume/date=%Y-%m-%d a1.sinks.k1.hdfs.rollInterval=3600 a1.sinks.k1.hdfs.fileType=DataStream a1.sources.s1.channels=c1 a1.sinks.k1.channel=c1
运行agent后,发现在hdfs按照日期生成了目录,文件按天存放在目录。
(2)host interceptor
配置后,会在event的headers里展示运行agent的主机ip或名字。
a1.sources=s1 a1.channels=c1 a1.sinks=k1 a1.sources.s1.type=netcat a1.sources.s1.bind=0.0.0.0 a1.sources.s1.port=8090 a1.sources.s1.interceptors=i1 i2 a1.sources.s1.interceptors.i1.type=timestamp # 配置host interceptor a1.sources.s1.interceptors.i2.type=host # 如果想显示主机名,设置false # a1.sources.s1.interceptors.i2.useIP=false a1.channels.c1.type=memory a1.channels.c1.capacity=10000 a1.channels.c1.transactionCapacity=100 a1.sinks.k1.type=logger a1.sources.s1.channels=c1 a1.sinks.k1.channel=c1
启动agent后,发送tcp数据包,发现event消息头里有ip。
(3)static interceptor
如果要自定添加一个key-value到消息头,就可以使用它。
a1.sources=s1 a1.channels=c1 a1.sinks=k1 a1.sources.s1.type=netcat a1.sources.s1.bind=0.0.0.0 a1.sources.s1.port=8090 a1.sources.s1.interceptors=i1 i2 i3 a1.sources.s1.interceptors.i1.type=timestamp a1.sources.s1.interceptors.i2.type=host # 配置static interceptor,可以配置多个,再添加个i4即可 a1.sources.s1.interceptors.i3.type=static a1.sources.s1.interceptors.i3.key=name a1.sources.s1.interceptors.i3.value=messi a1.channels.c1.type=memory a1.channels.c1.capacity=10000 a1.channels.c1.transactionCapacity=100 a1.sinks.k1.type=logger a1.sources.s1.channels=c1 a1.sinks.k1.channel=c1
启动agent后,发送tcp数据包,发现event消息头里有自定义的key-value。
(4)UUID interceptor
这个就是让消息头里添加一个全局唯一标识的id。
a1.sources=s1 a1.channels=c1 a1.sinks=k1 a1.sources.s1.type=netcat a1.sources.s1.bind=0.0.0.0 a1.sources.s1.port=8090 a1.sources.s1.interceptors=i1 i2 i3 i4 a1.sources.s1.interceptors.i1.type=timestamp a1.sources.s1.interceptors.i2.type=host a1.sources.s1.interceptors.i3.type=static a1.sources.s1.interceptors.i3.key=name a1.sources.s1.interceptors.i3.value=clyang # 添加UUID interceptor,有$Builder,说明这使用了内部静态类 a1.sources.s1.interceptors.i4.type=org.apache.flume.sink.solr.morphline.UUIDInterceptor$Builder a1.channels.c1.type=memory a1.channels.c1.capacity=10000 a1.channels.c1.transactionCapacity=100 a1.sinks.k1.type=logger a1.sources.s1.channels=c1 a1.sinks.k1.channel=c1
启动agent后,发送tcp数据包,发现event消息头里有UUID。
(5)search and replace interceptor
如果需要替换和搜索某些字符串,可以使用这个拦截器,需要配合正则表达式。
a1.sources=s1 a1.channels=c1 a1.sinks=k1 a1.sources.s1.type=netcat a1.sources.s1.bind=0.0.0.0 a1.sources.s1.port=8090 a1.sources.s1.interceptors=i1 i2 i3 i4 i5 a1.sources.s1.interceptors.i1.type=timestamp a1.sources.s1.interceptors.i2.type=host a1.sources.s1.interceptors.i3.type=static a1.sources.s1.interceptors.i3.key=name a1.sources.s1.interceptors.i3.value=clyang a1.sources.s1.interceptors.i4.type=org.apache.flume.sink.solr.morphline.UUIDInterceptor$Builder # 搜索和替换拦截器 a1.sources.s1.interceptors.i5.type=search_replace # 正则 a1.sources.s1.interceptors.i5.searchPattern=[0-9] # 匹配后替换后的字符 a1.sources.s1.interceptors.i5.replaceString=XJ a1.channels.c1.type=memory a1.channels.c1.capacity=10000 a1.channels.c1.transactionCapacity=100 a1.sinks.k1.type=logger a1.sources.s1.channels=c1 a1.sinks.k1.channel=c1
另开一个窗口,发送一条tcp数据,里面的数字会被替换掉。
[root@hadoop01 /home/software/apache-flume-1.6.0-bin/data]# nc hadoop01 8090 20 i miss you OK
控制台的20已结替换成XJ了。
(6)regex filter interceptor
它可以解析事件体,根据正则表达式来筛选。
a1.sources=s1 a1.channels=c1 a1.sinks=k1 a1.sources.s1.type=netcat a1.sources.s1.bind=0.0.0.0 a1.sources.s1.port=8090 # 配置regex filter interceptor a1.sources.s1.interceptors=i1 a1.sources.s1.interceptors.i1.type=regex_filter # 写正则表达式 a1.sources.s1.interceptors.i1.regex=[A-Z] # 将匹配的event排除,不采集 a1.sources.s1.interceptors.i1.excludeEvents=true a1.channels.c1.type=memory a1.channels.c1.capacity=10000 a1.channels.c1.transactionCapacity=100 a1.sinks.k1.type=logger a1.sources.s1.channels=c1 a1.sinks.k1.channel=c1
另开一个窗口,发送一条tcp数据,如果消息全是大写字母,agent控制台将不打印日志,小写字母将采集。
# 发送两条,一条全大写,一条全小写 [root@hadoop01 /home/software/apache-flume-1.6.0-bin/data]# nc hadoop01 8090 REGEX INTERCEPTOR OK regex interceptor OK
agent控制台接收结果,大写的没有接收到,小写的打印到了控制台。
sink processor
sink processor是用于处理sink组的,因为单个sink存在宕机的可能,为了实现故障转移,将需要配置sink processor。另外sink processor还可以实现sink的负载均衡。
(1)default sink processor
它只能处理一个sink,无需配置sink组,按照普通的source→channel→sink来配置就可以。
(2)failover sink processor
它可以实现故障转移,使用它需要配置一个sink组。failover sink processor内部维护着一个具有优先级的sink列表,可以保证可用的event被有效处理。当一个sink处理event失败,将降级到pool中,并且设定了CD时间,需CD时间达到才能再次使用,一旦再次使用时成功处理了一条event,它将升级到live pool。
另外,可以在配置文件中为sink设置优先级,用数字来表示,数字越大优先级越高,注意数字不能重复。如果某个sink处理event失败,sink组里剩下的选取一条优先级最高的继续处理,如果sink组里的sink没有指定优先级,则默认按照配置的顺序来选择。
下面简单的设置一个,基于扇出,因为扇出需要配置多个sink,将这些sink配置放到一个sink group并配置sink优先级,测试发送数据哪个agent将接收到,且一个sink关闭能否切换到另外一个sink来处理数据,示意图如下,蓝色框代表一个agent,绿色框代表一个sink group。
agent1配置在hadoop01,另外两个agent分别配置在hadoop02和hadoop03。
hadoop01上配置。
# 扇出流动 a1.sources=s1 a1.channels=c1 c2 a1.sinks=k1 k2 # 指定sinkgroup组的名字 a1.sinkgroups=g1 # 组下有两个sink a1.sinkgroups.g1.sinks=k1 k2 # 组的处理器类型为failover a1.sinkgroups.g1.processor.type=failover # 配置sink的优先级,k1对应的优先级要高 a1.sinkgroups.g1.processor.priority.k1=8 a1.sinkgroups.g1.processor.priority.k2=2 a1.sinkgroups.g1.processor.maxpenalty=5000 # 配置source a1.sources.s1.type=netcat a1.sources.s1.port=8888 a1.sources.s1.bind=node01 # channel1 为memory a1.channels.c1.type=memory a1.channels.c1.capacity=10000 a1.channels.c1.transactionCapacity=100 # channel2 为file a1.channels.c2.type=file a1.channels.c2.dataDirs=/home/flumedata # sink1 到hadoop02 a1.sinks.k1.type=avro a1.sinks.k1.hostname=hadoop02 a1.sinks.k1.port=8888 # sink2 到hadoop03 a1.sinks.k2.type=avro a1.sinks.k2.hostname=hadoop03 a1.sinks.k2.port=8888 # 连接source、channel和sink a1.sources.s1.channels=c1 c2 a1.sinks.k1.channel=c1 a1.sinks.k2.channel=c2
hadoop02和hadoop03的配置和上面扇出的配置一样。
分别启动hadoop03、hadoop02和hadoop01,并且在hadoop01不段发送tcp数据包,查看hadoop02和hadoop03的日志输出情况。
[root@hadoop01 /home/software/apache-flume-1.6.0-bin/data]# nc hadoop01 8888 # 连续发送3条 this from 01 OK this from 01 OK this from 01 OK # hadoop02关闭agent后再次发送 tis from 01 OK
刚开始连续发送3条,发现只有hadoop02的agent有日志输出,hadoop03没有,因为hadoop02上的sink优先级更高。
关闭hadoop02的agent后,hadoop01上的agent会报错sink k1不可用,进行故障转移到可用的sink,即k2,而k2在hadoop03节点。
查看hadoop03,发现故障转移前和后的所有消息都接收到了,一共四条,即实现了故障转移。
(3)load balancing sink processor
它可以实现sink组内活动sink的负载均衡,有轮询(round-robin)和随机(random)两种策略。基于上面扇出的例子,只需修改hadoop01上配置文件就可以实现测试,以轮询为例。
a1.sources=s1 a1.channels=c1 a1.sinks=k1 k2 # 指定sinkgroup组的名字 a1.sinkgroups=g1 a1.sinkgroups.g1.sinks=k1 k2 # 配置为load_balance a1.sinkgroups.g1.processor.type=load_balance # 轮询策略 a1.sinkgroups.g1.processor.selector=round_robin a1.sources.s1.type=netcat a1.sources.s1.port=8888 a1.sources.s1.bind=0.0.0.0 # c1 a1.channels.c1.type=memory a1.channels.c1.capacity=10000 a1.channels.c1.transactionCapacity=100 # k1 a1.sinks.k1.type=avro a1.sinks.k1.hostname=hadoop02 a1.sinks.k1.port=8888 # k2 a1.sinks.k2.type=avro a1.sinks.k2.hostname=hadoop03 a1.sinks.k2.port=8888 # 连接source、channel和sink a1.sources.s1.channels=c1 a1.sinks.k1.channel=c1 a1.sinks.k2.channel=c2
也同样启动三台agent后,hadoop01节点上不断发送tcp数据,发现hadoop02和hadoop03上轮流收到信息,测试ok。
hadoop02接受部分信息。
hadoop03接受部分信息。
自定义source
可以自定义一个source,模仿上文的sequence generator source。
(1)代码部分。
package com.boe; import org.apache.flume.Context; import org.apache.flume.Event; import org.apache.flume.EventDrivenSource; import org.apache.flume.channel.ChannelProcessor; import org.apache.flume.conf.Configurable; import org.apache.flume.event.EventBuilder; import org.apache.flume.source.AbstractSource; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * 自定义一个source,用来实现仿造sequenceSource * 可以实现EventDrivenSource或者PollableSource * EventDrivenSource 需要自己提供线程将数据放入到channel,可控 * PollableSource PollableSource提供线程将数据放入到channel,不可控 * Configurable 用于获取配置文件中的指定属性 */ public class AuthSource extends AbstractSource implements EventDrivenSource,Configurable{ private int step=1; private ExecutorService es; //获取配置文件中指定步长参数step @Override public void configure(Context context) { //属性名为步长 String s = context.getString("step"); if(s!=null){ //判断step是否是正数 int i = Integer.parseInt(s); if(i<=0){ throw new IllegalArgumentException(); }else{ step=i; } } } @Override public synchronized void start() { //启动5个线程 es= Executors.newFixedThreadPool(5); //获取到一个channel ChannelProcessor cp=super.getChannelProcessor(); //提交任务完成自增过程 es.submit(new Add(step,cp)); } @Override public synchronized void stop() { es.shutdown(); } } class Add implements Runnable{ private int step; private ChannelProcessor cp; public Add(int step,ChannelProcessor cp){ this.step=step; this.cp=cp; } @Override public void run() { int i=0; while(true){ i+=step; //将数据给到channel Map<String,String> map=new HashMap<>(); map.put("time",System.currentTimeMillis()+""); //第一个参数为body,第二个为headers Event event= EventBuilder.withBody((i + "").getBytes(),map); cp.processEvent(event); } } }
(2)打包。
在IDEA中将其打包,将其添加到flume的lib目录下。如果是maven项目直接package打包即可,普通项目需要先添加一个artifact,然后build artifact后打包。注意最好删除extract,否则jar包扔进去,启动agent会报错。
打包后,在IDEA下使用terminal,将文件远程拷贝到flume的lib目录下即可。
(3)写配置文件,测试。
自定义source后,需要在配置文件中指定类的全路径名。
# 配置agent a1.sources=s1 a1.channels=c1 a1.sinks=k1 # 配置自定义source a1.sources.s1.type=com.boe.AuthSource # 步长为10 a1.sources.s1.step=10 # 配置channel a1.channels.c1.type=memory a1.channels.c1.capacity=10000 a1.channels.c1.transactionCapacity=100 # 配置sink a1.sinks.k1.type=logger # 连接source、channel和sink a1.sources.s1.channels=c1 a1.sinks.k1.channel=c1
配置好后,先启动flume agent,加载上面的文件。
[root@hadoop01 /home/software/apache-flume-1.6.0-bin/data]# ../bin/flume-ng agent -n a1 -c ../conf/ -f ./auto.conf -Dflume.root.logger=info,console
agent控制台可以看到按照步长10来打印了。
自定义interceptor
ETL数据清洗中,为满足个性化的拦截需求,这个时候可以自定义拦截器,同样将jar包扔到flume的lib目录下生效。下面实现一个自定义拦截器,实现根据日志内容关键字清洗数据,然后保存到hdfs的功能。
(1)需要自定义拦截器,实现Interceptor接口。
package com.boe; import org.apache.flume.Context; import org.apache.flume.Event; import org.apache.flume.interceptor.Interceptor; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.List; /** * 自定义拦截器,实现根据关键字清洗数据 */ public class ETLInterceptor implements Interceptor{ //可以不需要写 @Override public void initialize() { } //拦截数据方法 @Override public Event intercept(Event event) { //获取event中body数据 byte[] body = event.getBody(); String log = new String(body, Charset.forName("UTF-8")); //body中包含以下字符,过滤掉 if(!log.contains("ass")&&!log.contains("fuck")&&!log.contains("bitch")){ return event; } return null; } //如果event是一个列表的情况,这个方法需要调用上面的方法 @Override public List<Event> intercept(List<Event> list) { ArrayList<Event> events=new ArrayList<>(); for (Event event:list){ //调用上面方法判断 Event event1=intercept(event); if(event1!=null){ events.add(event1); } } return events; } //这个可以不写 @Override public void close() { } //大类里面创建一个静态类 public static class Builder implements Interceptor.Builder{ //重写这个方法即可 @Override public Interceptor build() { return new ETLInterceptor(); } @Override public void configure(Context context) { } } }
写完代码后,打成jar包扔到flume的lib目录下。
(2)写一个配置文件,引入自定义拦截器。
# agent a1.sources=s1 a1.channels=c1 a1.sinks=k1 # source a1.sources.s1.type=netcat a1.sources.s1.port=8848 a1.sources.s1.bind=hadoop01 # 使用自定义拦截器 a1.sources.s1.interceptors=i1 a1.sources.s1.interceptors.i1.type=com.boe.ETLInterceptor$Builder # channel a1.channels.c1.type=memory a1.channels.c1.capacity=10000 a1.channels.c1.transactionCapacity=100 # sink a1.sinks.k1.type=hdfs # 目录是myinterceptor a1.sinks.k1.hdfs.path=hdfs://hadoop01:9000/myinterceptor a1.sinks.k1.hdfs.rollInterval=3600 a1.sinks.k1.hdfs.fileType=DataStream # 连接source、channel和sink a1.sources.s1.channels=c1 a1.sinks.k1.channel=c1
(3)启动agent,测试发送tcp数据包,可以看出,需要过滤的关键字信息被过滤掉了,只展示正常的消息。
# 注意里面有敏感字,测试能否被interceptor清洗掉 [root@hadoop01 /home/software/apache-flume-1.6.0-bin/data]# nc hadoop01 8848 messi is best football player OK ronald? OK fuck OK ass OK sorry OK
清洗成功,hdfs只写入了正常的信息。
以上,就是flume的使用入门,后面再单独补充taildir source和kafka sink,先记录这么多。
参考博文:
(1)flume官方文档