使用Apache Beam进行实时监控

时间:2019-02-03 13:07:57

标签: google-cloud-dataflow apache-beam

我想使用Apache Beam完成以下工作:

  

每5秒钟计算最近一分钟从pubsub中读取的事件

目标是对费率数据的输入进行半实时查看。然后可以将其扩展到更复杂的用例。

搜索后,我还没有找到解决这个看似简单的问题的方法。无效的东西:

  • 全局窗口+重复触发(没有输入时触发不触发)
  • 滑动窗口+没有默认值(不允许空窗口明显发出)

关于如何解决此问题的任何建议?

2 个答案:

答案 0 :(得分:4)

如前所述,Beam不为空窗口发射数据。除了王睿给出的原因之外,我们还可以添加挑战,即后期如何处理这些空窗格。

无论如何,即使度量最终降低到零,您描述的特定用例-监视消息数量的滚动计数-也应该可以进行一些工作。一种可能性是发布稳定数量的虚拟消息,这些消息将使水印前进并触发窗格,但稍后在管道中将其过滤掉。这种方法的问题在于,需要对发布源进行调整,并且可能并不总是很方便/可行。另一个涉及将生成的伪造数据作为另一个输入,并将其与主流共同分组。优点是无需调整源或接收器即可在Dataflow中完成所有操作。为了说明这一点,我提供一个示例。

输入分为两个流。对于虚拟对象,我每5秒使用GenerateSequence创建一个新元素。然后,我打开PCollection的窗口(窗口策略必须与主流的窗口策略兼容,所以我将使用相同的方法)。然后,我将该元素映射到一个值为0的键值对(我们可以使用其他值,因为我们知道该元素来自哪个流,但是我想证明不计算虚拟记录)。

