Flume日志收集系统详解
- 一、Flume简介
- 1.1 Flume特点
- 1.1.1 可靠性
- 1.1.2 可恢复性
- 1.2 Flume架构
- 二、Flume原理
- 2.1 主要组件
- 2.2 工作流程
- 三、flume创建实例
- 3.1 Exec Source 类型
- 3.2 spooling directory source类型
- 3.3 Taildir Source类型
- 3.4 Netcat Source (TCP)类型
- 3.4 将读取文件上传至hdfs上
- 3.5 Java自定义拦截器,将读取数据上传至hdfs
- 3.7 flume读取数据至kafka
一、Flume简介
Apache Flume是一个 分布式的、可靠的、可用的 数据收集系统 ,它可以有效地收集、聚合和移动大量的 日志数据 ,这些数据可以从许多不同的来源转移到一个集中的数据存储中。
Apache Flume不仅仅限于日志数据聚合。由于数据源是可定制的,所以Flume可用于传输大量事件数据,包括但不限于网络流量数据、社交媒体生成的数据、电子邮件消息以及几乎所有可能的数据源。
1.1 Flume特点
1.1.1 可靠性
Flume 的核心是把 数据从数据源收集过来,再送到目的地 。为了保证输送一定成功,在送到目的地之前,会 先缓存数据 待数据真正到达目的地后,删除自己缓存的数据 。Flume 使用 事务性的方式保证传送 Event整个过程的可靠性 。 Sink 必须在Event 被存入 Channel 后,或者,已经被传达到下一站 Agent里,又或者,已经被存入外部数据目的地之后,才能把 Event 从 Channel 中 remove 掉。这样数据流里的 Event 无论是在一个 agent 里还是多个 agent 之间流转,都能保证可靠,因为以上的事务保证了 Event 会被成功存储起来。比如 Flume支持在本地保存一份文件 Channel 作为备份,而 Memory Channel 将 Event存在内存队列里,速度快,但丢失的话无法恢复。
1.1.2 可恢复性
Events在 通道中执行,由该通道管理从失败中恢复 。 Flume支持由本地文件系统支持的持久文件通道。还有一个内存通道,它只是简单地将事件存储在内存队列中,速度更快,但是当代理进程死亡时,仍然留在内存通道中的任何事件都无法恢复。
1.2 Flume架构
Flume架构包括三部分: Client、 Agent、 Event
①Event: 事件,是数据传输的基本单元 。 它具有字节有效载荷和一组可选的字符串属性 ,通常对应一行数据。 实际包含一个 Map结构的 headers和一个 byte[]类型的 body属性。 Event是数据流的数据对象,而 Flume数据流( Data Flow) 描述了数据从产生、传输、处理并最终写入目标的一条路径。
②Agent: 代理,是一个独立的JVM进程,包含三个组件(Source、Channel、Sink),事件通过组件从外部源流向下一个目标。
③Client: 客户端,数据产生的地方,如Web服务器。
注: Flume以一个或多个Agent部署运行,Flume数据流模型(架构图)如下图所示:
二、Flume原理
2.1 主要组件
Agent组件包括:包括: Source、 SourceRunner、 Interceptor、 Channel、ChannelSelector、ChannelProcessor、 Sink SinkRunner、 SinkProcessor、 SinkSelector,其中 Source、Channel 、 Sink为核心组件,各组件作用如下:
- Source : 是负责接收数据到 Flume Agent的 组件,用来获取 Event 并写入 Channel。
- SourceRunner : 负责启动 Source,一个 SourceRunner包含一个 Source对象。
- Interceptor: 拦截器,是简单的插件式组件,设置在 Source和 Channel之间。Source接收到的事件 Event,在写入Channel之前,拦截器都可以进行转换或者删除这些事件。每个拦截器只处理同一个 Source接收到的事件,可以自定义拦截器。
- Channel: 位于 Source和 Sink之间的缓冲区, 中转 Event 的一个临时存储,保存有 Source 组件传递过来的 Event,可以认为是一个队列。 Channel允许 Source和 Sink运作在不同的速率上。 Channel是线程安全的,可以同时处理几个 Source的写入操作和几个 Sink的读取操作。
- ChannelSelector: 选择器,作用是为 Source选择下游的 Channel。有两种选择方式, 复制和多路复用 。复制 是把 Source中传递过来的 Event复制给所有对应的下游的Channel。多路复用 是把 Source传递过来的 Event按照不同的属性传递到不同的下游 Channel中去。
- ChannelProcessor: 通过 ChannelSelector获取到 Channels后,如何发送 Event到
Channel。 一个 Source对象包含一个 ChannelProcessor对象,一个 ChannelProcessor对象包含多个 Interceptor对象和一个 ChannelSelector对象。 - Sink: 从 Channel 中读取并移除 Event,将 Event 传递到 Flow Pipeline 中的下一个 Agent 或者其他存储系统。 Sink不断地轮询 Channel中的事件且批量地移除它们 ,并将这些事件批量写入到存储或索引系统、或者被发送到另一个 Flume Agent。Sink是完全事务 性 的。 在从 Channel批量删除数据之前,每个 Sink用Channel启动一个事务。批量 事件 一旦成功写出到存储系统或下一个Flume Agent Sink就利用 Channel提交事务。事务 一旦 被提交,该 Channel从 自己 的内部缓冲区删除事件。
- SinkRunner: 负责启动 Sink。在 Agent启动时,会同时启动 Channel SourceRunner
SinkRunner - SinkProcessor: Flume提供 FailoverSinkProcessor和 LoadBalancingSinkProcesso,一个是失效备援,一个是负载均衡,那么 SinkProcessor不同子类的存在就是为了实现不同的分配操作和策略,而 sink的 start()通常是启动线程去执行消费操作。
- SinkSelector: LoadBalancingSinkProcessor包含 SinkSelector,会根据 SinkSelector在 SinkGroup(逻辑上的一组 Sink)中选择 Sink并启动。
2.2 工作流程
三、flume创建实例
在flume安装的根目录/conf目录下创建文件job:,在该文件夹下存放自定义的flume配置文件
mkdir /opt/install/flume160/conf/job
3.1 Exec Source 类型
执行Linux指令,并按照指令返回结果,如 “tail-f”
示例: 在job目录下创建 exec.conf 文件,编辑内容如下:
a1.sources = s1
a1.sinks = sk1
a1.channels = c1
#设置source类型为exec
a1.sources.s1.type = exec
a1.sources.s1.command = tail -f /opt/dataFile/flume-0817/exectest.txt
#source和channel连接
a1.sources.s1.channels = c1
a1.channels.c1.type = memory
#指定sink
a1.sinks.sk1.type = logger
#sink和channel进行连接
a1.sinks.sk1.channel = c1
a1.channels.c1.capacity = 1000
a1.channels.c1.transactionCapacity = 100
在flume根目录执行:
./bin/flume-ng agent --name a1 --conf conf/ --conf-file conf/job/exec.conf -Dflume.root.logger=INFO,console
在 /opt/dataFile/flume-0817/exectest.txt 文件中写入数据,flume 即可开始运行,执行相应读写操作
注: 在exectest.txt尾部添加内容:
echo hahaha >> /opt/dataFile/flume-0817/exectest.txt
3.2 spooling directory source类型
从磁盘文件夹中获取文件数据,可避免重启或者发送失败后数据丢失,还可用于监控文件夹新文件
示例: 在job目录下创建 events-flume-logger.conf 文件,编辑内容如下:
events.sources = eventsSource
events.channels = eventsChannel
events.sinks = eventsSink
events.sinks.eventsSink.type = logger
events.sources.eventsSource.type = spooldir
#需先创建目录: /opt/dataFile/flumeFile/events 用于存放需要读取的.csv文件
events.sources.eventsSource.spoolDir = /opt/dataFile/flumeFile/events
events.sources.eventsSource.deserializer = LINE
events.sources.eventsSource.deserializer.maxLineLength = 32000
#正则匹配需要读的文件名
events.sources.eventsSource.includePattern = events_[0-9]{4}-[0-9]{2}-[0-9]{2}.csv
events.channels.eventsChannel.type = file
#需先创建目录:/optdataFile/flumeFile/checkpoint/events 设置检查点
events.channels.eventsChannel.checkpointDir = /optdataFile/flumeFile/checkpoint/events
#需先创建目录: /opt/dataFile/flumeFile/data/events 存放Channel数据
events.channels.eventsChannel.dataDirs = /opt/dataFile/flumeFile/data/events
events.sources.eventsSource.channels = eventsChannel
events.sinks.eventsSink.channel = eventsChannel
在flume根目录执行:
./bin/flume-ng agent --name events --conf conf/ --conf-file conf/job/events-flume-logger.conf -Dflume.root.logger=INFO,console
将要读取的数据 events.csv 拷贝至 /opt/dataFile/flumeFile/events 目录下,flume 即可开始运行,执行相应读写操作
拷贝数据,注意修改文件格式:
install events.csv /opt/dataFile/flumeFile/events/events_2020-08-17.csv
3.3 Taildir Source类型
Taildir Source监控指定的一些文件,并在检测到新的一行数据产生的时候实时地读取它们,如果新的一行数据还没写完, Taildir Source会等到这行写完后再读取。 Taildir Source可以从任意指定的位置开始读取文件,可以实现断点续读,如果发生宕机,会从宕机前记录的最后读取的位置开始读文件,而不是从首行重新开始读取。 默认情况下,它将从每个文件的第一行开始读取。
文件按照修改时间的顺序来读取。修改时间最早的文件将最先被读取(简单记成:先来先走)。 Taildir Source不重命名、删除或修改它监控的文件。当前不支持读 取二进制文件。只能逐行读取文本文件。注意:Taildir Source目前不能运行在 windows系统上。
示例: 在job目录下创建 tailDir.conf 文件,编辑内容如下:
a1.sources = s1
a1.sinks = sk1
a1.channels = c1
#设置source类型为TAILDIR
a1.sources.s1.type = TAILDIR
a1.sources.s1.filegroups = f1 f2
#配置filegroups的两个数据源 f1 f2
a1.sources.s1.filegroups.f1 = /opt/dataFile/flume-0817/tail_1/example.log
a1.sources.s1.filegroups.f2 = /opt/dataFile//flume-0817/tail_2/.*log.*
#指定position的位置, 读取文件会记录位置
a1.sources.s1.positionFile = /opt/dataFile//flume-0817/tail_position/taildir_position.json
#指定headers
a1.sources.s1.headers.f1.headerKey1 = value1
a1.sources.s1.headers.f2.headerKey1 = value2
a1.sources.s1.headers.f2.headerKey1 = value3
a1.sources.s1.fileHeader = true
#source和channel连接
a1.sources.s1.channels = c1
a1.channels.c1.type = memory
#指定sink
a1.sinks.sk1.type = logger
#sink和channel进行连接
a1.sinks.sk1.channel = c1
a1.channels.c1.capacity = 1000
a1.channels.c1.transactionCapacity = 100
在flume根目录执行:
./bin/flume-ng agent --name a1 --conf conf/ --conf-file conf/job/tailDir.conf -Dflume.root.logger=INFO,console
在f1、f2目录中创建一些数据,,flume 即可开始运行,执行相应读写操作
3.4 Netcat Source (TCP)类型
一个类似netcat的源,它监听给定的端口并将每行文本转换成一个事件。就像 nc -k -l [主机 ][端口 ]。换句话说,它打开指定的端口并侦听数据。期望提供的数据是换行分隔的文本 。每行文本被转换成一个 Flume事件并通过连接的通道发送。
示例: 在job目录下创建 netcat-flume-logger.conf 文件,编辑内容如下:
a1.sources = r1
a1.sinks = k1
a1.channels = c1
a1.sources.r1.type = netcat
a1.sources.r1.bind = localhost
a1.sources.r1.port = 44444
a1.sinks.k1.type = logger
a1.channels.c1.type = memory
a1.channels.c1.capacity = 1000
a1.channels.c1.transactionCapacity = 100
a1.sources.r1.channels = c1
a1.sinks.k1.channel = c1
在flume根目录执行:
./bin/flume-ng agent --name a1 --conf conf/ --conf-file conf/job/netcat-flume-logger.conf -Dflume.root.logger=INFO,console
测试: 需先下载一个网络工具netcat,Linux默认情况下是没有安装的,安装过程如下:
yum install -y nc
#列出telnet相关的安装包
yum list telnet*
#安装telnet服务
yum install telnet-server
#安装telnet客户端
yum install telnet.*
输入端口号44444,测试连接:
telnet localhost 44444
3.4 将读取文件上传至hdfs上
示例: 读取文件user_friends.csv,上传至hdfs /data/userFriends目录下,在job目录下创建 file-flume-hdfs.conf 文件,编辑内容如下:
user_friends.sources = userFriendsSource
user_friends.channels = userFriendsChannel
user_friends.sinks = userFriendsSink
user_friends.sources.userFriendsSource.type = spooldir
#需先创建目录: /opt/dataFile/flumeFile/userFriends 用于存放需要读取的.csv文件
user_friends.sources.userFriendsSource.spoolDir = /opt/dataFile/flumeFile/user_friends
user_friends.sources.userFriendsSource.deserializer = LINE
user_friends.sources.userFriendsSource.deserializer.maxLineLength = 600000
#正则匹配需要读的文件名
user_friends.sources.userFriendsSource.includePattern = userfriends_[0-9]{4}-[0-9]{2}-[0-9]{2}.csv
user_friends.channels.userFriendsChannel.type = file
#需先创建目录:/optdataFile/flumeFile/checkpoint/userFriends 设置检查点
user_friends.channels.userFriendsChannel.checkpointDir = /opt/dataFile/flumeFile/checkpoint/userFriends
#需先创建目录: /opt/dataFile/flumeFile/data/userFriends 存放Channel数据
user_friends.channels.userFriendsChannel.dataDirs = /opt/dataFile/flumeFile/data/userFriends
user_friends.sinks.userFriendsSink.type = hdfs
user_friends.sinks.userFriendsSink.hdfs.fileType = DataStream
user_friends.sinks.userFriendsSink.hdfs.filePrefilx = userFriends
user_friends.sinks.userFriendsSink.hdfs.filePrefilx = .csv
user_friends.sinks.userFriendsSink.hdfs.path = hdfs://192.168.206.129:9000/data/userFriends/%Y-%m-%d
user_friends.sinks.userFriendsSink.hdfs.useLocalTimeStamp = true
user_friends.sinks.userFriendsSink.hdfs.batchSize = 640
user_friends.sinks.userFriendsSink.hdfs.rollCount = 0
user_friends.sinks.userFriendsSink.hdfs.rollSize = 6400000
user_friends.sinks.userFriendsSink.hdfs.rollInterval = 30
user_friends.sources.userFriendsSource.channels = userFriendsChannel
user_friends.sinks.userFriendsSink.channel = userFriendsChannel
在flume根目录执行:
./bin/flume-ng agent --name user_friends --conf conf/ --conf-file conf/job/file-flume-hdfs.conf -Dflume.root.logger=INFO,console
将要读取的数据 user_friends.csv 拷贝至 /opt/dataFile/flumeFile/userFriends 目录下,flume 即可开始运行,执行相应读写操作
拷贝数据,注意修改文件格式:
install events.csv /opt/dataFile/flumeFile/userFriends/userfriends_2020-08-17.csv
3.5 Java自定义拦截器,将读取数据上传至hdfs
自定义拦截器实现功能: 读取每行数据,数据以 “spark” 开头时,将文件上传至 hdfs: /data/sparkDemo目录下,否则上传至 hdfs: /data/tmpDemo 目录下
①: IDEA中需先导入Maven依赖:
<dependency>
<groupId>org.apache.flume</groupId>
<artifactId>flume-ng-core</artifactId>
<version>1.6.0</version>
</dependency>
IDEA中创建java文件 InterceptorDemo ,
自定义拦截器代码如下:
package cn.com;
import org.apache.flume.Context;
import org.apache.flume.Event;
import org.apache.flume.interceptor.Interceptor;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class InterceptorDemo implements Interceptor { //继承拦截器接口
private List<Event> addHeaderEvents;
@Override
public void initialize() {
addHeaderEvents = new ArrayList<>();
}
@Override
public Event intercept(Event event) {
Map<String,String> headers = event.getHeaders();
String body = new String(event.getBody());
if(body.startsWith("spark")){
headers.put("type","spark");
}else{
headers.put("type","tmp");
}
return event;
}
@Override
public List<Event> intercept(List<Event> list) {
addHeaderEvents.clear();
for(Event event:list){
addHeaderEvents.add(intercept(event));
}
return addHeaderEvents;
}
@Override
public void close() {
}
//创建一个静态内部类,同过静态内部类加载 InterceptorDemo 对象
public static class Builder implements Interceptor.Builder{
@Override
public Interceptor build() {
return new InterceptorDemo();
}
@Override
public void configure(Context context) {
}
}
}
②: 将以上java文件,打jar包上传至 /opt/install/flume160/lib/ 目录
③: Linux中在job目录下创建 netcat-flume-logerhdfs.conf 文件,编辑内容如下:
a1.sources = r1
a1.channels = c1 c2
a1.sinks = k1 k2
a1.sources.r1.type = netcat
a1.sources.r1.bind = localhost
a1.sources.r1.port = 44444
#设置拦截器,注意设置拦截器所在jar包位置: cn.com.InterceptorDemo$Builder ,用$连接
a1.sources.r1.interceptors = i1
a1.sources.r1.interceptors.i1.type = cn.com.InterceptorDemo$Builder
#设置选择器
a1.sources.r1.selector.type = multiplexing
a1.sources.r1.selector.header = type
a1.sources.r1.selector.mapping.gree = c1
a1.sources.r1.selector.mapping.lijia = c2
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
a1.sinks.k1.type = hdfs
a1.sinks.k1.hdfs.fileType = DataStream
a1.sinks.k1.hdfs.filePrefix = spark
a1.sinks.k1.hdfs.fileSuffix = .csv
a1.sinks.k1.hdfs.path = hdfs://192.168.206.129:9000/data/sparkdemo/%Y-%m-%d
a1.sinks.k1.hdfs.useLocalTimeStamp = true
a1.sinks.k1.hdfs.batchSize = 640
a1.sinks.k1.hdfs.rollCount = 0
a1.sinks.k1.hdfs.rollSize = 100
a1.sinks.k1.hdfs.rollInterval = 3
a1.sinks.k2.type = hdfs
a1.sinks.k2.hdfs.fileType = DataStream
a1.sinks.k2.hdfs.filePrefix = tmp
a1.sinks.k2.hdfs.fileSuffix = .csv
a1.sinks.k2.hdfs.path = hdfs://192.168.206.129:9000/data/tmpdemo/%Y-%m-%d
a1.sinks.k2.hdfs.useLocalTimeStamp = true
a1.sinks.k2.hdfs.batchSize = 640
a1.sinks.k2.hdfs.rollCount = 0
a1.sinks.k2.hdfs.rollSize = 100
a1.sinks.k2.hdfs.rollInterval = 3
a1.sources.r1.channels = c1 c2
a1.sinks.k1.channel = c1
a1.sinks.k2.channel = c2
④: 测试连接
telnet localhost 44444
分别输入数据
sparkabc
spark abc 123
abcdefg
123456
sparktest
可以在hdfs端口: http://192.168.206.129:50070/ 查看拦截器是否分类读取上传至 hdfs/data 目录下
3.7 flume读取数据至kafka
例① 将读取的数据直接保存至kafka中:
vi userfriends-flume-kafka.conf ,内容如下:
user_friends.sources = userFriendsSource
user_friends.channels = userFriendsChannel
user_friends.sinks = userFriendsSink
user_friends.sources.userFriendsSource.type = spooldir
user_friends.sources.userFriendsSource.spoolDir = /opt/dataFile/flumeFile/user_friends
user_friends.sources.userFriendsSource.deserializer = LINE
user_friends.sources.userFriendsSource.deserializer.maxLineLength = 60000
user_friends.sources.userFriendsSource.includePattern = userfriends_[0-9]{4}-[0-9]{2}-[0-9]{2}.csv
user_friends.channels.userFriendsChannel.type = file
user_friends.channels.userFriendsChannel.checkpointDir = /opt/dataFile/flumeFile/checkpoint/userFriends
user_friends.channels.userFriendsChannel.dataDir = /opt/dataFile/flumeFile/data/userFriends
user_friends.sinks.userFriendsSink.type = org.apache.flume.sink.kafka.KafkaSink
user_friends.sinks.userFriendsSink.batchSize = 640
user_friends.sinks.userFriendsSink.brokerList = 192.168.206.129:9092
user_friends.sinks.userFriendsSink.topic = user_friends_raw
user_friends.sources.userFriendsSource.channels = userFriendsChannel
user_friends.sinks.userFriendsSink.channel = userFriendsChannel
例② 使用flume自带的拦截器过滤掉首行,如下图:
想要过滤掉首行字段:
vi users-flume-kafka.conf ,内容如下:
users.sources = usersSource
users.channels = usersChannel
users.sinks = usersSink
users.sources.usersSource.type = spooldir
users.sources.usersSource.spoolDir = /opt/dataFile/flumeFile/users
users.sources.usersSource.deserializer = LINE
users.sources.usersSource.deserializer.maxLineLength = 3000
users.sources.usersSource.includePattern = users_[0-9]{4}-[0-9]{2}-[0-9]{2}.csv
#设置自带的拦截器
users.sources.usersSource.interceptors = head_filter
users.sources.usersSource.interceptors.head_filter.type = regex_filter
users.sources.usersSource.interceptors.head_filter.regex = ^user_id*
users.sources.usersSource.interceptors.head_filter.excludeEvents = true
users.channels.usersChannel.type = file
users.channels.usersChannel.checkpointDir = /opt/dataFile/flumeFile/checkpoint/users
users.channels.usersChannel.dataDir = /opt/dataFile/flumeFile/data/users
users.sinks.usersSink.type = org.apache.flume.sink.kafka.KafkaSink
users.sinks.usersSink.batchSize = 640
users.sinks.usersSink.brokerList = 192.168.206.129:9092
users.sinks.usersSink.topic = users
users.sources.usersSource.channels = usersChannel
users.sinks.usersSink.channel = usersChannel
例③ 将读取数据同时保存到 kafka 和 hdfs 中:
vi train-flume-hdfs_kafka.conf ,内容如下:
train.sources = trainSource
train.channels = kafkaChannel hdfsChannel
train.sinks = kafkaSink hdfsSink
train.sources.trainSource.type = spooldir
train.sources.trainSource.spoolDir = /opt/dataFile/flumeFile/train
train.sources.trainSource.deserializer = LINE
train.sources.trainSource.deserializer.maxLineLength = 3000
train.sources.trainSource.trainSource.includePattern = train_[0-9]{4}-[0-9]{2}-[0-9]{2}.csv
#使用自带拦截器过滤首行
train.sources.trainSource.interceptors = head_filter
train.sources.trainSource.interceptors.head_filter.type = regex_filter
train.sources.trainSource.interceptors.head_filter.regex = ^user*
train.sources.trainSource.interceptors.head_filter.excludeEvents = true
train.channels.kafkaChannel.type = file
train.channels.kafkaChannel.checkpointDir = /opt/dataFile/flumeFile/checkpoint/train
train.channels.kafkaChannel.dataDir =
/opt/dataFile/flumeFile/data/train
train.channels.hdfsChannel.type = memory
train.channels.hdfsChannel.capacity = 64000
train.channels.hdfsChannel.transactionCapacity = 16000
train.sinks.kafkaSink.type = org.apache.flume.sink.kafka.KafkaSink
train.sinks.kafkaSink.batchSize = 640
train.sinks.kafkaSink.brokerList = 192.168.206.129:9092
train.sinks.kafkaSink.topic = train
train.sinks.hdfsSink.type = hdfs
train.sinks.hdfsSink.hdfs.fileType = DataStream
train.sinks.hdfsSink.hdfs.filePrefix = train
train.sinks.hdfsSink.hdfs.fileSuffix = .csv
train.sinks.hdfsSink.hdfs.path = hdfs://192.168.206.129:9000/data/train/%Y-%m-%d
train.sinks.hdfsSink.hdfs.useLocalTimeStamp = true
train.sinks.hdfsSink.hdfs.batchSize = 6400
train.sinks.hdfsSink.hdfs.rollCount = 0
train.sinks.hdfsSink.hdfs.rollSize = 64000000
train.sinks.hdfsSink.hdfs.rollInterval = 10
train.sources.trainSource.channels = hdfsChannel kafkaChannel
train.sinks.hdfsSink.channel = hdfsChannel
train.sinks.kafkaSink.channel = kafkaChannel