我有一个year/month/day/hour/*
类型的文件夹结构,并且我希望Beam能够按时间顺序将其作为不受限制的来源来读取。具体来说,这意味着在记录的第一个小时中读取所有文件,并添加它们的内容以进行处理。然后,添加下一个小时要处理的文件内容,直到当前等待新文件到达最新year/month/day/hour
文件夹的时间为止。
有可能用apache beam吗?
答案 0 :(得分:0)
所以我要做的是根据文件路径向每个元素添加时间戳。作为测试,我使用了以下示例。
首先,如this answer中所述,可以使用FileIO
连续匹配文件模式。根据您的用例,这将有帮助,一旦您完成了回填,您就希望继续读取同一作业中的新到达文件。在这种情况下,我提供了gs://BUCKET_NAME/data/**
,因为我的文件就像gs://BUCKET_NAME/data/year/month/day/hour/filename.extension
:
p
.apply(FileIO.match()
.filepattern(inputPath)
.continuously(
// Check for new files every minute
Duration.standardMinutes(1),
// Never stop checking for new files
Watch.Growth.<String>never()))
.apply(FileIO.readMatches())
观看频率和超时时间可以随意调整。
然后,在下一步中,我们将接收匹配的文件。我将使用ReadableFile.getMetadata().resourceId()
获取完整路径,并用"/"
对其进行拆分,以建立相应的时间戳。我将其四舍五入为小时,此处不考虑时区校正。使用readFullyAsUTF8String
,我们将读取整个文件(如果整个文件不适合放入内存,请小心,建议根据需要将您的输入分片)并将其分成几行。使用ProcessContext.outputWithTimestamp
,我们将向下游发送文件名和行的KV(不再需要文件名,但这将有助于查看每个文件的来源)以及从路径派生的时间戳。请注意,我们正在将时间戳“倒退”,以免混淆水印启发式方法,并且您会收到诸如以下消息:
无法使用时间戳为2019-03-17T00:00:00.000Z的输出。输出时间戳必须不早于当前输入的时间戳(2019-06-05T15:41:29.645Z)减去允许的时滞(0毫秒)。有关更改允许的偏斜的详细信息,请参见DoFn#getAllowedTimestampSkew()Javadoc。
要解决此问题,我将getAllowedTimestampSkew
设置为Long.MAX_VALUE
,但要考虑到已弃用该设置。验证码:
.apply("Add Timestamps", ParDo.of(new DoFn<ReadableFile, KV<String, String>>() {
@Override
public Duration getAllowedTimestampSkew() {
return new Duration(Long.MAX_VALUE);
}
@ProcessElement
public void processElement(ProcessContext c) {
ReadableFile file = c.element();
String fileName = file.getMetadata().resourceId().toString();
String lines[];
String[] dateFields = fileName.split("/");
Integer numElements = dateFields.length;
String hour = dateFields[numElements - 2];
String day = dateFields[numElements - 3];
String month = dateFields[numElements - 4];
String year = dateFields[numElements - 5];
String ts = String.format("%s-%s-%s %s:00:00", year, month, day, hour);
Log.info(ts);
try{
lines = file.readFullyAsUTF8String().split("\n");
for (String line : lines) {
c.outputWithTimestamp(KV.of(fileName, line), new Instant(dateTimeFormat.parseMillis(ts)));
}
}
catch(IOException e){
Log.info("failed");
}
}}))
最后,我进入1小时的FixedWindows
并记录结果:
.apply(Window
.<KV<String,String>>into(FixedWindows.of(Duration.standardHours(1)))
.triggering(AfterWatermark.pastEndOfWindow())
.discardingFiredPanes()
.withAllowedLateness(Duration.ZERO))
.apply("Log results", ParDo.of(new DoFn<KV<String, String>, Void>() {
@ProcessElement
public void processElement(ProcessContext c, BoundedWindow window) {
String file = c.element().getKey();
String value = c.element().getValue();
String eventTime = c.timestamp().toString();
String logString = String.format("File=%s, Line=%s, Event Time=%s, Window=%s", file, value, eventTime, window.toString());
Log.info(logString);
}
}));
对我来说,它可以与.withAllowedLateness(Duration.ZERO)
一起使用,但是根据设置的顺序,可能会有所不同。请记住,该值太高会导致窗口打开时间更长,并使用更多持久性存储。
我设置了$BUCKET
和$PROJECT
变量,我只上传了两个文件:
gsutil cp file1 gs://$BUCKET/data/2019/03/17/00/
gsutil cp file2 gs://$BUCKET/data/2019/03/18/22/
并使用以下命令运行作业:
mvn -Pdataflow-runner compile -e exec:java \
-Dexec.mainClass=com.dataflow.samples.ChronologicalOrder \
-Dexec.args="--project=$PROJECT \
--path=gs://$BUCKET/data/** \
--stagingLocation=gs://$BUCKET/staging/ \
--runner=DataflowRunner"
结果:
完整code
让我知道它是如何工作的。这只是一个开始的示例,您可能需要调整窗口和触发策略,延迟等以适合您的用例