PCollection<KV<String,Integer>> dummyStream = p
    .apply("Generate Sequence", GenerateSequence.from(0).withRate(1, Duration.standardSeconds(5)))
    .apply("Window Messages - Dummy", Window.<Long>into(
            ...
    .apply("Count Messages - Dummy", ParDo.of(new DoFn<Long, KV<String, Integer>>() {
        @ProcessElement
        public void processElement(ProcessContext c) throws Exception {
            c.output(KV.of("num_messages", 0));
        }
    }));

对于从Pub / Sub读取的主流,我将每个记录映射到值1。稍后,我将使用map-reduce阶段将所有这些添加到典型的单词计数示例中。

PCollection<KV<String,Integer>> mainStream = p
    .apply("Get Messages - Data", PubsubIO.readStrings().fromTopic(topic))
    .apply("Window Messages - Data", Window.<String>into(
            ...
    .apply("Count Messages - Data", ParDo.of(new DoFn<String, KV<String, Integer>>() {
        @ProcessElement
        public void processElement(ProcessContext c) throws Exception {
            c.output(KV.of("num_messages", 1));
        }
    }));

然后,我们需要使用CoGroupByKey将它们加入(我使用相同的num_messages键对计数进行分组)。当两个输入之一具有元素时,此阶段将输出结果,因此将此处的主要问题(没有Pub / Sub消息的空窗口)解锁。

final TupleTag<Integer> dummyTag = new TupleTag<>();
final TupleTag<Integer> dataTag = new TupleTag<>();

PCollection<KV<String, CoGbkResult>> coGbkResultCollection = KeyedPCollectionTuple.of(dummyTag, dummyStream)
        .and(dataTag, mainStream).apply(CoGroupByKey.<String>create());

最后,我们将所有信息相加以获得该窗口的消息总数。如果dataTag中没有元素,则总和将默认为0。

public void processElement(ProcessContext c, BoundedWindow window) {
    Integer total_sum = new Integer(0);

    Iterable<Integer> dataTagVal = c.element().getValue().getAll(dataTag);
    for (Integer val : dataTagVal) {
        total_sum += val;
    }

    LOG.info("Window: " + window.toString() + ", Number of messages: " + total_sum.toString());
}

这应该导致类似:enter image description here

请注意,来自不同窗口的结果可能是无序的(无论如何,在写入BigQuery时都会发生这种情况),并且我没有使用窗口设置来优化示例。

完整代码:

public class EmptyWindows {

    private static final Logger LOG = LoggerFactory.getLogger(EmptyWindows.class);

    public static interface MyOptions extends PipelineOptions {
        @Description("Input topic")
        String getInput();

        void setInput(String s);
    }

    @SuppressWarnings("serial")
    public static void main(String[] args) {
        MyOptions options = PipelineOptionsFactory.fromArgs(args).withValidation().as(MyOptions.class);
        Pipeline p = Pipeline.create(options);

        String topic = options.getInput();

        PCollection<KV<String,Integer>> mainStream = p
            .apply("Get Messages - Data", PubsubIO.readStrings().fromTopic(topic))
            .apply("Window Messages - Data", Window.<String>into(
                    SlidingWindows.of(Duration.standardMinutes(1))
                            .every(Duration.standardSeconds(5)))
                    .triggering(AfterWatermark.pastEndOfWindow())
                    .withAllowedLateness(Duration.ZERO)
                    .accumulatingFiredPanes())
            .apply("Count Messages - Data", ParDo.of(new DoFn<String, KV<String, Integer>>() {
                @ProcessElement
                public void processElement(ProcessContext c) throws Exception {
                    //LOG.info("New data element in main output");
                    c.output(KV.of("num_messages", 1));
                }
            }));

        PCollection<KV<String,Integer>> dummyStream = p
            .apply("Generate Sequence", GenerateSequence.from(0).withRate(1, Duration.standardSeconds(5)))
            .apply("Window Messages - Dummy", Window.<Long>into(
                    SlidingWindows.of(Duration.standardMinutes(1))
                            .every(Duration.standardSeconds(5)))
                    .triggering(AfterWatermark.pastEndOfWindow())
                    .withAllowedLateness(Duration.ZERO)
                    .accumulatingFiredPanes())
            .apply("Count Messages - Dummy", ParDo.of(new DoFn<Long, KV<String, Integer>>() {
                @ProcessElement
                public void processElement(ProcessContext c) throws Exception {
                    //LOG.info("New dummy element in main output");
                    c.output(KV.of("num_messages", 0));
                }
            }));

        final TupleTag<Integer> dummyTag = new TupleTag<>();
        final TupleTag<Integer> dataTag = new TupleTag<>();

        PCollection<KV<String, CoGbkResult>> coGbkResultCollection = KeyedPCollectionTuple.of(dummyTag, dummyStream)
                .and(dataTag, mainStream).apply(CoGroupByKey.<String>create());

        coGbkResultCollection
                .apply("Log results", ParDo.of(new DoFn<KV<String, CoGbkResult>, Void>() {

                    @ProcessElement
                    public void processElement(ProcessContext c, BoundedWindow window) {
                        Integer total_sum = new Integer(0);

                        Iterable<Integer> dataTagVal = c.element().getValue().getAll(dataTag);
                        for (Integer val : dataTagVal) {
                            total_sum += val;
                        }

                        LOG.info("Window: " + window.toString() + ", Number of messages: " + total_sum.toString());
                    }
                }));

        p.run();
    }
}

答案 1 :(得分:0)

解决此问题的另一种方法是使用有状态的DoFn和循环Timer,该循环在每5秒的滴答时触发一次。该循环计时器生成实时监视所需的默认数据,并确保每个窗口至少要处理一个事件。

https://stackoverflow.com/a/54543527/430128描述的方法的一个问题是,在具有多个密钥的系统中,需要为每个密钥生成这些“虚拟”事件。

请参见https://beam.apache.org/blog/looping-timers/。该文章中的选项1和2分别是外部心跳源和光束管道中生成的源。选项3是循环计时器。