三, Flume 进阶下-- Flume自定义拦截器, 自定义Sources, 自定义Sinks

三, Flume 进阶- 自定义拦截器, 自定义Sources, 自定义Sinks

3.1 自定义Intercepter拦截器

我们结合实际例子了解自定义拦截器多路复用channel选择器的结合使用.

[案例需求]

  • 使用Flume 采集服务器本地日志, 我们需要根据日志类型的不同, 将不同种类的日志发往不同的分析系统. 本案例中, 我们以端口数据模拟日志(netcat 发送数据), 以单个数字和单个字母来模拟不同类型的日志.
  • 我们需要使用定义intercepter 区分数字和字母, 然后使用多路复用channel选择器向不同channel分发不同类型数据.

[需求分析]

  • 在实际的开发中, 一台服务器产生的日志类型有很多种, 不同类型的日志可能需要发送到不同的分析系统. 而由于日志在Flume中作为event传输时, 会先经过Intercepter拦截器的过滤和修改,然后被多路复用策略的channel选择器选择合适的channel进行缓存. 我们可以利用这一过程, 实现对不同类型日志的分拣.
  • 在channel选择器的两种策略中, **复制channel选择器(Replicating Channel Selecttor)**是指把数据完整的发到每一个channel中; **多路复用channel选择器(Multiplexing channel selector)**是指按照一定的规则把指定类型的数据发送到指定的channel上.
  • 多路复用channel选择器的底层原理: sources和channel之间传输的event数据, 格式为header+body, 而header是由<key, value>键值对组成的, 所以根据event中Header的k-v键值对, 可以将key的value不同的event发送到不同的channel中, 这是分发不同类型数据的关键. 如何实现分拣不同数据呢?? --> 自定义一个intercepter拦截器, 为不同event数据中 Header赋予不同的k-v值.
  • 如何实现自定义拦截器, 打个比方: 有一个字符a作为event1, 一个数字8作为event2, 我们需要把这两个不同类型的event传送到两个目的地(交给不同的channel,最终由不同的sink写出去)中, 那么就需要对event的头信息进行自定义, 又由于头信息headers由k-v组成, 所以可以设立判断条件,
    • 当遇到数字8时, 向头信息中写入event.getHeaders().put('type', 'number/数字'),
    • 当遇到字符a时, 向头信息中写入event.getHeaders().put('type', 'letter/字母'),

[案例实操]

在这里插入图片描述

  1. 新建maven项目flume_demo, 并往pom文件中导入flume依赖, 刷新maven.
<!--    flume-ng 依赖-->
    <dependencies>
        <dependency>
            <groupId>org.apache.flume</groupId>
            <artifactId>flume-ng-core</artifactId>
            <version>1.7.0</version>
        </dependency>
    </dependencies>
  1. 新建类, 命名为 cn.cyy.interceptor.TypeInterceptor, 键入下面的代码
package cn.cyy.interceptor;

import org.apache.flume.Context;
import org.apache.flume.Event;
import org.apache.flume.interceptor.Interceptor;

import java.util.List;
import java.util.Map;

public class Typeinterceptor implements Interceptor {
    
    
    @Override
    public void initialize() {
    
    

    }

    // 单个event 事件的处理方法
    // 1. event 的头信息header 是以k-v形式存储信息
    // 2. 所以我们对数据进行过滤清洗, 就是根据body内容的不同往header中写入一些标记信息(k-v),  然后再让多路复用channel选择器读取,缓存到不同的channel中.
    @Override
    public Event intercept(Event event) {
    
    
        //1. 得到event 的头信息
        Map<String, String> headers = event.getHeaders();

        //2. 得到event 的body内容
        byte[] bytebody = event.getBody();
        //String bodys = new String(bytebody); //把字节数组转为string字符串, 方便我们处理

        //3. 根据body的内容, 写入不同发头信息标记
        if(bytebody[0] >= '0' && bytebody[0] <= '9'){
    
    
            //往头信息中做标记
            headers.put("type","number");
        }else if(bytebody[0] > 'a' && bytebody[0] < 'z'){
    
    
            headers.put("type","letter");
        }
        
        return event;
    }
    
    // 多个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 Typeinterceptor();
        }

        @Override
        public void configure(Context context) {
    
    

        }
    }
}
  1. 打包maven项目, 把在target目录下生成的jar包放到flume安装目录下flume-1.7.0/lib/.
    在这里插入图片描述

  2. 代码编写就此完成, 下面就是配置文件的写入.

  • flume-1.conf 是在bigtda01主机中, 通过netcat类型的sources监听本机7788端口数据, 在数据经自定义拦截器的拦截过滤之后, 由多路复用channel选择器把不同的数据写入到不同的channel中, 然后就是avro sink拉取数据, 传送到远程主机bigdata02, bigdata03.
# bigdata01  
# netcat 监听端口获取数据
# channel selector = multiplexing 
# avro sink1 + avro sink2

# 命名各组件
a1.sources = r1
a1.channels = c1 c2
a1.sinks = k1 k2

