flume自定义组件(source,Interceptor,sink)

Source

原理

每次Agent启动后,会调用PollableSourceRunner.start(),开启一个PollableSourceRunner线程!

这个线程会初始化PollableSource(可以轮询地去读取数据源中的数据)对象!

​ PollableSource由所在的PollingRunner线程控制,调用PollableSource的process()方法,来探测是否有新的数据,将新的数据封装为event,存储到channel中!

package com.zhengkw.flume;

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

import java.util.ArrayList;
import java.util.List;

/**
 * @ClassName:MySource
 * @author: zhengkw
 * @description: flume自定义source
 * @date: 20/03/13下午 10:20
 * @version:1.0
 * @since: jdk 1.8
 */
public class MySource extends AbstractSource implements Configurable, PollableSource {
    String name;

    /**
     * @descrption: 调用此方法处理封装处理event
     * Status 为枚举类  ready backoff 2个状态!
     * <p>
     * 最核心的方法,读取数据,封装为event,写入到channel
     * 如果读到数据,封装为event,返回ready,否则如果当前没有读到数据,返回backoff
     * 每间隔5s,自动封装10个event,10个event的内容为{zhengkw:i}
     * @return: org.apache.flume.PollableSource.Status
     * @date: 20/03/13 下午 10:24
     * @author: zhengkw
     */

    @Override
    public Status process() throws EventDeliveryException {
        Status status = Status.READY;
        List<Event> events = new ArrayList<>();
        //一次封装10个
        for (int i = 0; i < 10; i++) {
            SimpleEvent event = new SimpleEvent();
            //封装数据
            event.setBody(("zhengkw" + i).getBytes());
            //添加
            events.add(event);
        }
        try {
            //获取channelprocessor
            ChannelProcessor processor = getChannelProcessor();
            //将数据放入putlist中
            processor.processEventBatch(events);

            // 每次执行完,让线程休息5s
            Thread.sleep(5000);
        } catch (Exception e) {
            //如果处理异常就回滚!
            status = Status.BACKOFF;
            e.printStackTrace();
        }


        return status;
    }

    /**
     * @descrption: 单位ms 控制无数据读取时睡眠时间增量 与countGroup获取的Long做乘积
     * Thread.sleep(Math.min(
     * counterGroup.incrementAndGet("runner.backoffs.consecutive")*
     * source.getBackOffSleepIncrement(),
     * source.getMaxBackOffSleepInterval()));
     * @return: long
     * @date: 20/03/13 下午 10:24
     * @author: zhengkw
     */
    @Override
    public long getBackOffSleepIncrement() {
        //1秒
        return 1000;
    }

    /**
     * @descrption:单位ms 控制最大睡眠时间
     * @return: long
     * @date: 20/03/13 下午 10:25
     * @author: zhengkw
     */
    @Override
    public long getMaxBackOffSleepInterval() {
        //最长5秒
        return 5000;
    }

    /**
     * @param context
     * @descrption: 获取 flume中agent配置文件上下文
     * @return: void
     * @date: 20/03/13 下午 10:30
     * @author: zhengkw
     */
    @Override
    public void configure(Context context) {
        //如果没有通过name获取到值 则获取默认值!
        name = context.getString("name", "i'm default");
    }
}

Interceptor

在存入channel之前拦截,如果有多个拦截器,按照配置文件从上至下的顺序依次执行拦截!

package com.zhengkw.flume;

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

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

/**
 * @ClassName:MyInterceptor
 * @author: zhengkw
 * @description: flume自定义拦截器
 * @date: 20/03/13下午 10:21
 * @version:1.0
 * @since: jdk 1.8
 */
public class MyInterceptor implements Interceptor {

    // 初始化,会在拦截器创建完成后,调用一次
    @Override
    public void initialize() {

    }

    /**
     * @param event
     * @descrption: 实现一个往event header中添加一个时间戳
     * @return: org.apache.flume.Event
     * @date: 20/03/13 下午 11:52
     * @author: zhengkw
     */
    @Override
    public Event intercept(Event event) {


        Map<String, String> headers = event.getHeaders();
        headers.put("time", System.currentTimeMillis() + "");
        //将封装好的header set到event中!
        event.setHeaders(headers);
        return event;
    }

    /**
     * @param events
     * @descrption: 拦截一批event
     * 每个处理方法直接调用拦截单个的方法intercept(Event event)!
     * @return: java.util.List<org.apache.flume.Event>
     * @date: 20/03/13 下午 11:49
     * @author: zhengkw
     */
    @Override
    public List<Event> intercept(List<Event> events) {
        for (Event event : events) {
            intercept(event);
        }

        return events;
    }

    /**
     * @descrption: 因为自定义拦截器实现的接口,
     * 接口中没有构造器!
     * 必须实现 Interceptor内部接口才能Builder
     * 才能返回拦截器的一个实例
     * @return:
     * @date: 20/03/13 下午 11:59
     * @author: zhengkw
     */
    public static class Builder implements Interceptor.Builder {
        /**
         * @descrption: 实现接口中的build方法
         * @return: org.apache.flume.interceptor.Interceptor
         * @date: 20/03/14 上午 12:04
         * @author: zhengkw
         */
        @Override
        public Interceptor build() {
            // Builder implementations MUST have a no-arg constructor
            return new MyInterceptor();
        }

        @Override
        public void configure(Context context) {

        }
    }


    @Override
    public void close() {

    }
}

Sink

原理

每个Sink都由一个SinkRunner线程负责调用其process()方法,完成从channel抽取数据,存储到外部设备的逻辑!

