org.apache.flume.source.SpoolDirectorySource是flume的一个常用的source,这个源支持从磁盘中某文件夹获取文件数据。不同于其他异步源,这个源能够避免重启或者发送失败后数据丢失。flume可以监控文件夹,当出现新文件时会读取该文件并获取数据。当一个给定的文件被全部读入到通道中时,该文件会被重命名以标志已经完成。同时,该源需要一个清理进程来定期移除完成的文件。

  通道可选地将一个完成路径的原始文件插入到每个事件的hearder域中。在读取文件时,source缓存文件数据到内存中。同时,需要确定设置了bufferMaxLineLength选项,以确保该数据远大于输入数据中数据最长的某一行。

注意!!!channel只接收spooling directory中唯一命名的文件。如果文件名重复或文件在读取过程中被修改,则会有读取失败返回异常信息。这种场景下,同名的文件复制到这个目录时建议带唯一标示,比如时间戳。

一、configure(Context context)方法。代码如下:

public void configure(Context context) {
spoolDirectory = context.getString(SPOOL_DIRECTORY);
Preconditions.checkState(spoolDirectory != null,
"Configuration must specify a spooling directory"); completedSuffix = context.getString(SPOOLED_FILE_SUFFIX,
DEFAULT_SPOOLED_FILE_SUFFIX);
deletePolicy = context.getString(DELETE_POLICY, DEFAULT_DELETE_POLICY);
fileHeader = context.getBoolean(FILENAME_HEADER,
DEFAULT_FILE_HEADER);
fileHeaderKey = context.getString(FILENAME_HEADER_KEY,
DEFAULT_FILENAME_HEADER_KEY);
batchSize = context.getInteger(BATCH_SIZE,
DEFAULT_BATCH_SIZE);
inputCharset = context.getString(INPUT_CHARSET, DEFAULT_INPUT_CHARSET); ignorePattern = context.getString(IGNORE_PAT, DEFAULT_IGNORE_PAT);
trackerDirPath = context.getString(TRACKER_DIR, DEFAULT_TRACKER_DIR); deserializerType = context.getString(DESERIALIZER, DEFAULT_DESERIALIZER);
deserializerContext = new Context(context.getSubProperties(DESERIALIZER +
".")); // "Hack" to support backwards compatibility with previous generation of
// spooling directory source, which did not support deserializers
Integer bufferMaxLineLength = context.getInteger(BUFFER_MAX_LINE_LENGTH);
if (bufferMaxLineLength != null && deserializerType != null &&
deserializerType.equals(DEFAULT_DESERIALIZER)) {
deserializerContext.put(LineDeserializer.MAXLINE_KEY,
bufferMaxLineLength.toString());
} }

  1、spoolDirectory是监控目录,不能为空,没有默认值。这个source不具有监控子目录的功能,也就是不能递归监控。如果需要,这需要自己去实现,http://blog.csdn.net/yangbutao/article/details/8835563 这里有递归检测的实现;

  2、completedSuffix是文件读取完毕后给完成文件添加的标记后缀,默认是".COMPLETED";

  3、deletePolicy这是是否删除读取完毕的文件,默认是"never",就是不删除,目前只支持"never"和“IMMEDIATE”;

  4、fileHeader是否在event的Header中添加文件名,boolean类型

  5、fileHeaderKey这是event的Header中的key,value是文件名

  6、batchSize这个是一次处理的记录数,默认是100;

  7、inputCharset编码方式,默认是"UTF-8";

  8、ignorePattern忽略符合条件的文件名

  9、trackerDirPath被处理文件元数据的存储目录,默认".flumespool"

  10、deserializerType将文件中的数据序列化成event的方式,默认是“LINE”---org.apache.flume.serialization.LineDeserializer

  11、deserializerContext这个主要用在Deserializer中设置编码方式outputCharset和文件每行最大长度maxLineLength。

  

  二、start()方法。代码如下:

 public void start() {
logger.info("SpoolDirectorySource source starting with directory: {}",
spoolDirectory); ScheduledExecutorService executor =
Executors.newSingleThreadScheduledExecutor();
counterGroup = new CounterGroup(); File directory = new File(spoolDirectory);
try {
reader = new ReliableSpoolingFileEventReader.Builder()
.spoolDirectory(directory)
.completedSuffix(completedSuffix)
.ignorePattern(ignorePattern)
.trackerDirPath(trackerDirPath)
.annotateFileName(fileHeader)
.fileNameHeader(fileHeaderKey)
.deserializerType(deserializerType)
.deserializerContext(deserializerContext)
.deletePolicy(deletePolicy)
.inputCharset(inputCharset)
.build();
} catch (IOException ioe) {
throw new FlumeException("Error instantiating spooling event parser",
ioe);
} Runnable runner = new SpoolDirectoryRunnable(reader, counterGroup);
executor.scheduleWithFixedDelay(
runner, 0, POLL_DELAY_MS, TimeUnit.MILLISECONDS); super.start();
logger.debug("SpoolDirectorySource source started");
}

  1、构建了一个org.apache.flume.client.avro.ReliableSpoolingFileEventReader的对象reader;

  2、启动了一个每隔POLL_DELAY_MS(默认500,单位ms)执行一次SpoolDirectoryRunnable的进程;

  三、读取并发送event进程。代码如下:

 private class SpoolDirectoryRunnable implements Runnable {
private ReliableSpoolingFileEventReader reader;
private CounterGroup counterGroup; public SpoolDirectoryRunnable(ReliableSpoolingFileEventReader reader,
CounterGroup counterGroup) {
this.reader = reader;
this.counterGroup = counterGroup;
} @Override
public void run() {
try {
while (true) {
List<Event> events = reader.readEvents(batchSize);  //读取batchSize个记录
if (events.isEmpty()) {
break;
}
counterGroup.addAndGet("spooler.events.read", (long) events.size()); getChannelProcessor().processEventBatch(events);  //将events批量发送到channel
reader.commit();
}
} catch (Throwable t) {
logger.error("Uncaught exception in Runnable", t);
if (t instanceof Error) {
throw (Error) t;
}
}
}
}

  该进程实现了批量读取reader所指向的文件的数据,并发送到channel。