# 配置sources (netcat sources)

a1.sources.r1.type = netcat
a1.sources.r1.bind  = localhost
a1.sources.r1.port = 7788

######### 引入自定义并已经打包好的拦截器类, 并把channel选择器设为为多路复用 (注意拦截器的类型是自定义类的全类名$Builder)

a1.sources.r1.interceptors = i1
a1.sources.r1.interceptors.i1.type = cn.cyy.interceptor.Typeinterceptor$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


#配置channels (memory channel)
a1.channels.c1.type = memory
a1.channels.c1.capacity = 1000
a1.channels.c1.transactionCapcity = 100


a1.channels.c2.type = memory
a1.channels.c2.capacity = 1000
a1.channels.c2.transactionCapcity = 100

#配置sinks (avro sink)
a1.sinks.k1.type = avro
a1.sinks.k1.hostname = bigdata02
a1.sinks.k1.port = 2021

a1.sinks.k2.type = avro
a1.sinks.k2.hostname = bigdata03
a1.sinks.k2.port = 2022


# 绑定各个组件
a1.sources.r1.channels = c1 c2
a1.sinks.k1.channel = c1
a1.sinks.k2.channel = c2
  1. flume-2.conf 接收letter类型的数据并打印到控制台.
# 接收flume-1 传来的数字数据
# 命名各组件
a2.sources = r1
a2.channels = c1
a2.sinks = k1

# 配置sources (avro sources)

a2.sources.r1.type = avro
a2.sources.r1.bind= bigdata02
a2.sources.r1.port = 2021

#配置channels (memory channel)
a2.channels.c1.type = memory
a2.channels.c1.capacity = 1000
a2.channels.c1.transactionCapcity = 100

#配置sinks (logger sink)
a2.sinks.k1.type = logger



# 绑定各个组件
a2.sources.r1.channels = c1
a2.sinks.k1.channel = c1
  1. flume-3.conf 接收number类型的数据并打印到控制台.
# 接收flume-1 传来的数字数据
# 命名各组件
a3.sources = r1
a3.channels = c1
a3.sinks = k1

# 配置sources (avro sources)

a3.sources.r1.type = avro
a3.sources.r1.bind= bigdata03
a3.sources.r1.port = 2022

#配置channels (memory channel)
a3.channels.c1.type = memory
a3.channels.c1.capacity = 1000
a3.channels.c1.transactionCapcity = 100

#配置sinks (logger sink)
a3.sinks.k1.type = logger



# 绑定各个组件
a3.sources.r1.channels = c1
a3.sinks.k1.channel = c1
  1. 最终结果:
#执行命令(注意按照 avro source 是服务端, 应该先执行的规则依次运行各个配置文件)
bin/flume-ng agent -n a1 -c conf/ -f jod/custom-interceptor/flume-3.conf

bin/flume-ng agent -n a1 -c conf/ -f jod/custom-interceptor/flume-2.conf

bin/flume-ng agent -n a1 -c conf/ -f jod/custom-interceptor/flume-1.conf

在这里插入图片描述

3.2 自定义sources

  • Source 是负责接收数据到Flume Agent 的组件, 其可以处理多种类型多种格式的数据, 包括 avro, exec, netcat, spooling directory, http, thrift, jms, sepuence generator, syslog, legacy. 我们还可以根据实际需求自定义某些source.

自定义MySource需要继承AbstractSource类. 并实现Configurable和PollableSource接口, 并实现以下方法:

扫描二维码关注公众号,回复: 13230235 查看本文章
  1. config(Context context) //初始化context(读取配置文件内容)
  2. process() //获取数据封装成event并写入channel, 这个方法将被循环调用
  3. getBackOffSleepIncrement()//暂不用
  4. getMaxBackOffSleepInterval() //暂不用

[案例需求与分析]

使用 flume 接收数据,并给每条数据添加前缀,输出到控制台。前缀可从 flume 配置文
件中配置。

在这里插入图片描述

  • 对Mysource代码编写的分析:

在这里插入图片描述

[案例实操]

  1. 新建maven项目, 引入flume-ng-core依赖.
<dependencies>
<dependency>
<groupId>org.apache.flume</groupId>
<artifactId>flume-ng-core</artifactId>
<version>1.7.0</version>
</dependency>
</dependencies>
  1. 编码
package cn.cyy.source;

import org.apache.flume.Context;
import org.apache.flume.EventDeliveryException;
import org.apache.flume.PollableSource;
import org.apache.flume.conf.Configurable;
import org.apache.flume.event.SimpleEvent;
import org.apache.flume.source.AbstractSource;

public class Mysource extends AbstractSource implements Configurable, PollableSource {
    
    

    private String prefix;
    private String suffix;

