用Apache Beam顺序读取文件和文件夹

时间:2019-06-05 04:45:56

标签: apache-beam

我有一个year/month/day/hour/*类型的文件夹结构,并且我希望Beam能够按时间顺序将其作为不受限制的来源来读取。具体来说,这意味着在记录的第一个小时中读取所有文件,并添加它们的内容以进行处理。然后,添加下一个小时要处理的文件内容,直到当前等待新文件到达最新year/month/day/hour文件夹的时间为止。

有可能用apache beam吗?

1 个答案:

答案 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"

结果:

enter image description here

完整code

让我知道它是如何工作的。这只是一个开始的示例,您可能需要调整窗口和触发策略,延迟等以适合您的用例