一、
在使用flume采集日志时,可以通过flume进行监控某一个文件把生产的数据传输给指定的sink,但是有时使用官方给的source不能解决我们的需求或者有一些缺点,就比如日志收集,source是exec类型,通过tail -F 进行监控,但是如果某段时间flume所在机器宕机了,那么当重新启动后,在去监控时,会导致有数据丢失,不是接着上一次的数据继续进行读取,因此针对这种情况时可能需要我们自定一个source,记录偏移量,每次都都是接着上次继续读
二
下面就是具体实现的代码
再写代码时可以参照官方给的source的源码进行编写,比如ExecSource
flume的生命周期: 先执行构造器,再执行 config方法 --> start方法 --> processor.process–> stop
读取配置文件:(配置读取的文件内容:读取那个文件,编码及、偏移量写到那个文件,多长时间检测一下文件是否有新内容
import org.apache.commons.io.FileUtils;
import org.apache.flume.Context;
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 org.apache.flume.source.ExecSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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;
import java.util.concurrent.TimeUnit;
//自定义source,记录偏移量
/*
flume的生命周期: 先执行构造器,再执行 config方法 -> start方法-》 processor.process
1、读取配置文件:(配置读取的文件内容:读取那个文件,编码及、偏移量写到那个文件,多长时间检测一下文件是否有新内容
)
*/
public class TailFileSource extends AbstractSource implements EventDrivenSource, Configurable {
//记录日志
private static final Logger logger = LoggerFactory.getLogger(ExecSource.class);
private String filePath;
private String charset;
private String positionFile;
private long interval;
private ExecutorService executor;
private FileRunnable fileRunnable;
//读取配置文件(flume在执行一次job时定义的配置文件)
@Override
public void configure(Context context) {
//(如果在flume的job的配置文件中不修改,就是用这些默认的配置)
//读取哪个文件
filePath = context.getString("filePath");
//默认使用utf-8
charset = context.getString("charset","UTF-8");
//把偏移量写到哪
positionFile = context.getString("positionFile");
//指定默认每个一秒 去查看一次是否有新的内容
interval = context.getLong("interval", 1000L);
}
/*
*1、创建一个线程来监听一个文件
*/
@Override
public synchronized void start() {
//创建一个单线程的线程池
executor = Executors.newSingleThreadExecutor();
//获取一个ChannelProcessor
final ChannelProcessor channelProcessor = getChannelProcessor();
fileRunnable = new FileRunnable(filePath,charset,positionFile,interval,channelProcessor);
//提交到线程池中
executor.submit(fileRunnable);
//调用父类的方法
super.start();
}
@Override
public synchronized void stop() {
//停止
fileRunnable.setFlag(false);
//停止线程池
executor.shutdown();
while (!executor.isTerminated()){
logger.debug("Waiting for filer exec executor service to stop");
try {
//等500秒在停
executor.awaitTermination(500, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
logger.debug("InterutedExecption while waiting for exec executor service" +
" to stop . Just exiting");
e.printStackTrace();
}
}
super.stop();
}
private static class FileRunnable implements Runnable{
private String charset;
private long interval;
private long offset = 0L;
private ChannelProcessor channelProcessor;
private RandomAccessFile raf;
private boolean flag = true;
private File posFile;
/*
先于run方法执行,构造器只执行一次
先看看有没有偏移量,如果有就接着读,如果没有就从头开始读
*/
public FileRunnable(String filePath, String charset, String positionFile, long interval, ChannelProcessor channelProcessor) {
this.charset = charset;
this.interval = interval;
this.channelProcessor = channelProcessor;
//读取偏移量, 在postionFile文件
posFile = new File(positionFile);
if(!posFile.exists()){
//如果不存在就创建一个文件
try {
posFile.createNewFile();
} catch (IOException e) {
e.printStackTrace();
logger.error("创建保存偏移量的文件失败:",e);
}
}
try {
//读取文件的偏移量
String offsetString = FileUtils.readFileToString(posFile);
//以前读取过
if(!offsetString.isEmpty()&&null!=offsetString&&!"".equals(offsetString)){
//把偏移量穿换成long类型
offset = Long.parseLong(offsetString);
}
//按照指定的偏移量读取数据
raf = new RandomAccessFile(filePath,"r");
//按照指定的偏移量读取
raf.seek(offset);
} catch (IOException e) {
logger.error("读取保存偏移量文件时发生错误",e);
e.printStackTrace();
}
}
@Override
public void run() {
while (flag){
//读取文件中的新数据
try {
String line = raf.readLine();
if(line!=null){
//有数据进行处理,避免出现乱码
line = new String(line.getBytes("iso8859-1"),charset);
channelProcessor.processEvent(EventBuilder.withBody(line.getBytes()));
//获取偏移量,更新偏移量
offset = raf.getFilePointer();
//将偏移量写入到位置文件中
FileUtils.writeStringToFile(posFile,offset+"");
}else{
//没读到谁一会儿
Thread.sleep(interval);
}
//发给channle
//更新偏移量
//每个时间间隔读取一次
} catch (InterruptedException e) {
e.printStackTrace();
logger.error("read filethread Interrupted",e);
}catch (IOException e){
logger.error("read log file error",e);
}
}
}
public void setFlag(boolean flag) {
this.flag = flag;
}
}
}
三、
编写完以后,打成jar包,放到flume的lib下,conf的配置如下
#bin/flume-ng agent -n a1 -f /home/hadoop/a1.conf -c conf -Dflume.root.logger=INFO,console
#定义agent名, source、channel、sink的名称
a1.sources = r1
a1.channels = c1
a1.sinks = k1
#具体定义source,这里的type是自定义的source的类的全路径
a1.sources.r1.type = com.yt.flume.source.TailFileSource
#这里的参数名都和自定义类的参数一直
#读取哪个文件
a1.sources.r1.filePath = /home/bigdata/data/logs/access.txt
#偏移量保存的文件
a1.sources.r1.positionFile = /home/bigdata/data/logs/posi.txt
#时间间隔,每隔多久读取一次
a1.sources.r1.interval = 2000
#编码
a1.sources.r1.charset = UTF-8
#具体定义channel
a1.channels.c1.type = memory
a1.channels.c1.capacity = 1000
a1.channels.c1.transactionCapacity = 100
#具体定义sink
a1.sinks.k1.type = file_roll
a1.sinks.k1.sink.directory = /home/bigdata/data/logs/sinks
#组装source、channel、sink
a1.sources.r1.channels = c1
a1.sinks.k1.channel = c1
执行:
/home/bigdata/install/apache-flume-1.5.0-cdh5.3.6-bin/bin/flume-ng
agent --conf /home/bigdata/install/apache-flume-1.5.0-cdh5.3.6-bin/conf/ --name a1 --conf-file
/home/bigdata/install/apache-flume-1.5.0-cdh5.3.6-bin/job/tailFile.conf
-Dflume.root.logger==INFO,console