1 概述
1.1 Flume定义
Flume是Cloudera提供的一个高可用的,高可靠的,分布式的海量日志采集、聚合和传输的系统。Flume基于流式架构,灵活简单。
1.2 Flume优点
① 可以和任意存储进程集成
② 输入的数据速率大于写入目的存储的速率,flume会进行缓冲,减小hdfs的压力。
③ flume中的事务基于channel,使用了两个事务模型(sender + receiver),确保消息被可靠发送。
Flume使用两个独立的事务分别负责从source到channel,以及从channel到sink的事件传递。一旦事务中所有的数据全部成功提交到channel,那么source才认为该数据读取完成。同理,只有成功被sink写出去的数据,才会从channel中移除。
1.3 Flume组成架构
1.4 Flume组件
-
Agent
Agent是一个JVM进程,它以事件的形式将数据从源头送至目的地。
Agent主要有3个部分组成,Source、Channel、Sink。
-
Source
Source是负责接收数据到Flume Agent的组件。
Source组件可以处理各种类型、各种格式的日志数据,包括avro、thrift、exec、jms、spooling directory、netcat、sequence generator、syslog、http、legacy
-
Channel
Channel是位于Source和Sink之间的缓冲区。因此,Channel允许Source和Sink运作在不同的速率上。Channel是线程安全的,可以同时处理几个Source的写入操作和几个Sink的读取操作。
Flume自带两种Channel:Memory Channel和File Channel
①Memory Channel是内存中的队列。Memory Channel在不需要关心数据丢失的情景下适用。如果需要关心数据丢失,那么Memory Channel就不应该使用,因为程序死亡、机器宕机或者重启都会导致数据丢失。
②File Channel将所有事件写到磁盘。因此在程序关闭或机器宕机的情况下不会丢失数据。 -
Sink
Sink不断地轮询Channel中的事件且批量移除它们,并将这些事件批量写入到存储或索引系统、或者被发送到另一个Flume Agent。
Sink是完全事务性的。在从Channel批量删除数据之前,每个Sink用Channel启动一个事务。批量事件一旦成功写出到存储系统或下一个Flume Agent,Sink就利用Channel提交事务。事务一旦被提交,该Channel从自己的内部缓冲区删除事件。
Sink组件目的地包括hdfs、kafka、logger、avro、thrift、ipc、file、null、HBase、solr、自定义。
-
Event
传输单元,Flume数据传输的基本单元,以事件的形式将数据从源头送至目的地。 Event由可选的header和载有数据的一个byte array 构成。Header是容纳了key-value字符串对的HashMap。
2 安装
①Flume官网地址
http://flume.apache.org
②文档查看地址
http://flume.apache.org/FlumeUserGuide.html
③下载地址
http://archive.apache.org/dist/flume/
2.2 安装步骤
准备工作:安装JDK、并且配置环境变量
1)将apache-flume-1.9.0-bin.tar.gz上传到linux的/opt/modules目录下
2)解压apache-flume-1.9.0-bin.tar.gz到/opt/installs目录下
3)将flume/conf下的flume-env.sh.template文件修改为flume-env.sh,并配置flume-env.sh文件
[root@flume0 conf]# pwd
/opt/installs/apache-flume-1.9.0-bin/conf
[root@flume0 conf]# mv flume-env.sh.template flume-env.sh
[root@flume0 conf]# vi flume-env.sh
export JAVA_HOME=/opt/installs/jdk1.8
[root@flume0 apache-flume-1.9.0-bin]# bin/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
3 案例
3.1 实时读取文件到HDFS
1)需求:使用Flume监听整个目录的文件
2)需求分析:
3)实现步骤:
- 创建spooldir-memory-hdfs.conf
[root@hadoop10 job]# pwd /opt/installs/flume1.9/job [root@flume0 job]# touch spooldir-memory-hdfs.conf # 内容如下 a1.sources = r1 a1.channels = c1 a1.sinks = k1 a1.sources.r1.type = spooldir a1.sources.r1.spoolDir = /opt/upload a1.sources.r1.fileSuffix = .done a1.channels.c1.type = memory a1.sinks.k1.type = hdfs a1.sinks.k1.hdfs.path = hdfs://hadoop10:9000/flume/%Y-%m-%d/%H a1.sinks.k1.hdfs.useLocalTimeStamp = true #设置文件类型 a1.sinks.k1.hdfs.fileType = DataStream #是否按照时间滚动文件夹 a1.sinks.k1.hdfs.round = true #多少时间单位创建一个新的文件夹 a1.sinks.k1.hdfs.roundValue = 10 #重新定义时间单位 a1.sinks.k1.hdfs.roundUnit = minute #多久生成一个新的文件 0代表禁用 a1.sinks.k1.hdfs.rollInterval = 0 #设置每个文件的滚动大小 1048576 = 1M a1.sinks.k1.hdfs.rollSize = 1048576 #文件的滚动与 Event 数量无关 a1.sinks.k1.hdfs.rollCount = 0 a1.sources.r1.channels = c1 a1.sinks.k1.channel = c1
对于所有与时间相关的转义序列,Event Header中必须存在以 “timestamp”的key
(除非hdfs.useLocalTimeStamp设置为true,此方法会使用TimestampInterceptor自动添加timestamp)。
a1.sinks.k1.hdfs.useLocalTimeStamp = true
- 启动监控文件夹命令
[root@flume0 flume1.9.0]# bin/flume-ng agent --conf conf --name a1 --conf-file job/spooldir-memory-hdfs.conf -Dflume.root.logger=INFO,console
说明: 在使用Spooling Directory Source时
1) 不要在监控目录中创建并持续修改文件
2) 上传完成的文件会默认以.COMPLETED结尾
3) 被监控文件夹每500毫秒扫描一次文件变动
3. 向upload文件夹中添加文件
[root@flume0 opt]# mkdir /opt/upload
[root@flume0 upload]# touch 1.txt
[root@flume0 upload]# ls
1.txt.done
- 访问hdfs http://hadoop10:50070
3.2 Taildir多目录断点续传
TAILDIR类型的source在读取文件完成后,会接着读取此文件,查看此文件是否有最新的文件内容,如果有最新的文件内容会对此文件的新的内容进行读取
flume 1.7.0推出了taildirSource组件
- 需求:监控多个目录实现断点续传
- 需求分析:
- 实现步骤
- 在flume的job目录下创建一个conf文件
[root@flume0 job]# touch taildir-file-logger.conf
- 在taildir-file-logger.conf文件中编写一个agent
a1.sources = r1
a1.channels = c1
a1.sinks = k1
a1.sources.r1.type = TAILDIR
a1.sources.r1.filegroups = f1 f2
a1.sources.r1.filegroups.f1 = /opt/app/ceshi.log
a1.sources.r1.filegroups.f2 = /opt/app/logs/.*log.*
a1.channels.c1.type = file
#将采集的数据输出到flume的日志中,默认flume1.9/logs/flume.log
#启动的时候配置日志输出位置 -Dflume.root.logger=INFO,console,将会输出到控制台
a1.sinks.k1.type = logger
a1.sources.r1.channels = c1
a1.sinks.k1.channel = c1
- 创建指定的目录和对应的文件
/opt/app/目录下创建 ceshi.log文件 在/opt/app目录下创建logs目录
- 启动agent
[root@flume0 flume1.9.0]# bin/flume-ng agent --conf conf -name a1 --conf-file job/taildir-file-logger.conf -Dflume.root.logger=INFO,console
- 向测试.log文件中追加内容
[root@flume0 app]# echo hello world >> ceshi.log
3.3 单数据源多出口(选择器)
1)需求:
Flume-1监控文件变动,Flume-1将变动内容传递给Flume-2,Flume-2负责存储到HDFS。同时Flume-1将变动内容传递给Flume-3,Flume-3负责输出到Local FileSystem。
2)需求分析:
3)实现步骤:
- 准备工作
在/opt/flume/job目录下创建group1文件夹 [root@flume0 job]# mkdir group1 在/opt目录下创建 datas/flume文件夹 [root@flume0 job]# mkdir -p /opt/datas/flume
- 在group1目录下,创建exec-memory-avro.conf
注:Avro是由Hadoop创始人Doug Cutting创建的一种语言无关的数据序列化和RPC框架。[root@flume0 job]# cd group1 [root@flume0 group1]# touch exec-memory-avro.conf # 添加内容如下 a1.sources = r1 a1.channels = c1 c2 a1.sinks = k1 k2 # 将数据流复制给所有channel replicating复制选择器默认值 a1.sources.r1.selector.type = replicating a1.sources.r1.type = exec a1.sources.r1.command = tail -F /opt/a.log a1.channels.c1.type = memory a1.channels.c2.type = memory a1.sinks.k1.type = avro a1.sinks.k1.hostname = hadoop10 a1.sinks.k1.port = 4545 a1.sinks.k2.type = avro a1.sinks.k2.hostname = hadoop10 a1.sinks.k2.port = 4646 a1.sources.r1.channels = c1 c2 a1.sinks.k1.channel = c1 a1.sinks.k2.channel = c2
注:RPC(Remote Procedure Call)—远程过程调用,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。 - 在group1目录下,创建avro-memory-hdfs.conf
[root@flume0 group1]# touch avro-memory-hdfs.conf # 添加如下内容 a2.sources = r1 a2.sinks = k1 a2.channels = c1 a2.sources.r1.type = avro a2.sources.r1.bind = 0.0.0.0 a2.sources.r1.port = 4545 a2.channels.c1.type = memory a2.sinks.k1.type = hdfs a2.sinks.k1.hdfs.path = hdfs://hadoop10:9000/flume/%Y-%m-%d/%H a2.sinks.k1.hdfs.useLocalTimeStamp = true a2.sinks.k1.hdfs.fileType = DataStream a2.sources.r1.channels = c1 a2.sinks.k1.channel = c1
- 在group1目录下,创建avro-memory-fileroll.conf
注意:输出的本地目录必须是已经存在的目录,如果该目录不存在,并不会创建新的目录。[root@flume0 group1]# touch avro-memory-fileroll.conf # 添加如下内容 a3.sources = r1 a3.channels = c1 a3.sinks = k1 a3.sources.r1.type = avro a3.sources.r1.bind = 0.0.0.0 a3.sources.r1.port = 4646 a3.channels.c1.type = memory a3.sinks.k1.type = file_roll a3.sinks.k1.sink.directory = /opt/app/logs a3.sinks.k1.sink.rollInterval = 0 a3.sources.r1.channels = c1 a3.sinks.k1.channel = c1
- 执行配置文件
[root@flume0 flume1.9.0]# bin/flume-ng agent --conf conf --name a3 --conf-file job/group1/avro-memory-fileroll.conf [root@flume0 flume1.9.0]# bin/flume-ng agent --conf conf --name a2 --conf-file job/group1/avro-memory-hdfs.conf [root@flume0 flume1.9.0]# bin/flume-ng agent --conf conf --name a1 --conf-file job/group1/exec-memory-avro.conf
- 提交测试数据
[root@flume0 opt]# echo hahaha >> a.log
- 检查HDFS上数据
3.4 多数据源汇总
1)需求:实现将多个agent的数据发送给一个agent
2)需求分析:
3)实现步骤:
- 准备工作
在flume/job目录下创建group2文件夹 [root@flume0 job]# mkdir group2
- 在group2目录下,创建agent1.conf
[root@flume0 group2]# touch agent1.conf # 内容如下 a1.sources = r1 a1.channels = c1 a1.sinks = k1 a1.sources.r1.type = exec a1.sources.r1.command = tail -F /opt/app/a.txt a1.channels.c1.type = memory a1.sinks.k1.type = avro a1.sinks.k1.hostname = hadoop10 a1.sinks.k1.port = 6661 a1.sources.r1.channels = c1 a1.sinks.k1.channel = c1
- 在group2目录下,创建agent2.conf
[root@flume0 group2]# touch agent2.conf # 内容如下 a1.sources = r1 a1.channels = c1 a1.sinks = k1 a1.sources.r1.type = exec a1.sources.r1.command = tail -F /opt/app/b.txt a1.channels.c1.type = memory a1.sinks.k1.type = avro a1.sinks.k1.hostname = hadoop10 a1.sinks.k1.port = 6661 a1.sources.r1.channels = c1 a1.sinks.k1.channel = c1
- 在group2目录下,创建agent3.conf
[root@flume0 group2]# touch agent3.conf # 内容如下 a1.sources = r1 a1.channels = c1 a1.sinks = k1 a1.sources.r1.type = avro a1.sources.r1.bind = hadoop10 a1.sources.r1.port = 6661 a1.channels.c1.type = memory a1.sinks.k1.type = logger a1.sources.r1.channels = c1 a1.sinks.k1.channel = c1
- 执行配置文件
[root@flume0 flume1.9.0]# bin/flume-ng agent --conf conf --name a1 --conf-file job/group2/agent3.conf -Dflume.root.logger=INFO,console [root@flume0 flume1.9.0]# bin/flume-ng agent --conf conf --name a1 --conf-file job/group2/agent2.conf [root@flume0 flume1.9.0]# bin/flume-ng agent --conf conf --name a1 --conf-file job/group2/agent1.conf
- 使用echo命令向a.log和b.log追加内容
4 拦截器
flume通过使用Interceptors(拦截器)实现修改和过滤事件的功能。举个栗子,一个网站每天产生海量数据,但是可能会有很多数据是不完整的(缺少重要字段),或冗余的,如果不对这些数据进行特殊处理,那么会降低系统的效率。这时候拦截器就派上用场了。
事件:和产生的数据有关 event对象中body属性包含数据
4.1 内置拦截器
flume内置的拦截器:
由于拦截器一般针对Event的Header进行处理
- event是flume中处理消息的基本单元,由零个或者多个header和正文body组成。
- Header 是 key/value 形式的,可以用来制造路由决策或携带其他结构化信息(如事件的时间戳或事件来源的服务器主机名)。你可以把它想象成和 HTTP 头一样提供相同的功能——通过该方法来传输正文之外的额外信息。
- Body是一个字节数组,包含了实际的内容。
- flume提供的不同source会给其生成的event添加不同的header
4.1.1 timestamp
Timestamp Interceptor拦截器就是可以往event的header中插入关键词为timestamp的时间戳
[root@flume0 job]# mkdir interceptors
[root@flume0 job]# cd interceptors/
[root@flume0 interceptors]# touch demo1-timestamp.conf
#文件内容如下
a1.sources = r1
a1.channels = c1
a1.sinks = k1
a1.sources.r1.type = taildir
a1.sources.r1.filegroups = f1
a1.sources.r1.filegroups.f1 = /opt/app/ceshi.log
#timestamp interceptor 时间戳
a1.sources.r1.interceptors = i1
a1.sources.r1.interceptors.i1.type = timestamp
a1.channels.c1.type = memory
a1.sinks.k1.type = logger
a1.sources.r1.channels = c1
a1.sinks.k1.channel = c1
测试
[root@flume0 interceptors]# echo hello >> /opt/app/ceshi.log
测试结果
2020-04-11 03:54:14,179 (SinkRunner-PollingRunner-DefaultSinkProcessor) [INFO - org.apache.flume.sink.LoggerSink.process(LoggerSink.java:95)] Event: { headers:{timestamp=1586548451701} body: 68 65 6C 6C 6F
4.1.2 host
该拦截器可以往event的header中插入关键词默认为host的主机名或者ip地址(注意是agent运行的机器的主机名或者ip地址)
[root@flume0 interceptors]# touch demo2-host.conf
#文件内容如下
a1.sources = r1
a1.channels = c1
a1.sinks = k1
a1.sources.r1.type = taildir
a1.sources.r1.filegroups = f1
a1.sources.r1.filegroups.f1 = /opt/app/ceshi.log
#host interceptor
a1.sources.r1.interceptors = i1
a1.sources.r1.interceptors.i1.type = host
a1.channels.c1.type = memory
a1.sinks.k1.type = logger
a1.sources.r1.channels = c1
a1.sinks.k1.channel = c1
测试
[root@flume0 interceptors]# echo hello >> /opt/app/ceshi.log
测试结果
2020-04-11 04:04:09,954 (SinkRunner-PollingRunner-DefaultSinkProcessor) [INFO - org.apache.flume.sink.LoggerSink.process(LoggerSink.java:95)] Event: { headers:{host=192.168.150.61} body: 61 61 61 aaa }
4.1.3 regex_filter
Regex Filtering Interceptor拦截器用于过滤事件,筛选出与配置的正则表达式相匹配的事件。可以用于包含事件和排除事件。
常用于数据清洗,通过正则表达式把数据过滤出来。
[root@flume0 interceptors]# touch demo3-regex-filtering.conf
#文件内容如下
a1.sources = r1
a1.channels = c1
a1.sinks = k1
a1.sources.r1.type = taildir
a1.sources.r1.filegroups = f1
a1.sources.r1.filegroups.f1 = /opt/app/ceshi.log
#regex interceptor
a1.sources.r1.interceptors = i1
a1.sources.r1.interceptors.i1.type = regex_filter
#全部是数字的数据
a1.sources.r1.interceptors.i1.regex = ^[0-9]*$
#排除符合正则表达式的数据
a1.sources.r1.interceptors.i1.excludeEvents = true
a1.channels.c1.type = memory
a1.sinks.k1.type = logger
a1.sources.r1.channels = c1
a1.sinks.k1.channel = c1
Regex Filtering 应用场景:排除错误日志
2007-02-13 15:22:26 [com.sms.test.TestLogTool]-[INFO] this is info
2007-02-13 15:22:26 [com.sms.test.TestLogTool]-[ERROR] my exception com.sms.test.TestLogTool.main(TestLogTool.java:8)
多个拦截器可以同时使用,例如:
# 拦截器:作用于Source,按照设定的顺序对event装饰或者过滤
a1.sources.r1.interceptors = i1 i2 i3
a1.sources.r1.interceptors.i1.type = timestamp
a1.sources.r1.interceptors.i2.type = host
a1.sources.r1.interceptors.i3.type = regex_filter
a1.sources.r1.interceptors.i3.regex = ^[0-9]*$
4.2 自定义拦截器
概述:在实际的开发中,一台服务器产生的日志类型可能有很多种,不同类型的日志可能需要发送到不同的分析系统。此时会用到 Flume 拓扑结构中的 Multiplexing 结构,Multiplexing的原理是,根据 event 中 Header 的某个 key 的值,将不同的 event 发送到不同的 Channel中,所以我们需要自定义一个 Interceptor,为不同类型的 event 的 Header 中的 key 赋予不同的值。
演示:我们以端口数据模拟日志,以数字(单个)和字母(单个)模拟不同类型的日志,我们需要自定义 interceptor 区分数字和字母,将其分别发往不同的分析系统(Channel)。
实现步骤
1.创建一个项目,并且引入以下依赖
<dependency>
<groupId>org.apache.flume</groupId>
<artifactId>flume-ng-core</artifactId>
<version>1.9.0</version>
</dependency>
2.自定义拦截器,实现拦截器接口
package com.abc.interceptors;
import org.apache.flume.Context;
import org.apache.flume.Event;
import org.apache.flume.interceptor.Interceptor;
import java.util.List;
public class MyInterceptor implements Interceptor {
@Override
public void initialize() {}
@Override
public Event intercept(Event event) {
byte[] body = event.getBody();
if (body[0] >= 'a' && body[0] <= 'z'){
event.getHeaders().put("type","letter");
}else if (body[0] >= '0' && body[0] <= '9'){
event.getHeaders().put("type","number");
}
return event;
}
@Override
public List<Event> intercept(List<Event> list) {
for (Event event : list) {
intercept(event);
}
return list;
}
@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目录下
4.编写agent,在job目录下的interceptors目录下创建,命名为my.conf
a1.sources = r1
a1.channels = c1 c2
a1.sinks = k1 k2
a1.sources.r1.type = TAILDIR
a1.sources.r1.filegroups = f1
a1.sources.r1.filegroups.f1 = /opt/app/a.txt
a1.sources.r1.interceptors = i1
a1.sources.r1.interceptors.i1.type = com.abc.interceptor.MyInterceptor$Builder
a1.sources.r1.selector.type = multiplexing
a1.sources.r1.selector.header = type
a1.sources.r1.selector.mapping.letter = c1
a1.sources.r1.selector.mapping.number = c2
a1.sources.r1.selector.default = c2
a1.channels.c1.type = memory
a1.channels.c2.type = memory
a1.sinks.k1.type = file_roll
a1.sinks.k1.sink.directory = /opt/app/filter1
a1.sinks.k1.sink.rollInterval = 0
a1.sinks.k2.type = file_roll
a1.sinks.k2.sink.directory = /opt/app/filter2
a1.sinks.k2.sink.rollInterval = 0
a1.sources.r1.channels = c1 c2
a1.sinks.k1.channel = c1
a1.sinks.k2.channel = c2
5.在/opt/app目录下创建t1、t2文件夹
6.测试
[root@flume0 apache-flume-1.9.0-bin]# bin/flume-ng agent --conf conf --name a1 --conf-file job/interceptors/my.conf -Dflume.roogger=INFO,console
5 通道选择器
在event进入到Channel之前,可以使用通道选择器,使指定的Event进入到指定的Channel中
Flume内置两种选择器,replicating 和 multiplexing,如果Source配置中没有指定选择器,那么会自动使用复制Channel选择器。
1.复制选择器 (replicating )
特点:数据同步给多个Channel
参考:企业开发案例 - 单数据源多出口案例(选择器)
a1.sources.r1.selector.type = replicating
2.多路复用选择器 (multiplexing)
特点:数据分流到指定Channel
多路复用选择器的使用前提是 event中包含的headers中 有指定key-value,根据key-value选择将让channel发送指定的channel中
a1.sources.r1.selector.type = multiplexing
a1.sources.r1.selector.header = type
a1.sources.r1.selector.mapping.number = c1
a1.sources.r1.selector.mapping.letter = c2
a1.sources = r1
a1.channels = c1 c2 c3 c4
a1.sources.r1.selector.type = multiplexing
a1.sources.r1.selector.header = state
a1.sources.r1.selector.mapping.CZ = c1
a1.sources.r1.selector.mapping.US = c2 c3
a1.sources.r1.selector.default = c4
6 Flume参数调优
-
source
- 增加source的个数,可以加大source读取文件的能力。
例:当某一个目录产生的文件过多时需要将这个文件目录拆分成多个文件目录,同时配置好多个source以保证source有足够的能力获取到新产生的数据 - batchSize参数决定source一次批量运输到channle的event条数,适当调大该参数可以提高source搬运event到channle时的性能
- 增加source的个数,可以加大source读取文件的能力。
-
channel
type选择memory时性能好,但是可能导致丢失数据。type选择file容错性更好,但是性能稍差。使用file Channel时dataDirs配置多个不同盘下的目录可以提高性能。
Capacity参数决定Channel可容纳最大的event条数。
transactionCapacity 参数决定每次Source往channel里面写的最大event条数和每次Sink从channel里面读的最大event条数。
transactionCapacity需要大于Source和Sink的batchSize参数。
capacity >= transactionCapacity >= (source和sink的batchSize)
-
sink
- 增加Sink的个数,可以增加Sink消费event的能力。
Sink也不是越多越好够用就行,过多的Sink会占用系统资源,造成系统资源不必要的浪费。 - batchSize参数决定Sink一次批量从Channel读取的event条数,适当调大这个参数可以提高Sink从Channel搬出event的性能。
- 增加Sink的个数,可以增加Sink消费event的能力。
7 Flume事务机制
Flume使用两个独立的事务分别负责从Source到Channel,以及从Channel到Sink的事件传递。
比如spooling directory source 为文件的每一行创建一个事件,一旦事务中所有的事件全部传递到Channel且提交成功,那么Source就将该文件标记为完成。
同理事务以类似的方式处理从Channel到Sink的传递过程,如果因为某种原因使得事件无法记录,那么事务将会回滚。且所有的事件都会保持到Channel中,等待重新传递。