package com.zhengkw.flume;

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

/**
 * @ClassName:MySink
 * @author: zhengkw
 * @description: 自定义Sink
 * @date: 20/03/13下午 10:21
 * @version:1.0
 * @since: jdk 1.8
 */
public class MySink extends AbstractSink implements Configurable {

    //前缀
    private String prefix;
    //后缀
    private String suffix;
    //用org.slf4j.Logger在控制台上打印 监控Sink!
    private Logger logger = LoggerFactory.getLogger(MySink.class);


    /**
     * @descrption: 最核心的方法,这个方法负责从channel中获取event,
     * 将event写到指定的设备
     * 如果成功传输了一个或多个event,就返回ready,
     * 否则如果从channel中获取不到event,返回backoff
     * @return: org.apache.flume.Sink.Status
     * @date: 20/03/13 下午 11:09
     * @author: zhengkw
     */
    @Override
    public Status process() throws EventDeliveryException {
        Status status = Status.READY;
        //获取channel
        Channel channel = getChannel();
        //获取事务对象!
        Transaction transaction = channel.getTransaction();
        try {

            //开启事务
            transaction.begin();
            Event e = null;
            //channel调用take方法将数据传给 takelist
            //将take到的地址值给e
            e = channel.take();
            if (e == null) {
                status = Status.BACKOFF;
            } else {
                //用Logger在控制台上打印info级别的信息
                //打印event头,打印data byte数组转字符串! 前后缀
                logger.info("Header:" + e.getHeaders() + " Body:" + prefix + e.getBody().toString() + suffix);
            }
            //提交事务!
            transaction.commit();
        } catch (ChannelException ex) {
            transaction.rollback();

            status = Status.BACKOFF;
            ex.printStackTrace();

        } finally {
            //关闭事务!
            transaction.close();

        }

        return status;
    }

    /**
     * @param context
     * @descrption: //从agent的配置文件中获取配置
     * @return: void
     * @date: 20/03/13 下午 11:42
     * @author: zhengkw
     */
    @Override
    public void configure(Context context) {
        prefix = context.getString("prefix", "kevin:");
        suffix = context.getString("suffix", ":come on!");
    }
}

conf

a1.sources = r1
a1.sinks = k1
a1.channels = c1

# 自定义source,type必须是类的全类名
a1.sources.r1.type = com.zhengkw.flume.MySource
a1.sources.r1.name = merryme:

# 配置sink
a1.sinks.k1.type =com.zhengkw.flume.MySink
a1.sinks.k1.prefix = jack:
a1.sinks.k1.suffix = :back!

#为source添加拦截器
a1.sources.r1.interceptors = i1
#type必须写Bulider的全类名
a1.sources.r1.interceptors.i1.type =com.zhengkw.flume.MyInterceptor$Builder

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

# 绑定和连接组件
a1.sources.r1.channels = c1
a1.sinks.k1.channel = c1

测试

在这里插入图片描述

总结

注意事项

  • 自定义的Source中没有写事务开启,提交,关闭!而在自定义sink中需要实现!channelprocessor中来管理事务的所以source中不需要写!
  • 配置拦截器时,注意全类名用的是内部类的全类名
    com.zhengkw.flume.MyInterceptor$Builder $ 符号代表内部类,因为编译后的class文件名,内部类用此区分!

在这里插入图片描述

  • 因为自定义拦截器实现的是接口, 接口中没有构造器
    必须实现 Interceptor内部接口才能Builder才能返回拦截器的一个实例

在这里插入图片描述

  • Flume中的事务代表的是一批要以原子性写入到channel或要从channel中移除的events!
  • ​ Flume在设计时,采取的是at least once语义!因此在没有故障时,flume只会写入一次数据!
  • Source和Sink中的process方法都有可能发生异常,所以可能会发生数据重复的问题!

事务的特点​

  • 事务的实现由channel提供!source和sink在put和 take数据时,只是先获取channel中已经定义好的事务!

  • 不同的channel的事务可能实现方式是不同的,但是原理和目的是一样的!都是为了put和take一批events的原子性!

  • put事务,不需要source操作,而是由ChannelProcessor进行操作!

  • 假如一个事务中,一部分event已经写入到目的地,但是随着事务的回滚,这些event可能重复写入!

  • 如果数据重复怎么办?

  • 如果对数据不敏感,重复无所谓!

  • 如果对数据敏感,要求数据最好有一个唯一的id字段,在具体使用数据时,进行去重!

  • Source核心

put事务

  • put事务指source将封装好的event,交给ChannelProcessor,ChannelProcessor在处理这批events时,先把events放入到putList中,放入完成后,一次性commit(),这批events就可以成功写入到channel!

  • 写入成功后,执行清空putList操作!​

  • 如果在过程中,发生任何异常,此时执行rollback(),rollback()会回滚putList,回滚也会直接清空putList!

take事务

  • ​ take事务指sink不断从channel中获取event!,每获取一批event中的一个,都会将这个event放入takeList中!
  • 一般一批evnet全部写入,执行commit()方法,这个方法会清空takeList!如果在此期间,发生了异常,执行rollback(),此时会回滚takeList中的这批event到channel!

回滚

  • 回滚只是清空put/takeList中的数据!!
  • 具体重新放入event到List中是channel所实现的!!!!
  • 因为channel提供的事务!!
发布了37 篇原创文章 · 获赞 17 · 访问量 1821

猜你喜欢

转载自blog.csdn.net/qq_37714755/article/details/104853436