flume自定义source,且kafka代替channel,实现flume往kafka传递数据

在使用flume收集数据时,有时候需要我们自定义source,而官方给的案例,有时也不能满足我们的需要,下面的案例是仿照源码的架构编写的。
下面的案例是:自定义source,用kafka代替channel,因为我们的目标就是,通过flume将数据采集到kafka,这样省去了从channel到sink的过程,提升了效率,而自定义source是为了防止重复传递数据。
在代码中我做了详细的解释:

package DataToKafka;

import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.flume.Context;
import org.apache.flume.Event;
import org.apache.flume.EventDrivenSource;
import org.apache.flume.channel.ChannelProcessor;
import org.apache.flume.conf.Configurable;
import org.apache.flume.event.EventBuilder;
import org.apache.flume.source.AbstractSource;


import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.charset.Charset;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 自定义source用来保证不重复消费
 * 原始日志文件
 * 偏移量文件
 * 编码格式
 * 睡眠时间
 *
 * */
public class SourceMy extends AbstractSource implements EventDrivenSource, Configurable {
    //定义成员属性
    //定义原始文件路径
    private String filepath;
    //定义偏移量文件路径
    private String offsetpath;
    //定义编码集
    private String charset;
    //定义间隔时间
    private Long interval;
   /* //定义线程池
    private ExecutorService excutor;*/
   //定义FileRunner对象
    private FileRunner fileRunner;

    @Override//加载配置文件的,利用context取出配置文件中的各种定义信息
    public void configure(Context context) {
        //从配置文件中加载原始文件
        filepath = context.getString("filepath");
        //从配置文件中加载偏移量文件的路径
        offsetpath = context.getString("offsetpath");
        //从配置文件中加载编码集
        charset = context.getString("charset","UTF-8");
        //从配置文件中加载间隔时间
        interval = context.getLong("interval",5000L);

    }

    @Override//执行操作的
    public synchronized void start() {
        //创建线程池
        /*
        * 固定线程池
        * Executors.newFixedThreadPool(int nThread),括号中参数是定义线程池中的线程数
        *可缓冲线程池
        * Executors.newCachedThreadPool()
        * 单线程的线程池
        * Executors.newSingleThreadExecutor()
        * */
        //因为还要用线程去监控文件,所以要把executor定义在公共属性中
        ExecutorService excutor = Executors.newSingleThreadExecutor();
        //定义一个channel发送的对象
        ChannelProcessor channelProcessor = getChannelProcessor();
        //定义出一个线程
        fileRunner = new FileRunner(filepath,offsetpath,charset,interval,channelProcessor);
        //将线程交给线程池管理,线程执行命令
        excutor.submit(fileRunner);

        super.start();
    }

    //定义一个类实现Runnable接口
    public class FileRunner implements Runnable {
        //定义成员属性
        private String filepath;
        private String offsetpath;
        private String charset;
        private Long interval;
        //定义channel发送类,作用就是将封装的event反送给channel
        private ChannelProcessor channelProcessor;
        //定义偏移量文件类
        private File offsetFile;
        //定义偏移量
        private Long offset = 0L;
        //定义原始文件的封装类
        private RandomAccessFile accessFile;
        //定义一个flag
        private boolean flag = true;

        public void setFlag(boolean flag) {
            this.flag = flag;
        }


