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

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

package DataCollect;

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,用来保证不重复消费
* 原始日志文件
* 偏移量文件
* 编码格式
* 睡眠时间
* */

/*
* flume1.6及以前版本
* 如果想监控目录中的多个文件
* 文件的目录
* Filelisten类
* 拿出改名字的文件名
* 变化的文件
* 删除的文件
* 新增的文件
* */
//要继承抽象的AbstractSource类,然后要实现EventDrivenSource、Configurable接口
public class MySource extends AbstractSource implements EventDrivenSource, Configurable {
    //定义成员属性
    //定义原始文件路径
    private String filepath;
    //定义偏移量文件路径
    private String offsetpath;
    //定义编码集
    private String charset;
    //定义间隔时间
    private Long interval;

    //定义线程池
    private ExecutorService executor;

    //filerunner对象
    private fileRunner fileRunner;

    //加载采集方案(配置文件)
    //利用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()
        //可缓冲线程池
//        Executors.newCachedThreadPool()
        //单线程的线程池
        //因为还要用线程去监控文件,所以要把executor定义在公共属性中
        executor = Executors.newSingleThreadExecutor();
        //创建一个channel发送的类的对象
        ChannelProcessor channelProcessor = getChannelProcessor();
        //创建对象
        fileRunner = new fileRunner(filepath,offsetpath,charset,interval,channelProcessor);
        //线程要去执行命令
        executor.submit(fileRunner);
        super.start();
    }

    //定义一个类实现Runnable接口
    public class fileRunner implements Runnable{
        //定义成员属性
        private String filepath;
        private String offsetpath;
        private String charset;
        private Long interval;

        //定义原始日志文件的封装类
        private RandomAccessFile accessFile;

        //定义channel发送类,作用就是将封装的event反送给channel
        private ChannelProcessor channelProcessor;

        //定义偏移量
        private Long offset=0L;

        //定义偏移量文件类
        private File offsetFile;

        //定义一个标志
        private boolean flag;

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

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

            //判断偏移量文件是否存在,若不存在,则创建一个偏移量文件,因为这个偏移量文件在run方法中也要用,所以定义到成员属性中
            //封装成File类
            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方法中需要使用它去一行一行的读取原始文件,所以定义到成员属性中
                //将文件封装成RandomAccessFile
                accessFile = new RandomAccessFile(filepath, "r");//第一个参数是指定文件对象,第二个参数代表“只读”
                //调用seek方法,传入刚才获取到的偏移量,代表着我们已经将文件的开始读取位置,搞到了偏移量的位置
                accessFile.seek(offset);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        //run方法中,开始读取原始文件,封装event,发送给channel,并更新偏移量。如此一直循环下去
        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文件夹中
我的原始的数据文件在:/opt/module/flume/data/gaotie.txt
我的存储偏移量的文件在:/opt/module/flume/data/postfile
我的conf配置文件名为:flume-file-kafka2.conf
编写的conf配置文件内容是:

a1.sources=r1
a1.channels=c1

a1.sources.r1.type=DataCollect.MySource
a1.sources.r1.filepath=/opt/module/flume/data/gaotie.txt
a1.sources.r1.offsetpath=/opt/module/flume/data/postfile
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 = myhadoop101:9092,myhadoop102:9092,myhadoop103:9092
a1.channels.c1.kafka.topic = gaotie
a1.channels.c1.kafka.consumer.group.id = gaotie-consumer

a1.sources.r1.channels = c1

然后先启动zookeeper

bin/zkServer.sh start

再启动kafka

bin/kafka-server-start.sh config/server.properties &

最后开启flume的监控命令

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

可以开启kafka的消费者命令

bin/kafka-console-consumer.sh --zookeeper myhadoop101:2181 --topic gaotie --from-beginning

来消费指定的topic的内容,来检测是否成功将数据传到kafka中,也可以直接在kafka下的logs文件夹中找到指定的topic,进入topic文件夹中,查看以.log结尾的文件是否有数据

发布了189 篇原创文章 · 获赞 13 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/NewBeeMu/article/details/103059967