四、org.apache.flume.client.avro.ReliableSpoolingFileEventReader的构造方法首先是先尝试对spoolDirectory是否有创建文件、读、写、删除等权限;然后在构造"$spoolDirectory/.flumespool/.flumespool-main.meta"元数据文件

五、上面SpoolDirectoryRunnable.run方法中的List<Event> events = reader.readEvents(batchSize),是org.apache.flume.client.avro.ReliableSpoolingFileEventReader.readEvents(batchSize):

  public List<Event> readEvents(int numEvents) throws IOException {
if (!committed) {
if (!currentFile.isPresent()) {//为空,如果Optional包含非null的引用(引用存在),返回true
throw new IllegalStateException("File should not roll when " +
"commit is outstanding.");
}
logger.info("Last read was never committed - resetting mark position.");
currentFile.get().getDeserializer().reset();
} else {//已经committed成功
// Check if new files have arrived since last call
//Returns true if this holder contains a (non-null) instance
if (!currentFile.isPresent()) {//为空,获取下一个文件,初次调用
currentFile = getNextFile();
}
// Return empty list if no new files
if (!currentFile.isPresent()) {//为空,已经没有可读的文件了
return Collections.emptyList();
}
    //其它的说明是currentFile目前还在读
} EventDeserializer des = currentFile.get().getDeserializer();
List<Event> events = des.readEvents(numEvents);//添加event的body /* It's possible that the last read took us just up to a file boundary.
* If so, try to roll to the next file, if there is one. */
if (events.isEmpty()) {
retireCurrentFile(); //改名字
currentFile = getNextFile();//换下一个文件
if (!currentFile.isPresent()) {
return Collections.emptyList();
}
events = currentFile.get().getDeserializer().readEvents(numEvents);//继续读,添加event的body
} if (annotateFileName) {
String filename = currentFile.get().getFile().getAbsolutePath();
for (Event event : events) {
event.getHeaders().put(fileNameHeader, filename);//添加header
}
} committed = false;
lastFileRead = currentFile;
return events;
}

