flume与kafka

Flume:Flume 是管道流方式,提供了很多的默认实现,让用户通过参数部署,及扩展API。
Kafka:Kafka是一个可持久化的分布式的消息队列。
Flume:可以使用拦截器实时处理数据。这些对数据屏蔽或者过量是很有用的。
Kafka:需要外部的流处理系统才能做到。

选择方式

flume更适合流式数据的处理与向hdfs存储文件。
kafka更适合被多种类型的消费者消费的场景

用kafka代替flume的source与channel

官方文档

1.一个完整的flume配置文件

# example.conf: A single-node Flume configuration

# 代理的名字
a1.sources = r1
a1.sinks = k1
a1.channels = c1

# source的设置
a1.sources.r1.type = netcat
a1.sources.r1.bind = localhost
a1.sources.r1.port = 44444

# sink的设置
a1.sinks.k1.type = logger

# 使用缓存作为channel
a1.channels.c1.type = memory
a1.channels.c1.capacity = 1000
a1.channels.c1.transactionCapacity = 100

#建立source与channel的连接
a1.sources.r1.channels = c1
a1.sinks.k1.channel = c1

2.自定义source需要修改的地方

设置flume的配置文件。指定自己source信息、kafka的信息。
source为自己写的api。

#source类型的指定
tier1.sources.source1.type = org.apache.flume.source.kafka.KafkaSource
#channel的指定
tier1.sources.source1.channels = channel1
#批次大小(写入消息的数量)
tier1.sources.source1.batchSize = 5000
#批处理持续时间毫秒
tier1.sources.source1.batchDurationMillis = 2000
#kafka端口服务
tier1.sources.source1.kafka.bootstrap.servers = localhost:9092
#主题,可运行后自行创建
tier1.sources.source1.kafka.topics = test1, test2
#设置消费者组的id
tier1.sources.source1.kafka.consumer.group.id = custom.g.id

3.自定义channel要修改的地方

此处选择kafka作为channel

#channel的类型
a1.channels.channel1.type = org.apache.flume.channel.kafka.KafkaChannel
#kafka服务端口
a1.channels.channel1.kafka.bootstrap.servers = kafka-1:9092,kafka-2:9092,kafka-3:9092
#使用的主题
a1.channels.channel1.kafka.topic = channel1
#组id
a1.channels.channel1.kafka.consumer.group.id = flume-consumer

自定义flume的source,设置相应的参数

自定义的source。需要定制的内容为
属性

  • 要监控的文件
  • 偏移量的记录文件
  • 每次传递的任务数量,连接等待时间,可以在代码中设置默认值。

方法
由于自定义的类实现了如下的操作

extends AbstractSource implements EventDrivenSource, Configurable

要实现已有的方法来完成代码

  • configure
    设置初始化属性(完成对属性的赋值)
  • start
    开启一个线程来运行FileRunner
  • FileRunner
    完成最复杂的操作,获取偏移量,将内容包装为event发送给channel,以及偏移量的更新
    实现了runable,代码中没有主方法,需要自定义线程池来完成操作。
  • stop
    停止FileRunner,终止程序。
package flume;

import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.flume.*;
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 org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

/**
 * Created by Administrator on 2019/11/12.
 */
public class MySource extends AbstractSource implements EventDrivenSource, Configurable  {

    /*监听的文件*/
    private String filePath;

    /*记录读取偏移量的文件*/
    private String posiFile;

    /*若读取文件暂无内容,则等待数秒*/
    private Long interval;

    /*读写文件的字符集*/
    private String charset;

    /*读取文件内容的线程*/
    private FileRunner fileRunner;

    /*线程池*/
    private ExecutorService executor;

    private static final Logger logger = LoggerFactory.getLogger(MySource.class);

    /**
     * 初始化配置文件内容
     *
     * @param context
     */
    public void configure(Context context) {
        filePath = context.getString("filePath");
        posiFile = context.getString("posiFile");
        interval = context.getLong("interval", 2000L);
        charset = context.getString("charset", "UTF-8");
    }

