【问题标题】:Reading multiple .gz file and identifying which row belongs to which file读取多个.gz文件并识别哪一行属于哪个文件
【发布时间】:2017-01-27 11:36:01
【问题描述】:

我正在读取多个 .gz 文件以使用 google 数据流进行处理。数据的最终目的地是 BigQuery。 BigQuery 表在 .gz 文件中的 csv 文件中的每一列都有专用列。 BQ 表中还有一列作为 file_name,它给出了该记录所属的文件名。我正在使用 TextIO.Read 读取文件并对其进行 ParDo 转换。在 DoFn 中有一种方法可以识别传入字符串所属的文件名。

我的代码如下所示:

PCollection<String> logs = pipeline.apply(TextIO.Read.named("ReadLines")
                .from("gcs path").withCompressionType(TextIO.CompressionType.AUTO));

PCollection<TableRow> formattedResults = logs.apply(ParDo.named("Format").of(new DoFn<String, TableRow>() {}

更新 1:

我现在尝试如下:

        PCollection<String> fileNamesCollection // this is collection of file names
        GcsIOChannelFactory channelFactory = new GcsIOChannelFactory(options.as(GcsOptions.class));
        PCollection<KV<String,String>> kv = fileNamesCollection.apply(ParDo.named("Format").of(new DoFn<String, KV<String,String>>() {
                private static final long serialVersionUID = 1L;

                @Override
                public void processElement(ProcessContext c) throws Exception {
                    ReadableByteChannel readChannel = channelFactory.open(c.element());
                    GZIPInputStream gzip = new GZIPInputStream(Channels.newInputStream(readChannel));
                    BufferedReader br = new BufferedReader(new InputStreamReader(gzip));

                    String line = null;
                    while ((line = br.readLine()) != null) {
                        c.output(KV.of(c.element(), line));
                    }
                }
        }));

但是当我运行这个程序时,我发现 channelFactory 是不可序列化的,我有任何实现 Serializable 接口的通道工厂,可以在这里使用。

更新2:我终于能够执行程序并成功提交作业。感谢 jkff 的帮助。 以下是我的最终代码,我将其粘贴在这里,以便对其他人也有帮助。

        ProcessLogFilesOptions options = PipelineOptionsFactory.fromArgs(args).withValidation()
                .as(ProcessLogFilesOptions.class); // ProcessLogFilesOptions is a custom class
        DataflowWorkerLoggingOptions loggingOptions = options.as(DataflowWorkerLoggingOptions.class);
        loggingOptions.setDefaultWorkerLogLevel(Level.WARN);

        String jobName = "unique_job_name";
        options.as(BlockingDataflowPipelineOptions.class).setJobName(jobName);

        Pipeline pipeline = Pipeline.create(options);

        List<String> filesToProcess = new ArrayList<String>();
        for(String fileName : fileNameWithoutHrAndSuffix) { // fileNameWithoutHrAndSuffix has elements like Log_20160921,Log_20160922 etc
            filesToProcess.addAll((new GcsIOChannelFactory(options.as(GcsOptions.class))).match(LogDestinationStoragePath+fileName));
        }
        // at this time filesToProcess will have all logs files name as Log_2016092101.gz,Log_2016092102.gz,.........,Log_2016092201.gz,Log_2016092223.gz
        PCollection<String> fileNamesCollection = pipeline.apply(Create.of(filesToProcess));

        PCollection<KV<String,String>> kv = fileNamesCollection.apply(ParDo.named("Parsing_Files").of(new DoFn<String, KV<String,String>>() {
                private static final long serialVersionUID = 1L;
                @Override
                public void processElement(ProcessContext c) throws Exception {
                    // I have to create _options here because Options, GcsIOChannelFactory are non serializable
                    ProcessLogFilesOptions _options = PipelineOptionsFactory.as(ProcessLogFilesOptions.class);
                    GcsIOChannelFactory channelFactory = new GcsIOChannelFactory(_options.as(GcsOptions.class));
                    ReadableByteChannel readChannel = channelFactory.open(c.element());
                    GZIPInputStream gzip = new GZIPInputStream(Channels.newInputStream(readChannel));
                    BufferedReader br = new BufferedReader(new InputStreamReader(gzip));

                    String line = null;
                    while ((line = br.readLine()) != null) {
                        c.output(KV.of(c.element(), line));
                    }

                    br.close();
                    gzip.close();
                    readChannel.close();
                }
        }));

        // Performing reshuffling here as suggested
        PCollection <KV<String,String>> withFileName = kv.apply(Reshuffle.<String, String>of());

        PCollection<TableRow> formattedResults = withFileName
                .apply(ParDo.named("Generating_TableRow").of(new DoFn<KV<String,String>, TableRow>() {
                    private static final long serialVersionUID = 1L;

                    @Override
                    public void processElement(ProcessContext c) throws Exception {
                    KV<String,String> kv = c.element();
                    String logLine = kv.getValue();
                    String logFileName = kv.getKey();

                    // do further processing as you want here
        }));

        // Finally insert in BQ table the formattedResults

【问题讨论】:

    标签: google-cloud-dataflow


    【解决方案1】:

    现在,答案是否定的。如果您需要访问文件名,不幸的是,在这种情况下,您最好的选择是自己实现文件模式扩展和文件解析(作为ParDo)。您需要牢记以下几点:

    或者,您可以考虑编写自己的file-based source。但是,在这种特殊情况下(.gz 文件),我建议不要这样做,因为该 API 主要用于可以从任何偏移量随机访问读取的文件。

    【讨论】:

    • 我不是很清楚两点:文件自己解析和使用 GcsIoChannelFactory 打开流然后使用 GZipInputStream ,您能否分享一些代码示例以供参考
    • 我刚刚用更新 1 更新了我的问题,使用 GcsIOChannelFactory 存在问题,因为它不可序列化。
    • 正确,它不可序列化,因此您不应将其用作 DoFn 中的成员变量 - 既不是显式的,也不是通过匿名类中的捕获隐式使用的。只需将其设为局部变量即可。另外,请记住关闭资源 - 您当前答案中的代码不会关闭它们,它最终会用完文件描述符并崩溃。
    • 感谢@jkff,我能够成功提交作业并处理日志文件。我已在 Update 2 中粘贴了我的完整代码。我只有一个问题,因为 options 和 GcsIoChannelFactory 是不可序列化的,并且 GcsIoChannelFactory 没有空构造函数,所以我必须创建另一个 Options 作为 _options 以用于实例化 GcsIoChannelFactory 。这种方法好吗,我希望_options没有任何内部影响?
    • 是的,我觉得很好!
    猜你喜欢
    • 1970-01-01
    • 2020-06-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-06-10
    • 2015-11-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多