1,committed初始化时是true,所以第一次运行就是通过getNextFile()获取当前要去读的文件。如果是空就返回空值了。

2,使用deserializer(默认是org.apache.flume.serialization.LineDeserializer)的readEvents(numEvents)去批量读数据封装成event。

3,如获取的批量events为空,说明这个文件读完了,需要对这个读完的文件做个“删除”(retireCurrentFile()方法,在这也会删除元数据文件),就是根据deletePolicy(删除还是添加去读完毕后缀completedSuffix);但是这个本方法是有返回值的就是events,所以需要获取下一个文件,即再次运行getNextFile(),并events = currentFile.get().getDeserializer().readEvents(numEvents)

4,是否要对这些events的Header中添加文件名

5,committed = false;    lastFileRead = currentFile; 并返回events。

这个方法还有几点需要解释:

其一、就是committed参数,此参数关系到这一批量的event是否已经正确处理完毕。可以看到上面的5中所讲,每调用一次ReliableSpoolingFileEventReader.readEvents(batchSize)均会在最后将committed设置为false,但是在SpoolDirectoryRunnable.run()方法中也可以看出在调用readEvents方法后还会调用ReliableSpoolingFileEventReader.commit()方法,代码如下:

 /** Commit the last lines which were read. */
@Override
public void commit() throws IOException {
if (!committed && currentFile.isPresent()) {
currentFile.get().getDeserializer().mark();
committed = true;
}
}

这个方法说明满足两个条件就可以:一、向trackerFile写入读到的记录位置,mark()方法会将syncPosition写入trackerFile,而ResettableFileInputStream中的position用来暂存位置增加的,待到何时会syncPosition=position,这样是为了防止出现异常时用于恢复丢失的数据;二、将committed  = true。两个条件:一个是committed=false,这个执行完readEvents最后会置为false;二、currentFile“非空”,代表有正在读的文件。如果committed在readEvents中开始时为false,说明:一、event提交到channel时出现了问题,没有执行reader.commit;二、currentFile已经“为空”,说明没有可以读的文件。这两点也体现在readEvents开始部分,committed=false时,如果没有可读文件就会抛出异常File should not roll when commit is outstanding.";如果是在提交到channel时出问题会通过currentFile.get().getDeserializer().reset()重新撤回到上次正确提交channel的位置,这样可以使得不丢失数据。

其二、就是getNextFile()方法。这个方法会首先过滤检测目录的子目录(也就是不能递归)、隐藏文件(以"."开头的文件)、已经读完的文件(有completedSuffix后缀的)、符合ignorePattern的文件;然后将过滤后的文件按时间的先后顺序排序,再创建一个新的对应的元数据文件;构造一个读取文件的输入流ResettableFileInputStream,并将此输入流作为参数传递给deserializer,最终返回一个Optional.of(new FileInfo(nextFile, deserializer));

其三、就是LineDeserializer)的readEvents(numEvents)方法。这个方法会多次(numEvents)调用LineDeserializer(默认)的readLine()获取一行数据封装成event。readLine()会通过org.apache.flume.serialization.ResettableFileInputStream.readChar()不断的去获取数据,读完正行后判断每行的长度是否超过规定值maxLineLength。readChar()方法除了不断读取一个字符外,还会记下字符的位置,等待将位置写入元数据文件中(通过deserializer.mark()写入)