        //定义一个构造方法,参数需要上面的属性
        //这个构造方法的作用是,读取偏移量文件内容,获取到偏移量,然后根据获取到的偏移量的值,将原始文件的位置搞到需要的偏移量的
        //位置,相当于初始化,将一切准备开始读取原始文件之前的动作做完
        public FileRunner(String filepath,String offsetpath,String charset,Long interval,ChannelProcessor channelProcessor) {
            this.filepath = filepath;
            this.charset = charset;
            this.interval = interval;
            this.offsetpath = offsetpath;
            this.channelProcessor = channelProcessor;

            //判断偏移量文件是否存在,若不存在,则创建一个偏移量文件,因为这个偏移量文件在run方法
            //中也要用,所以定义到成员属性中
            offsetFile = new File(this.offsetpath);
            //判断,如果不存在,则创建
            if (!offsetFile.exists()) {
                try {
                    offsetFile.createNewFile();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            //读取偏移量。这里要读取文件中的内容了,但是以往都是使用IO流进行读取文件内容,这里并没有使用IO流
            //我们可以使用文件的工具类,FileUtils,这个工具类中封装了对文件的各种操作
            try {
                //读取出偏移量
                String offsetStr = FileUtils.readFileToString(offsetFile);//这个方法的参数需要传入一个文件对象
                //判断,如果读取出的偏移量不是null或者“”
                if(null != offsetStr && !"".equals(offsetStr)) {
                    //因为在run方法中会使用到该偏移量,所以定义到成员属性中
                    offset = Long.parseLong(offsetStr);//将字符串转成long型
                }
                //将原始文件搞到我们需要开始读取的位置(根据偏移量)
                //这里使用了RandomAccessFile类,这个类中有个seek方法,可以直接定义到我们需要的文件位置,类似于C++中
                //的指针的含义;还有个readLine的方法,可以读取一行文件内容
                //在run方法中需要使用它去一行一行的读取原始文件,所以定义到成员属性中
                accessFile = new RandomAccessFile(filepath,"r");//第一个参数是指定文件对象,第二个参数代表“只读”
                //调用seek方法,传入刚才获取到的偏移量,代表着我们已经将文件的开始读取位置,搞到了偏移量的位置
                accessFile.seek(offset);

            } catch (IOException e) {
                e.printStackTrace();
            }

        }
        //run方法中,开始读取原始文件,封装event,发送给channel,并更新偏移量。如此一直循环下去
        @Override
        public void run() {
            while(flag) {
                try {
                    //读取原始文件数据
                    String line = accessFile.readLine();
                    //判断读取到的一行内容是否为空,若为空代表暂时读取了文件所有的内容了,休眠一会
                    if(StringUtils.isNotEmpty(line)) {
                        //将line封装成event
                        //方法的第二个参数需要的是Charset类的对象,我们定义的charset是字符串,所以要经过下面
                        //所写那样,转换成对象
                        Event event = EventBuilder.withBody(line, Charset.forName(charset));
                        //发送给channel
                        channelProcessor.processEvent(event);
                        //获取读完原始文件后的偏移量
                        offset = accessFile.getFilePointer();
                        //将偏移量更新到偏移量文件
                        //第一个参数需要一个文件对象,第二个参数是要写到文件中的数据(String型)
                        FileUtils.writeStringToFile(offsetFile,offset+"");
                    } else {
                        //没有读取到原始文件内容,休眠一会,休眠时间是一个间隔时间
                        try {
                            Thread.sleep(interval);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    @Override//关闭资源
    public synchronized void stop() {
        if (!Thread.currentThread().isInterrupted()){
            try {
                Thread.currentThread().wait(5000);
                //关闭上面的循环,使用FileRunner类的对象调用set方法,将flag的值改为false
                fileRunner.setFlag(false);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        super.stop();
    }
}

将代码打成jar包,上传到flume下的lib文件夹中
我的原始的数据文件在:flume/data/gt.txt
我的存储偏移量的文件在:flume/data/offset.txt
编写的conf配置文件内容是:

a1.sources = r1
a1.channels = c1

a1.sources.r1.type = DataToKafka.SourceMy
a1.sources.r1.filepath = /opt/module/flume/data/gt.txt
a1.sources.r1.offsetpath = /opt/module/flume/data/offset.txt
a1.sources.r1.charset = UTF-8
a1.sources.r1.interval = 5000


a1.channels.c1.type = org.apache.flume.channel.kafka.KafkaChannel
a1.channels.c1.kafka.bootstrap.servers = hadoop101:9092,hadoop102:9092,hadoop103:9092
a1.channels.c1.kafka.topic = gt
a1.channels.c1.kafka.consumer.group.id = gt-consumer

a1.sources.r1.channels = c1

将zookeeper启动,再将kafka启动
开启flume的监控命令

bin/flume-ng agent --conf conf/ --name a1 --conf-file job/flume-mysource-kafka.conf -Dflume.root.logger=INFO,console

flume-mysource-kafka.conf是我的conf配置文件的名称
可以在kafka中开启一个消费者,来消费指定的topic的内容,来检测是否成功将数据传到kafka中,也可以直接在kafka下的logs文件夹中找到指定的topic,进入topic文件夹中,查看以.log结尾的文件是否有数据

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值