说明
最近有需求需要实时监控HDFS指定目录,然后对新增文件的每一行进行处理。
采用了Flink DataStream API的readFile
方法来实现。
文件都是.gz
结尾的,也会存在脏文件进来,所以需要对文件名进行过滤,于是就用FilePathFilter
。
在不过滤之前,可以正常监听目录;但是加了过滤后就不能监听了。
纳闷了好一会,后来点进去仔细看了看源码,才发现是使用FilePathFilter
的姿势有问题。
这里的FilePathFilter
居然是用来过滤目录的。。。实现代码如下:
代码摘自
org.apache.flink.streaming.api.functions.source.ContinuousFileMonitoringFunction
private Map<Path, FileStatus> listEligibleFiles(FileSystem fileSystem, Path path) throws IOException {
final FileStatus[] statuses;
try {
// 列出目录下的所有文件状态
statuses = fileSystem.listStatus(path);
} catch (IOException e) {
}
if (statuses == null) {
。。。。
} else {
Map<Path, FileStatus> files = new HashMap<>();
// 处理新文件
for (FileStatus status : statuses) {
// 如果不是目录
if (!status.isDir()) {
Path filePath = status.getPath();
long modificationTime = status.getModificationTime();
// 看他的修改时间是否应该被忽略
if (!shouldIgnore(filePath, modificationTime)) {
files.put(filePath, status);
}
// 如果是目录。先判断是否开启了递归扫描;
// 再判断目录是否应该被接收。其中就是去用`FilePathFilter`来过滤的
} else if (format.getNestedFileEnumeration() && format.acceptFile(status)){
files.putAll(listEligibleFiles(fileSystem, status.getPath()));
}
}
return files;
}
}
// 上面用到的acceptFile方法
public boolean acceptFile(FileStatus fileStatus) {
final String name = fileStatus.getPath().getName();
return !name.startsWith("_")
&& !name.startsWith(".")
&& !filesFilter.filterPath(fileStatus.getPath());
}
源码中FilePathFilter
类的filterPath
中也确实写了是过滤目录的:
// org.apache.flink.api.common.io.FilePathFilter
// 如果在处理目录时,需要忽略给定的filePath,则返回true
public abstract boolean filterPath(Path filePath);
所以是不支持过滤文件名的,只支持过滤目录名。
我最开始是过滤了.gz结尾的,由于没有一个目录是以.gz结尾的,导致一个文件都扫描不到。。。
总结
- Flink实时监控目录下的文件,官方提供的Source不支持过滤文件名,需要我们自己实现
- Flink监控文件这块比Spark鸡肋很多,Spark Streaming这块实现还不错