    @Override
    public synchronized void start() {
        //启动一个线程,用于监听对应的日志文件
        //创建一个线程池
        executor = Executors.newSingleThreadExecutor();
        //用channelProcessor发送数据给channel
        ChannelProcessor channelProcessor = super.getChannelProcessor();
        fileRunner = new FileRunner(filePath, posiFile, interval, charset, channelProcessor);
        executor.submit(fileRunner);
        super.start();
    }

    @Override
    public synchronized void stop() {
        fileRunner.setFlag(Boolean.FALSE);
        while (!executor.isTerminated()) {
            logger.debug("waiting for exec executor service to stop");
            try {
                executor.awaitTermination(500, TimeUnit.MILLISECONDS);
            } catch (InterruptedException e) {
                e.printStackTrace();
                logger.debug("Interrupted while waiting for executor service to stop,Just exiting.");
                Thread.currentThread().interrupt();
            }
        }
        super.stop();
    }

    public static class FileRunner implements Runnable {
        private Long interval;

        private String charset;

        private Long offset = 0L;

        private File pFile;

        private RandomAccessFile raf;

        private ChannelProcessor channelProcessor;

        private Boolean flag = Boolean.TRUE;

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

        public FileRunner(String filePath, String posiFile, Long interval, String charset, ChannelProcessor channelProcessor) {
            this.interval = interval;
            this.charset = charset;
            this.channelProcessor = channelProcessor;

            //1、判断是否有偏移量文件,有则读取偏移量,没有则创建
            pFile = new File(posiFile);
            if (!pFile.exists()) {
                try {
                    pFile.createNewFile();
                } catch (IOException e) {
                    e.printStackTrace();
                    logger.error("create position file error!", e);
                }
            }
            //2、判断偏移量中的文件内容是否大于0
            try {
                String offsetStr = FileUtils.readFileToString(pFile, this.charset);
//          3、如果偏移量文件中有记录,则将内容转换为Long
                if (StringUtils.isNotBlank(offsetStr)) {
                    offset = Long.parseLong(offsetStr);
                }
//           4、如果有偏移量,则直接跳到文件的偏移量位置
                raf = new RandomAccessFile(filePath, "r");
//              跳到指定的位置
                raf.seek(offset);
            } catch (IOException e) {
                e.printStackTrace();
                logger.error("read position file error!", e);
            }
        }

        public void run() {
            //监听文件
            while (flag) {
//            读取文件中的内容
                String line = null;
                try {
                    line = raf.readLine();
                    if (StringUtils.isNotBlank(line)) {
//                      把数据打包成Event,发送到Channel
                        line = new String(line.getBytes("ISO-8859-1"), "UTF-8");
                        Event event = EventBuilder.withBody(line.getBytes());
                        channelProcessor.processEvent(event);
                        //更新偏移量文件,把偏移量写入文件
                        offset = raf.getFilePointer();
                        FileUtils.writeStringToFile(pFile, offset.toString());
                    } else {
                        try {
                            Thread.sleep(interval);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                            logger.error("thread sleep error", e);
                        }
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

在配置文件中设置任务与属性

topic,source.type,api中设置的没有默认值的参数。

#定义agent名, source、channel的名称
a0.sources = r1
a0.channels = c1
 
#具体定义source
a0.sources.r1.type = com.uu.MyFlume
a0.sources.r1.filePath = /opt/module/flume/data/logs.txt
a0.sources.r1.posiFile = /opt/module/flume/data/log.txt
 
 
a0.channels.c1.type = org.apache.flume.channel.kafka.KafkaChannel
a0.channels.c1.brokerList = hadoop101:9092,hadoop102:9092,hadoop103:9092
a0.channels.c1.zookeeperConnect=hadoop101:2181,hadoop102:2181,hadoop103:2181
a0.channels.c1.topic = second
#false表示是以纯文本的形式写进入的,true是以event的形式写进入的,以event写进入时,会出现乱码, 默认是true
a0.channels.c1.parseAsFlumeEvent = false
a0.sources.r1.channels = c1

启动,zookeeper一个消费端查看。

本设置表现为检测文件的尾端变化。