    // 对获取的数据进行处理(设置值, 传给channel)
    @Override
    public Status process() throws EventDeliveryException {
    
    
       Status status  = null;

        try{
    
    
            // 1. 用for循环模拟待处理的一系列数据
            for (int i = 0; i < 5; i++) {
    
    
                //2. 新建event事件对象, 它是flume数据传输的基本单位
                SimpleEvent event = new SimpleEvent();

                //3. 往event对象中写入值
                event.setBody((prefix +"eat"+i+"disk of"+suffix).getBytes());

                //4. 将事件传给channel选择器去处理
                getChannelProcessor().processEvent(event);
            }
            status = Status.READY;
        }catch (Throwable t) {
    
    
            // Log exception, handle individual exceptions as needed

            status = Status.BACKOFF;

            // re-throw all Errors
            if (t instanceof Error) {
    
    
                throw (Error)t;
            }
        }

        try {
    
    
            Thread.sleep(2000);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        return status;
    }

    // 从上下文中获取配置信息
    @Override
    public void configure(Context context) {
    
    
        //读取配置信息, 给prefix, suffix复制
        prefix = context.getString("prefix");
        suffix = context.getString("suffix","no val, set default value...");
    }


    @Override
    public long getBackOffSleepIncrement() {
    
    
        return 0;
    }

    @Override
    public long getMaxBackOffSleepInterval() {
    
    
        return 0;
    }
}
  1. 执行命令, 结果如下
 bin/flume-ng agent -n a1 -c conf/ -f job/custom-sources/flume.conf -Dflume.root.logger=INFO,console

在这里插入图片描述

3.3 自定义Sink

  • Sink 不断轮询Channel中的事件并批量的移除他们, 并将这些事件批量写入到存储或索引系统, 或者被发送到另一个Flume Agent上.
  • Sink是完全事务性的, 在从Channel批量删除数据之前, 每个Sink用Channel启动一个事务, 批量事件一旦成功写出到存储系统或者下一个Flume Agent, Sink就利用channel提交事务, 一旦事务成功提交, 该Channel就会从自己内部的缓冲区中删除事件.

  • Sink组件目的地包括hdfs, logger, avro, file, HBase thrift, ipc, null, solr, 自定义.

自定义MySink需要继承AbstracSink类. 并实现Configurable接口, 并实现以下方法:

  1. config(Context context) //初始化context(读取配置文件内容)
  2. process() //从Channel中获取数据(event), 这个方法将被循环调用.

[案例需求与分析]

使用 flume 接收数据,并在 Sink 端给每条数据添加前缀和后缀,输出到控制台。前后
缀可在 flume 任务配置文件中配置。

在这里插入图片描述

[需求实现]

  1. 新建Maven项目, 引入flume-ng-core 的依赖(略)

  2. 编码如下, 编码完成后,打包并放入到flume的job文件夹中

package cn.cyy.sink;

import org.apache.flume.*;
import org.apache.flume.conf.Configurable;
import org.apache.flume.sink.AbstractSink;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class MySink extends AbstractSink implements Configurable {
    private String prefix;
    private String suffix;

    // 获取log4j中的logger对象
    Logger logger = LoggerFactory.getLogger(MySink.class);


    //对从channel要来的数据进行处理
    @Override
    public Status process() throws EventDeliveryException {
        //1.  获取channel对象,开
        Channel channel = getChannel();

        //2. 使用channel对象获取事务并开启事务
        Transaction txn = channel.getTransaction();
        txn.begin();

        Event event = null;
        try{
            //3. 声明event对象, 并获取数据()
            while(true){
                  event   = channel.take();
                  if(event != null){
                      break;
                  }
            }


            //4. 对数据进行处理(把event的body存储的数据, 加上前后缀)
            logger.info(prefix + new String(event.getBody())+ suffix); // info-->debug-->warn-->error-->trace
            //5. 提交事务
            txn.commit();

            return Status.READY;

        }catch(Exception e){
            //有异常了, 则回滚
            txn.rollback();

            //多个sink在遇到异常时, 退避
            return Status.BACKOFF;

        }finally{
            txn.close();
        }

    }

    //获取配置文件
    @Override
    public void configure(Context context) {
        prefix = context.getString("prefix", "!!! default val");
        suffix = context.getString("suffix", "!!! default val");
    }
}

  1. 编写配置 flume-1.conf
# 命名各组件
a1.sources = r1
a1.sinks = k1
a1.channels = c1

# 配置sources
a1.sources.r1.type = netcat
a1.sources.r1.bind = localhost 
a1.sources.r1.port =7788

#配置channel
a1.channels.c1.type = memory 
a1.channels.c1.capacity = 1000
a1.channels.c1.transactionCapactiy = 100

#配置sinks
a1.sinks.k1.type = cn.cyy.sink.MySink
a1.sinks.k1.prefix = xiaozhou
a1.sinks.k1.suffix = xiaochen


#绑定各组件
a1.sources.r1.channels = c1
a1.sinks.k1.channel = c1
  1. 最终结果
[win10@bigdata01 flume-1.7.0]$ bin/flume-ng agent -n a1 -c conf/ -f job/custom_sink/flume-1.conf -Dflume.root.logegr=INFO,console

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/nmsLLCSDN/article/details/120603759