大数据技术栈环境搭建-Flume

文章内容输出来源:拉勾教育大数据开发高薪训练营

集群规划

hadoop1hadoop2hadoop3hadoop4hadoop5
Flume

Flume安装与配置

前提条件

每台机器都具备jdk环境,下述操作的jdk路径为/opt/install/jdk1.8.0_231

准备安装文件

本篇需要的文件有:

上传apache-flume-1.9.0-bin.tar.gzhadoop2/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的使用重点在于

  1. 熟悉各种Source-Channel-Sink的适用场景和作用
  2. 写配置文件时十二分细心

Flume使用

使用Flume采集日志,日志有两种:启动日志和事件日志

image-20210622205109995

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目录,统一管理

image-20210623092203662

然后拷贝一份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

image-20210623093521897

启动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"}}

image-20210623094903696

Flume收到数据

image-20210623095043915

达到我们的预期,测试OK

image-20210623095332276

当然还可以来乱发一条数据来试试,可以看到我们的过滤器是不认的

image-20210623095451237

image-20210623095513051

正式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

image-20210623214132572

采集多种日志

上面我已经可以采集到启动日志数据了,还有一种日志需要我们采集——事件日志。先来分析下情况:

  • 不同的日志文件,假设它们的存储目录不一样,也就是说我们的数据来自不同的目录

  • 我们想将不同日志数据放到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成功的最后一段信息看看

image-20210623230852041

测试

# 拷贝一份启动日志到监控目标目录,Flume输出信息见下图1
cp data/start0723.big.log start/

# 拷贝一份事件日志到监控目标目录,Flume输出信息见下图2
cp data/start0723.big.log start/

image-20210623232048439

image-20210623232135216

最后来检查一下HDFS的情况

image-20210623232231583

启动日志

image-20210623232409935

image-20210623232735498

还有一点

# 在生产环境中我们这样启动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内存的分配
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值