Flume-NG源码阅读之SpoolDirectorySource(原创)的更多相关文章

  1. Flume-NG源码阅读之Interceptor(原创)

    有的时候希望通过Flume将读取的文件再细分存储,比如讲source的数据按照业务类型分开存储,具体一点比如类似:将source中web.wap.media等的内容分开存储:比如丢弃或修改一些数据.这 ...

  2. Flume-NG源码阅读之SourceRunner,及选择器selector和拦截器interceptor的执行

    在AbstractConfigurationProvider类中loadSources方法会将所有的source进行封装成SourceRunner放到了Map<String, SourceRun ...

  3. JDK1.8源码阅读系列之四:HashMap (原创)

    本篇随笔主要描述的是我阅读 HashMap 源码期间的对于 HashMap 的一些实现上的个人理解,用于个人备忘,有不对的地方,请指出- 接下来会从以下几个方面介绍 HashMap 源码相关知识: 1 ...

  4. [原创]chromium源码阅读-进程间通信IPC.消息的接收与应答

    chromium源码阅读-进程间通信IPC.消息的接收与应答   chromium源码阅读-进程间通信IPC.消息的接收与应答 介绍 chromium进程间通信在win32下是通过命名管道的方式实现的 ...

  5. [PHP源码阅读]explode和implode函数

    explode和implode函数主要用作字符串和数组间转换的操作,比如获取一段参数后根据某个字符分割字符串,或者将一个数组的结果使用一个字符合并成一个字符串输出.在PHP中经常会用到这两个函数,因此 ...

  6. ng2048源码阅读

    ng2048源码阅读 Tutorial: http://www.ng-newsletter.com/posts/building-2048-in-angularjs.html Github: http ...

  7. PHP源码阅读strtr

    strtr 转换字符串中特定的字符,但是这个函数使用的方式多种. echo strtr('hello world', 'hw', 'ab'); // 第一种 aello borld echo strt ...

  8. TiDB 源码阅读系列文章(一)序

    原创: 申砾 PingCAP  2018-02-28 在 TiDB DevCon2018 上,我们对外宣布了 TiDB 源码阅读分享活动,承诺对外发布一系列文章以及视频帮助大家理解 TiDB 源码.大 ...

  9. Pytorch版本yolov3源码阅读

    目录 Pytorch版本yolov3源码阅读 1. 阅读test.py 1.1 参数解读 1.2 data文件解析 1.3 cfg文件解析 1.4 根据cfg文件创建模块 1.5 YOLOLayer ...

随机推荐

  1. BeyondCompare

    BeyondCompare可对文本,照片,文件夹,注册表等等进行比较 在git看不清楚版本和改动项的时候,直接将生产上的包拉下来和即将发布的包,作对比.

  2. django不返回QuerySets的API

    以下的方法不会返回QuerySets,但是作用非常强大,尤其是粗体显示的方法,需要背下来. 方法名 解释 get() 获取单个对象 create() 创建对象,无需save() get_or_crea ...

  3. npm 安装文件 运行报错 %1 is not a valid Win32 application

    安装了那个模板出了错报这样的错误 “%1 is not a valid Win32 application” 你就除那个模板新安装. 如下例: 运行 npm install -g @angular/c ...

  4. Unity 代码优化

    1.不用的代码删除掉,因为即使不用的代码也会 IL2Cpp. 2.MonoBehaviour 的方法是空方法,特别是Update方法,删除掉,会有性能消耗. 3.Unity 中 override 的方 ...

  5. 如何配置Smarty模板

    <?php //首先包含Smarty类文件 include_once('Smarty/Smarty.class.php'); //实例化Smarty类文件 $smarty=new Smarty( ...

  6. select exists 的应用一例

    当遇到多层exists的时候,事情就变得开始复杂了.我们来看看这个例子吧 假设有三张表 选课表:学号.课程号 学生表:学号.姓名 课程表:课程号.课程名 请选出选了所有课程的学生 SELECT 姓名 ...

  7. Ant Man CodeForces - 704B (图论,贪心)

    大意: 给N个点,起点S终点T,每个点有X,A,B,C,D,根据I和J的X坐标可得I到J的距离计算公式 |xi - xj| + ci + bj seconds if j< i |xi - xj| ...

  8. php数组转化为字符串

    1.函数explode();  这个是字符串转化为数组 , implode() ;这个是数组转化为字符串. $array=explode(separator,$string); $string=imp ...

  9. Superset

    Superset是一款可自助.可交互,可视化非常不错的产品 Superset is a data exploration platform designed to be visual, intuiti ...

  10. SVN 创建分支

    TortoiseSVN Client创建分支: