文章内容输出来源:拉勾教育大数据开发高薪训练营
集群规划
hadoop1 | hadoop2 | hadoop3 | hadoop4 | hadoop5 | |
---|---|---|---|---|---|
Flume | √ |
Flume安装与配置
前提条件
每台机器都具备jdk环境,下述操作的jdk路径为/opt/install/jdk1.8.0_231
准备安装文件
本篇需要的文件有:
- flume-1.9.0 压缩包
百度网盘-提取码:8848
上传apache-flume-1.9.0-bin.tar.gz
到hadoop2
的/opt/software
并解压到/opt/install/
,然后重命名为flume-1.9.0
# 解压到/opt/install
tar -zxvf apache-flume-1.9.0-bin.tar.gz -C /opt/install/
# 为了方便写配置文件,重命名为flume-1.9.0
mv apache-flume-1.9.0-bin/ flume-1.9.0/
配置
环境变量
vim /etc/profile
##### Flume环境变量 #####
export FLUME_HOME=/opt/install/flume-1.9.0
export PATH=$PATH:$FLUME_HOME/bin
cd $FLUME_HOME/conf
# 将 $FLUME_HOME/conf 下的 flume-env.sh.template 重命名为 flume-env.sh,并添加 jdk 配置
mv flume-env.sh.template flume-env.sh
vim flume-env.sh
export JAVA_HOME=/opt/lagou/servers/jdk1.8.0_231
完毕,Flume的安装配置就这么简单,使用Flume难度也很低,Flume的使用重点在于
- 熟悉各种Source-Channel-Sink的适用场景和作用
- 写配置文件时十二分细心
Flume使用
使用Flume采集日志,日志有两种:启动日志和事件日志
source选用
因为日志都是打到控制台,所以采用taildir source作为采集数据的source,从控制台获取日志数据。配置如下:
a1.sources.r1.type = TAILDIR #指定source的type
a1.sources.r1.positionFile = /data/conf/startlog_position.json # 指定记录读取进度的文件,用于解决断点续传问题
a1.sources.r1.filegroups = f1 # 被监控的文件夹目录集合,这些文件夹下的文件都会被监控,多个用空格分隔,这里只有一个,就叫它f1吧
a1.sources.r1.filegroups.f1 = /data/logs/start/.*log # 被监控文件夹的绝对路径。支持使用正则表达式来匹配文件名
sink选用
因为我们要使用Flume采集数据最终存储到HDFS,所以采用hdfs sink作为Flume的输出。配置如下:
a1.sinks.k1.type = hdfs
a1.sinks.k1.hdfs.path = /user/data/logs/start/%y-%m-%d/
a1.sinks.k1.hdfs.filePrefix = startlog.
# hdfs sink采用滚动生成文件的方式,这里要给它设置滚动策略,如果不设置这个,就会在目标路径生成一大堆小文件,HDFS不喜欢小文件,有以下几种策略:
a1.sinks.k1.hdfs.rollSize = 33554432 # 基于大小,默认1024字节,当文件写入该大小字节后触发滚动创建新文件(0表示不根据文件大小来分割文件)
# a1.sinks.k1.hdfs.rollCount = 0 # 基于事件个数,默认10个,当前文件写入Event达到该数量后触发滚动创建新文件
# a1.sinks.k1.hdfs.rollInterval = 0 # 基于时间,默认30秒
# a1.sinks.k1.hdfs.idleTimeout = 0 # 默认0秒,关闭非活动文件的超时时间(0表示禁用自动关闭文件),单位:秒
a1.sinks.k1.hdfs.minBlockReplicas = 1 # 指定每个HDFS块的最小副本数为1,如果不指定就采用HDFS配置的默认副本数3,这时候也会造成生成很多小文件
a1.sinks.k1.hdfs.batchSize = 1000 # 向HDFS写入内容时每次批量操作的Event数量,默认100,设成500
a1.sinks.k1.hdfs.useLocalTimeStamp = true # 使用日期时间转义符时是否使用本地时间戳(而不是使用 Event header 中自带的时间戳),默认为false,即不使用本地时间,但我们需要本地时间
agent配置
source和sink都配置好了,就差配成一个完整的配置了
a1.sources = r1
a1.sinks = k1
a1.channels = c1
# taildir source
a1.sources.r1.type = TAILDIR
a1.sources.r1.positionFile = /data/conf/startlog_position.json
a1.sources.r1.filegroups = f1
a1.sources.r1.filegroups.f1 = /data/logs/start/.*log
# memory channel
a1.channels.c1.type = memory
a1.channels.c1.capacity = 100000
a1.channels.c1.transactionCapacity = 2000
# hdfs sink
a1.sinks.k1.type = hdfs
a1.sinks.k1.hdfs.path = /user/data/logs/start/%Y-%m-%d/
a1.sinks.k1.hdfs.filePrefix = startlog-
a1.sinks.k1.hdfs.rollSize = 33554432
a1.sinks.k1.hdfs.rollCount = 0
a1.sinks.k1.hdfs.rollInterval = 0
a1.sinks.k1.hdfs.idleTimeout = 0
a1.sinks.k1.hdfs.minBlockReplicas = 1
a1.sinks.k1.hdfs.batchSize = 1000
a1.sinks.k1.hdfs.useLocalTimeStamp = true
# Bind the source and sink to the channel
a1.sources.r1.channels = c1
a1.sinks.k1.channel = c1
启动Flume
flume-ng agent --conf-file /data/conf/flume-log2hdfs1.conf -name a1 -Dflume.root.logger=INFO,console
向/data/logs/start/
目录中放入日志文件,发现Flume日志报错java.lang.OutOfMemoryError: GC overhead limit exceeded
,通过ps -ef | grep flume
查看启动Flume时的详细命令可以看到Flume默认分配的jvm最大堆内存为20MB,这个值太小,需要调整。
vim $FLUME_HOME/conf/flume-env.sh
# 设置jvm最小最大堆内存都是4GB,一来内存足够大,而来最小最大值一致,减少内存动态调整的抖动带来的性能影响
export JAVA_OPTS="-Xms4000m -Xmx4000m -Dcom.sun.management.jmxremote"
# 要想使配置文件生效,还要在命令行中指定配置文件目录,即在原启动命令基础上加上--conf选项指定flume-env.sh目录
flume-ng agent --conf /opt/install/flume-1.9.0/conf --conf-file /data/conf/flume-log2hdfs1.conf -name a1 -Dflume.root.logger=INFO,console
再向/data/logs/start/
目录中放入日志文件,Flume就会采集到日志数据了,因为我们监听的就是/data/logs/start/
目录。我们对监听的目录/文件做操作后,经过Flume的sink输出,文件就会放到HDFS上。
查看HDFS上的文件,会发现目录使用的和日志文件中数据的时间戳时间不一致,我们想要的是以日志文件中数据产生的时间来给文件归类,而不是我们对日志文件做操作的时间(当下)
存在的问题:Flume放数据时,使用本地时间;不理会日志的时间戳
自定义拦截器
要解决以上问题需要使用自定义拦截器,我们要对自定义Flume拦截器做测试,那就先准备测试用的Agent吧,这一步放到最后做也没问题,看习惯。
由于只是用来测试,那么source、channel、sink都选最简单的就好了——netcat source、memory channel、logger sink
用于测试的agent
vim /data/conf/flumetest1.conf
# a1是agent的名称。source、channel、sink的名称分别为:r1 c1 k1
a1.sources = r1
a1.channels = c1
a1.sinks = k1
# source
a1.sources.r1.type = netcat
a1.sources.r1.bind = hadoop2 # 由hadoop2作为数据输入源
a1.sources.r1.port = 9999 # 指定了999作为通信端口
a1.sources.r1.interceptors = i1 # 重点!指定数据进入source后要走的拦截器,多个则用空格隔开
a1.sources.r1.interceptors.i1.type = cn.flume.interceptor.CustomerInterceptor$Builder # 先定义好一个类CustomerInterceptor,稍后编写这个类
# 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.r1.channels = c1
a1.sinks.k1.channel = c1
编写自定义拦截器
原理:实现Flume的拦截器接口,实现
添加依赖
<dependencies>
<dependency>
<groupId>org.apache.flume</groupId>
<artifactId>flume-ng-core</artifactId>
<version>1.9.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.1.23</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3.2</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
编写拦截器、构建及测试
package cn.flume.interceptor;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.compress.utils.Charsets;
import org.apache.flume.Context;
import org.apache.flume.Event;
import org.apache.flume.event.SimpleEvent;
import org.apache.flume.interceptor.Interceptor;
import org.junit.Test;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
// 实现Flume的Interceptor接口
public class CustomerInterceptor implements Interceptor {
@Override
public void initialize() {
}
//逐条处理event,最关键是重写这个方法,内容可以不用过于关注,偏业务,这里是针对解析日志的时间戳来写的
@Override
public Event intercept(Event event) {
// 获取 event 的 body
String eventBody = new String(event.getBody(), Charsets.UTF_8);
// 获取 event 的 header
Map<String, String> headersMap = event.getHeaders();
// 解析body获取json串
String[] bodyArr = eventBody.split("\\s+");
try {
String jsonStr = bodyArr[6];
// 解析json串获取时间戳
JSONObject jsonObject = JSON.parseObject(jsonStr);
String timestampStr = jsonObject.getJSONObject("app_active").getString("time");
// 将时间戳转换为字符串 "yyyy-MM-dd"
// 将字符串转换为Long
long timestamp = Long.parseLong(timestampStr);
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
Instant instant = Instant.ofEpochMilli(timestamp);
LocalDateTime localDateTime = LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
String date = formatter.format(localDateTime);
// 将转换后的字符串放置header中
headersMap.put("logtime", date);
event.setHeaders(headersMap);
} catch (Exception e) {
headersMap.put("logtime", "Unknown");
event.setHeaders(headersMap);
}
return event;
}
// 批量处理event,实际上是循环调用逐条处理event方法
@Override
public List<Event> intercept(List<Event> events) {
List<Event> lstEvent = new ArrayList<>();
for (Event event : events) {
Event outEvent = intercept(event);
if (outEvent != null) {
lstEvent.add(outEvent);
}
}
return lstEvent;
}
@Override
public void close() {
}
/**
* 创建一个构建类Builder,实现Interceptor.Builder接口,用于构建Flume拦截器,实际上就是构建一个拦截器的实例,后续放到Flume的lib中使用
*/
public static class Builder implements Interceptor.Builder {
@Override
public Interceptor build() {
return new CustomerInterceptor();
}
@Override
public void configure(Context context) {
}
}
/**
* 单元测试
*/
@Test
public void testJunit() {
String str = "2020-08-20 11:56:00.365 [main] INFO com.lagou.ecommerce.AppStart - {\"app_active\":{\"name\":\"app_active\",\"json\":{\"entry\":\"1\",\"action\":\"0\",\"error_code\":\"0\"},\"time\":1595266507583},\"attr\":{\"area\":\"菏泽\",\"uid\":\"2F10092A1\",\"app_v\":\"1.1.2\",\"event_type\":\"common\",\"device_id\":\"1FB872-9A1001\",\"os_type\":\"0.01\",\"channel\":\"YO\",\"language\":\"chinese\",\"brand\":\"Huawei-9\"}}";
Map<String, String> map = new HashMap<>();
// new Event
Event event = new SimpleEvent();
event.setHeaders(map);
event.setBody(str.getBytes(Charsets.UTF_8));
// 调用interceptor处理event
CustomerInterceptor customerInterceptor = new CustomerInterceptor();
Event outEvent = customerInterceptor.intercept(event);
// 处理结果
Map<String, String> headersMap = outEvent.getHeaders();
System.out.println(JSON.toJSONString(headersMap));
}
}
打包上传
调试无误就可以打成jar包上传到服务器,我的习惯是上传的jar包都先存到/data/jars
目录,统一管理
然后拷贝一份jar包到Flume的lib
目录中,或者通过建立软链接的方式链到Flume的lib
目录,我的习惯是后者
ln -s /data/jars/flume_demo-1.0-SNAPSHOT-jar-with-dependencies.jar $FLUME_HOME/lib/flume_demo-1.0-SNAPSHOT-jar-with-dependencies.jar
启动Flume
这里测试使用的是我们上面编写的agent,脚本命令:
flume-ng agent --conf $FLUME_HOME/conf --conf-file /data/conf/flumetest1.conf -name a1 -Dflume.root.logger=INFO,console
测试agent
因为我们配置选用的source是netcat source,所以数据源为网络,同时我们也配置了以9999端口从hadoop2获取数据,直接使用telnet
命令做测试
# 连接hadoop2:9999
telnet hadoop2 9999
# 发出一条数据
2020-08-20 11:56:00.375 [main] INFO com.lagou.ecommerce.AppStart - {"app_active":{"name":"app_active","json":{"entry":"2","action":"1","error_code":"0"},"time":1595341796049},"attr":{"area":"吴江","uid":"2F10092A9","app_v":"1.1.18","event_type":"common","device_id":"1FB872-9A1009","os_type":"8.88","channel":"UO","language":"chinese","brand":"xiaomi-5"}}
Flume收到数据
达到我们的预期,测试OK
当然还可以来乱发一条数据来试试,可以看到我们的过滤器是不认的
正式agent
用测试用agent测试完自定义拦截器,我们知道给source添加拦截器应该怎么配置了,将对应的配置项放到我们最初带有不足的agent中,做到以下修改:
- 给source增加自定义拦截器
- 不使用本地时间戳
a1.sinks.k1.hdfs.useLocalTimeStamp = true
,而根据header中的logtime来写文件
vim /data/conf/flume-log2hdfs2.conf
# 配置内容如下
a1.sources = r1
a1.sinks = k1
a1.channels = c1
# taildir source
a1.sources.r1.type = TAILDIR
a1.sources.r1.positionFile = /data/conf/startlog_position.json
a1.sources.r1.filegroups = f1
a1.sources.r1.filegroups.f1 = /data/logs/start/.*log
a1.sources.r1.interceptors = i1 # 给source增加自定义拦截器
a1.sources.r1.interceptors.i1.type = cn.flume.interceptor.CustomerInterceptor$Builder
# memory channel
a1.channels.c1.type = memory
a1.channels.c1.capacity = 100000
a1.channels.c1.transactionCapacity = 2000
# hdfs sink
a1.sinks.k1.type = hdfs
a1.sinks.k1.hdfs.path = /user/data/logs/start/dt=%{logtime}/ # 根据header中的logtime来写文件,这里路径前加上"dt="是供后续我们将数据导入Hive表时分区时作为分区字段使用的
a1.sinks.k1.hdfs.filePrefix = startlog-
a1.sinks.k1.hdfs.rollSize = 33554432
a1.sinks.k1.hdfs.rollCount = 0
a1.sinks.k1.hdfs.rollInterval = 0
a1.sinks.k1.hdfs.idleTimeout = 0
a1.sinks.k1.hdfs.minBlockReplicas = 1
a1.sinks.k1.hdfs.batchSize = 1000
# a1.sinks.k1.hdfs.useLocalTimeStamp = true # 不使用本地时间戳
# Bind the source and sink to the channel
a1.sources.r1.channels = c1
a1.sinks.k1.channel = c1
启动Flume并测试
使用新写的agent配置
flume-ng agent --conf /opt/install/flume-1.9.0/conf --conf-file /data/conf/flume-log2hdfs2.conf -name a1 -Dflume.root.logger=INFO,console
测试
# 拷贝一份7月21日的日志文件到/data/logs/start
cp data/start0721.middle.log start/start1.log
# 拷贝一份7月22日的日志文件到/data/logs/start
cp data/start0722.big.log start/start2.log
# 拷贝一份7月23日的日志文件到/data/logs/start
cp data/start0723.big.log start/start3.log
采集多种日志
上面我已经可以采集到启动日志数据了,还有一种日志需要我们采集——事件日志。先来分析下情况:
-
不同的日志文件,假设它们的存储目录不一样,也就是说我们的数据来自不同的目录;
-
我们想将不同日志数据放到HDFS不同目录下,也就是我们采集的数据要存放的目录也不同;
于是理出思路:
- 既然数据来源不同,那就在source中配置监控多个来源
- 既然数据去向不同,那就在处理数据的时候标记上数据的类型,在sink中根据类型的不同来输出到对应目录
- 数据在进入source后,到达sink输出前要做标记,那就免不了拦截器的干涉
根据我的习惯,思路有了,就先将agent配好,再根据agent将缺少的部分补上
采集多日志agent
vim /data/conf/flume-log2hdfs3.conf
# 配置内容如下
a1.sources = r1
a1.sinks = k1
a1.channels = c1
# taildir source
a1.sources.r1.type = TAILDIR
a1.sources.r1.positionFile = /data/conf/startlog_position.json
a1.sources.r1.filegroups = f1 f2 # 先前只监控启动日志文件目录,现在要监控多一个事件日志目录,所以多配一个filegroup,用空格分隔
a1.sources.r1.filegroups.f1 = /data/logs/start/.*log
a1.sources.r1.headers.f1.logtype = start # 使用headers.<filegroupName>.<headerKey> = <headerValue>配置给filegroup下的Event添加一个固定的kv对到header中,k就是headerKey,v就是headerValue,这里配置了f1的headerKey为logtype,headerValue为start,表示日志类型为启动日志
a1.sources.r1.filegroups.f2 = /data/logs/event/.*log # f2就是监控事件日志的filegroup
a1.sources.r1.headers.f2.logtype = event # 这里配置了f2的headerKey为logtype,headerValue为event,表示日志类型为启动日志
a1.sources.r1.interceptors = i1 # 给source增加自定义拦截器
a1.sources.r1.interceptors.i1.type = cn.flume.interceptor.LogTypeInterceptor$Builder
# memory channel
a1.channels.c1.type = memory
a1.channels.c1.capacity = 100000
a1.channels.c1.transactionCapacity = 2000
# hdfs sink
a1.sinks.k1.type = hdfs
a1.sinks.k1.hdfs.path = /user/data/logs/%{logtype}/dt=%{logtime}/ # 使用header中的logtype和logtime来来区分目录
a1.sinks.k1.hdfs.filePrefix = startlog-
a1.sinks.k1.hdfs.fileType = DataStream
a1.sinks.k1.hdfs.rollSize = 33554432
a1.sinks.k1.hdfs.rollCount = 0
a1.sinks.k1.hdfs.rollInterval = 0
a1.sinks.k1.hdfs.idleTimeout = 0
a1.sinks.k1.hdfs.minBlockReplicas = 1
a1.sinks.k1.hdfs.batchSize = 1000
# Bind the source and sink to the channel
a1.sources.r1.channels = c1
a1.sinks.k1.channel = c1
编写自定义拦截器、构建及测试
package cn.flume.interceptor;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.compress.utils.Charsets;
import org.apache.flume.Context;
import org.apache.flume.Event;
import org.apache.flume.event.SimpleEvent;
import org.apache.flume.interceptor.Interceptor;
import org.junit.Test;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class LogTypeInterceptor implements Interceptor {
@Override
public void initialize() {
}
@Override
public Event intercept(Event event) {
// 获取 event 的 body
String eventBody = new String(event.getBody(), Charsets.UTF_8);
// 获取 event 的 header
Map<String, String> headersMap = event.getHeaders();
// 解析body获取json串
String[] bodyArr = eventBody.split("\\s+");
try {
String jsonStr = bodyArr[6];
// 解析json串获取时间戳
String timestampStr = "";
JSONObject jsonObject = JSON.parseObject(jsonStr);
if (headersMap.getOrDefault("logtype", "").equals("start")) {
// 取启动日志的时间戳
timestampStr = jsonObject.getJSONObject("app_active").getString("time");
} else if (headersMap.getOrDefault("logtype", "").equals("event")) {
// 取事件日志第一条记录的时间戳
JSONArray jsonArray = jsonObject.getJSONArray("lagou_event");
if (jsonArray.size() > 0) {
timestampStr = jsonArray.getJSONObject(0).getString("time");
}
}
// 将时间戳转换为字符串 "yyyy-MM-dd"
// 将字符串转换为Long
long timestamp = Long.parseLong(timestampStr);
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
Instant instant = Instant.ofEpochMilli(timestamp);
LocalDateTime localDateTime = LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
String date = formatter.format(localDateTime);
// 将转换后的字符串放置header中
headersMap.put("logtime", date);
event.setHeaders(headersMap);
} catch (Exception e) {
headersMap.put("logtime", "Unknown");
event.setHeaders(headersMap);
}
return event;
}
@Override
public List<Event> intercept(List<Event> events) {
List<Event> lstEvent = new ArrayList<>();
for (Event event : events) {
Event outEvent = intercept(event);
if (outEvent != null) {
lstEvent.add(outEvent);
}
}
return lstEvent;
}
@Override
public void close() {
}
public static class Builder implements Interceptor.Builder {
@Override
public Interceptor build() {
return new LogTypeInterceptor();
}
@Override
public void configure(Context context) {
}
}
@Test
public void startJunit() {
String str = "2020-08-20 11:55:51.545 [main] INFO com.lagou.ecommerce.AppStart - {\"app_active\":{\"name\":\"app_active\",\"json\":{\"entry\":\"2\",\"action\":\"1\",\"error_code\":\"0\"},\"time\":1595468466513},\"attr\":{\"area\":\"包头\",\"uid\":\"2F10092A400000\",\"app_v\":\"1.1.11\",\"event_type\":\"common\",\"device_id\":\"1FB872-9A100400000\",\"os_type\":\"0.1.2\",\"channel\":\"CX\",\"language\":\"chinese\",\"brand\":\"Huawei-2\"}}";
Map<String, String> map = new HashMap<>();
// new Event
Event event = new SimpleEvent();
map.put("logtype", "start");
event.setHeaders(map);
event.setBody(str.getBytes(Charsets.UTF_8));
// 调用interceptor处理event
LogTypeInterceptor customerInterceptor = new LogTypeInterceptor();
Event outEvent = customerInterceptor.intercept(event);
// 处理结果
Map<String, String> headersMap = outEvent.getHeaders();
System.out.println(JSON.toJSONString(headersMap));
}
@Test
public void eventJunit() {
String str = "2020-08-20 12:00:22.178 [main] INFO com.lagou.ecommerce.AppEvent - {\"lagou_event\":[{\"name\":\"loading\",\"json\":{\"loading_time\":\"0\",\"action\":\"2\",\"loading_type\":\"3\",\"type\":\"2\"},\"time\":1595298075016},{\"name\":\"notification\",\"json\":{\"action\":\"1\",\"type\":\"4\"},\"time\":1595278552792},{\"name\":\"ad\",\"json\":{\"duration\":\"21\",\"ad_action\":\"0\",\"shop_id\":\"17\",\"event_type\":\"ad\",\"ad_type\":\"3\",\"show_style\":\"1\",\"product_id\":\"10\",\"place\":\"placecampaign3_left\",\"sort\":\"7\"},\"time\":1595329062342},{\"name\":\"favorites\",\"json\":{\"course_id\":3,\"id\":0,\"userid\":0},\"time\":1595342344681}],\"attr\":{\"area\":\"张家界\",\"uid\":\"2F10092A1\",\"app_v\":\"1.1.1\",\"event_type\":\"common\",\"device_id\":\"1FB872-9A1001\",\"os_type\":\"3.3\",\"channel\":\"IN\",\"language\":\"chinese\",\"brand\":\"Huawei-6\"}}";
Map<String, String> map = new HashMap<>();
// new Event
Event event = new SimpleEvent();
map.put("logtype", "event");
event.setHeaders(map);
event.setBody(str.getBytes(Charsets.UTF_8));
// 调用interceptor处理event
LogTypeInterceptor customerInterceptor = new LogTypeInterceptor();
Event outEvent = customerInterceptor.intercept(event);
// 处理结果
Map<String, String> headersMap = outEvent.getHeaders();
System.out.println(JSON.toJSONString(headersMap));
}
}
打包上传+测试
单元测试OK就上传服务器,步骤同上,由于我加载jar用的是建软链接的方式,所以只需要将jar包上传到和上次一样的路径就可以,agent也编写好了,那就可以直接启动来测试,但是在这之前,如果之前做测试使用的是非常大的文件而source和sink的速度没匹配好导致taildir source重复获取数据和不释放资源,别忘了先清一波文件再启动测试。
# 清理环境
rm -f /data/conf/startlog_position.json
rm -f /data/logs/start/*.log
rm -f /data/logs/event/*.log
# 启动Flume
flume-ng agent --conf /opt/install/flume-1.9.0/conf --conf-file /data/conf/flume-log2hdfs3.conf -name a1 -Dflume.root.logger=INFO,console
摘取启动Flume成功的最后一段信息看看
测试
# 拷贝一份启动日志到监控目标目录,Flume输出信息见下图1
cp data/start0723.big.log start/
# 拷贝一份事件日志到监控目标目录,Flume输出信息见下图2
cp data/start0723.big.log start/
最后来检查一下HDFS的情况
启动日志
还有一点
# 在生产环境中我们这样启动Agent
nohup flume-ng agent --conf /opt/install/flume-1.9.0/conf --conf-file /data/conf/flume-log2hdfs3.conf -name a1 -Dflume.root.logger=INFO,console > /dev/null 2>&1 &
小结
- 在每个目录中可以使用正则匹配多个文件
- 使用 taildir source 监控指定的多个目录,可以给不同目录的日志加上不同 header
- hdfs文件的滚动方式(基于文件大小、基于event数量、基于时间)
- 调节flume jvm内存的分配