有状态的ParDo不能使用Dataflow Runner

时间:2017-03-02 19:47:02

标签: google-cloud-dataflow dataflow apache-beam

基于Javadocs和https://beam.apache.org/blog/2017/02/13/stateful-processing.html上的博客文章,我尝试使用一个简单的重复数据删除示例,使用2.0.0-beta-2 SDK从GCS读取一个文件(包含一个jsons列表,每个包含一个user_id字段)然后通过管道运行它,如下所述。

输入数据包含大约146K个事件,其中只有50个事件是唯一的。整个输入大约50MB,应该比2分钟的固定窗口少得多的时间处理。我只是在那里放置了一个窗口,以确保每个窗口的每个键的语义保持不使用GlobalWindow。我通过3个并行阶段运行窗口数据来比较结果,每个结果都在下面解释。

  1. 只是将内容复制到GCS上的新文件中 - 这可以确保所有事件都按预期处理,并且我验证了内容与输入完全相同
  2. 在user_id上使用Combine.PerKey,只选择Iterable中的第一个元素 - 这实际上应该对数据进行重复数据删除,并且按预期工作。生成的文件具有原始事件列表中唯一项目的确切数量--50个元素
  3. 有状态的ParDo,用于检查是否已经看到密钥,并且只有当密钥没有时才会发出输出。理想情况下,此结果应与重复数据匹配[2],但我所看到的只是3个唯一事件。这三个独特的事件总是在我做的几次运行中指向相同的3个user_id。
  4. 有趣的是,当我从DataflowRunner切换到本地运行整个过程的DirectRunner时,我看到[3]的输出匹配[2],只有50个独特的元素符合预期。所以,我怀疑DatafulRunner是否存在有状态ParDo的问题。

    public class StatefulParDoSample {
        private static Logger logger = LoggerFactory.getLogger(StatefulParDoSample.class.getName());
    
        static class StatefulDoFn extends DoFn<KV<String, String>, String> {
            final Aggregator<Long, Long> processedElements = createAggregator("processed", Sum.ofLongs());
            final Aggregator<Long, Long> skippedElements = createAggregator("skipped", Sum.ofLongs());
    
            @StateId("keyTracker")
            private final StateSpec<Object, ValueState<Integer>> keyTrackerSpec =
                    StateSpecs.value(VarIntCoder.of());
    
            @ProcessElement
            public void processElement(
                    ProcessContext context,
                    @StateId("keyTracker") ValueState<Integer> keyTracker) {
                processedElements.addValue(1l);
                final String userId = context.element().getKey();
    
                int wasSeen = firstNonNull(keyTracker.read(), 0);
                if (wasSeen == 0) {
                    keyTracker.write( 1);
                    context.output(context.element().getValue());
                } else {
                    keyTracker.write(wasSeen + 1);
                    skippedElements.addValue(1l);
                }
            }
        }
    
        public static void main(String[] args) {
            DataflowPipelineOptions pipelineOptions = PipelineOptionsFactory.create().as(DataflowPipelineOptions.class);
            pipelineOptions.setRunner(DataflowRunner.class);
            pipelineOptions.setProject("project-name");
            pipelineOptions.setStagingLocation(GCS_STAGING_LOCATION);
            pipelineOptions.setStreaming(false);
            pipelineOptions.setAppName("deduper");
            Pipeline p = Pipeline.create(pipelineOptions);
    
            final ObjectMapper mapper = new ObjectMapper();
            PCollection<KV<String, String>> keyedEvents =
            p
                .apply(TextIO.Read.from(GCS_SAMPLE_INPUT_FILE_PATH))
                .apply(WithKeys.of(new SerializableFunction<String, String>() {
                    @Override
                    public String apply(String input) {
                        try {
                            Map<String, Object> eventJson =
                                    mapper.readValue(input, Map.class);
                            return (String) eventJson.get("user_id");
                        } catch (Exception e) {
    
                        }
    
                        return "";
                    }
                }))
                .apply(
                    Window.into(
                        FixedWindows.of(Duration.standardMinutes(2))
                    )
                );
    
            keyedEvents
                .apply(ParDo.of(new StatefulDoFn()))
                .apply(TextIO.Write.to(GCS_SAMPLE_OUTPUT_FILE_PATH).withNumShards(1));
    
            keyedEvents
                .apply(Values.create())
                .apply(TextIO.Write.to(GCS_SAMPLE_COPY_FILE_PATH).withNumShards(1));
    
            keyedEvents
                .apply(Combine.perKey(new SerializableFunction<Iterable<String>, String>() {
                    @Override
                    public String apply(Iterable<String> input) {
                        return !input.iterator().hasNext() ? "empty" : input.iterator().next();
                    }
                }))
                .apply(Values.create())
                .apply(TextIO.Write.to(GCS_SAMPLE_COMBINE_FILE_PATH).withNumShards(1));
    
            PipelineResult result = p.run();
            result.waitUntilFinish();
        }
    }
    

1 个答案:

答案 0 :(得分:1)

这是批处理模式下Dataflow服务中的一个错误,已在即将发布的0.6.0 Beam版本中修复(如果跟踪前沿,则为HEAD)。

感谢您引起我的注意!作为参考,或者如果出现其他任何问题,我会通过BEAM-1611进行跟踪。