Google cloud dataflow 多个工作人员的sideInput一致性

Google cloud dataflow 多个工作人员的sideInput一致性,google-cloud-dataflow,Google Cloud Dataflow,我正在开发管道,需要将控制数据广播到DoFn转换的每个实例。理想情况下,我希望获得所有这些控制数据,而不仅仅是最后一个状态。我把这个例子简化为非常简单的一个例子——对side和main输入有两个CountingInput,对side-one过滤30个第一个刻度,然后查找sideInput PCollection<Long> iDs = p.apply(CountingInput.unbounded().withRate(1, Duration.mill

我正在开发管道,需要将控制数据广播到DoFn转换的每个实例。理想情况下,我希望获得所有这些控制数据,而不仅仅是最后一个状态。我把这个例子简化为非常简单的一个例子——对side和main输入有两个CountingInput,对side-one过滤30个第一个刻度,然后查找sideInput

    PCollection<Long> iDs =
            p.apply(CountingInput.unbounded().withRate(1, Duration.millis(200)))
                    .apply(ParDo.of(new DoFn<Long, Long>() {
                        @Override
                        public void processElement(ProcessContext c) {
                           Long cnt = c.element();
                           if (cnt <= 30) {
                               logger.info("ID=" + cnt);
                               c.output(cnt);
                           }
                        }
                    }));

    PCollectionView<List<Long>> iDsView = iDs
            .apply(Window.<Long>into(new GlobalWindows())
                    .triggering(Repeatedly.forever(AfterPane.elementCountAtLeast(1)))
                    .discardingFiredPanes()
            )
            .apply(View.asList());

    p.apply(CountingInput.unbounded().withRate(1, Duration.millis(1000)))
            .apply(ParDo
                    .withSideInputs(iDsView)
                    .of(new DoFn<Long, String>() {
                        @Override
                        public void processElement(ProcessContext c) {
                            Long in = c.element();
                            List<Long> si = c.sideInput(iDsView);

                            StringBuilder sb = new StringBuilder();
                            si.forEach(x -> sb.append(",").append(x));
                            logger.info("invocation=" + in
                                    + " class=" + this.toString()
                                    + " sideInput=[" + sb.toString().substring(1) + "]");
                        }
                    }));
但是当我使用--runner=BlockingDataflowPipelineRunner--numWorkers=4运行它时 我看到4名工人的侧线非常不一致,在我运行管道的几分钟内也是如此:

    00:47:16.586
    invocation=138 class=com.sandbox.dw.WriteLogsToBQ$2@312aa182 sideInput=[0]
    00:47:15.709
    invocation=137 class=com.sandbox.dw.WriteLogsToBQ$2@2d0b6481 sideInput=[3,6,9,12,18,21,24,30]
    00:47:14.445
    invocation=136 class=com.sandbox.dw.WriteLogsToBQ$2@5153b895 sideInput=[0]
    00:47:11.760
    invocation=134 class=com.sandbox.dw.WriteLogsToBQ$2@65683230 sideInput=[3,6,9,12,18,21,24,30]
    00:47:11.231
    invocation=132 class=com.sandbox.dw.WriteLogsToBQ$2@5ee8917a sideInput=[0]
    00:47:10.775
    invocation=133 class=com.sandbox.dw.WriteLogsToBQ$2@16000b0 sideInput=[3,6,9,12,18,21,24,30]
    00:47:09.477
    invocation=123 class=com.sandbox.dw.WriteLogsToBQ$2@6ffe3f47 sideInput=[15]
    00:47:08.977
    invocation=130 class=com.sandbox.dw.WriteLogsToBQ$2@458bc76b sideInput=[3,6,9,12,18,21,24,30]
    00:47:07.505
    invocation=129 class=com.sandbox.dw.WriteLogsToBQ$2@2c6fcbcf sideInput=[0]
    00:47:07.200
    invocation=128 class=com.sandbox.dw.WriteLogsToBQ$2@1bf63883 sideInput=[3,6,9,12,18,21,24,30]
    00:47:06.033
    invocation=127 class=com.sandbox.dw.WriteLogsToBQ$2@5fd02daf sideInput=[3,6,9,12,18,21,24,30]
    00:47:05.573
    invocation=119 class=com.sandbox.dw.WriteLogsToBQ$2@7ba4a88b sideInput=[15]
    00:47:04.502
    invocation=126 class=com.sandbox.dw.WriteLogsToBQ$2@a7d0a48 sideInput=[0]
我还注意到每个输入元素都重新创建了DoFn的实例。 有谁能建议最好的方法来保证使用sideInput向每个转换广播PubSub数据


下面是一个简单的例子来分享我的担忧:

    PCollectionView<List<Long>> iDsView =
            p.apply(CountingInput.unbounded().withRate(1, Duration.millis(1000)))
                    .apply(Window.<Long>into(new GlobalWindows())
                            .triggering(Repeatedly.forever(AfterPane.elementCountAtLeast(1)))
                            .discardingFiredPanes()
                    )
                    .apply(Max.longsGlobally())
                    .apply(ParDo.of(new DoFn<Long, Long>() {
                        @Override
                        public void processElement(ProcessContext c) {
                            Long elem = c.element();
                            logger.info("MaxElement=" + elem);
                            c.output(elem);
                        }
                    }))
                    .apply(View.asList());

    p.apply(CountingInput.unbounded().withRate(1, Duration.millis(300)))
            .apply(ParDo
                    .withSideInputs(iDsView)
                    .of(new DoFn<Long, Long>() {
                        @Override
                        public void processElement(ProcessContext c) {
                            Long in = c.element();
                            List<Long> si = c.sideInput(iDsView);
                            StringBuilder sb = new StringBuilder();
                            si.forEach(x -> sb.append(",").append(x));
                            logger.info("MainInput=" + in
                                    + " sideInput=[" + sb.toString().substring(1) + "]");
                        }
                    }));
数据流日志:

    MaxElement=61
    21:26:00.225
    MainInput=207 sideInput=[0]
    21:26:00.676
    MainInput=208 sideInput=[0]
    21:26:00.924
    MainInput=209 sideInput=[0]
    21:26:01.258
    MainInput=210 sideInput=[0]
    21:26:01.260
    MaxElement=62
    21:26:01.518
    MainInput=211 sideInput=[0]
    21:26:01.748
    MainInput=212 sideInput=[0]
    21:26:02.071
    MainInput=213 sideInput=[0]
    21:26:02.313
    MainInput=214 sideInput=[0]
    21:26:02.466
    MaxElement=63
    21:26:02.677
    MainInput=215 sideInput=[0]
    21:26:02.994
    MaxElement=64
    21:26:03.113
    MainInput=216 sideInput=[0]
    21:26:03.335
    MainInput=217 sideInput=[0]
    21:26:03.614
    MainInput=218 sideInput=[0]
    21:26:04.132
    MainInput=219 sideInput=[0]
    21:26:04.142
    MaxElement=65
    21:26:04.193
    MainInput=220 sideInput=[0]
    21:26:04.538
    MainInput=221 sideInput=[0]
    21:26:04.706
    MainInput=222 sideInput=[0]
    21:26:05.250
    MaxElement=66
    21:26:05.531
    MainInput=224 sideInput=[0]

我所关心的是,side-input显示的是过时的结果,我希望在触发触发器时side-input缓存会失效。

根据一致性需要,您可以拥有一个发布/子主题的每个进程静态订阅服务器,用于广播控制消息。这将是一个对象,它在初始化时创建了对该主题的随机订阅,并开始侦听该主题。每当它接收到消息时,它都会使DoFn的处理代码可以使用该消息的内容


请注意,没有很好的方法来确保清理这些订阅,因此,如果您经常启动和停止管道,则需要一些定期清理的方法。

根据您的一致性需要,您可以为广播控制消息的发布/子主题提供一个每个进程的静态订阅服务器。这将是一个对象,它在初始化时创建了对该主题的随机订阅,并开始侦听该主题。每当它接收到消息时,它都会使DoFn的处理代码可以使用该消息的内容


请注意,没有很好的方法来确保这些订阅被清除,因此,如果您经常启动和停止管道,则需要一些定期清除的方法。

您将遇到主输入元素和侧输入上的多个触发器之间的一致性问题。一致性模型非常松散:

  • 每次触发都会导致所有工作进程最终使用输出的元素进行更新,位没有截止日期或同步规则
  • 同时,在读取之前可能会发生另一个触发,因此可能永远看不到特定的输出元素
对于触发本身,请记住它也是不确定的:

  • 值将在处理过程中的自然提交点处输出,即使触发器的谓词所满足的元素少于完整绑定。这允许在合并窗口、重试、网络延迟和捆绑变化的情况下有效地预测行为
  • 因此,它被指定为在元素计数至少为1之后。即使触发器的谓词仅由一个元素满足,整个已处理元素束也将一起触发。因此,可以看到0到30的任何子集
因此,您的管道布局最自然地适用于以下情况:侧面输入表示向某个最终值的非确定性收敛(可能是无限的,因此收敛可能永远持续)。相反,我认为您需要通过
CoGroupByKey
进行适当的加入,或者按照您的回答中的建议进行直接侧频道订阅

根据用例的实际细节,可能还有其他更深奥的解决方案使用自定义
WindowFn
。主输入将阻塞,直到侧输入在匹配窗口中至少有一个触发元素

最后,简要介绍一下累积模式:

  • 基于您“希望获取所有控制数据,而不仅仅是最后一个状态”的陈述,听起来您希望将
    丢弃FiredPanes()
    替换为
    累积FiredPanes()
    ,但这将导致侧输入无限制地增长
  • 如果您使用窗口来限制端输入的生存期,则
    累加FiredPanes()
    将工作得更好-所有工作人员最终将收到所有控制消息,并且窗口的累加消息将在窗口过期时释放。但是不能保证一个主输入元素实际到达的晚到足以看到最终的值!因此,这可能仍然不是正确的方法

您将遇到主输入元素和侧面输入的多个触发之间的一致性问题。一致性模型非常松散:

  • 每次触发都会导致所有工作进程最终使用输出的元素进行更新,位没有截止日期或同步规则
  • 同时,在读取之前可能会发生另一个触发,因此可能永远看不到特定的输出元素
对于触发本身,请记住它也是不确定的:

  • 值将在处理过程中的自然提交点处输出,即使触发器的谓词所满足的元素少于完整绑定。这允许在合并窗口、重试、网络延迟和捆绑变化的情况下有效地预测行为
  • 因此,它被指定为在元素计数至少为1之后。即使触发器的谓词仅由一个元素满足,整个已处理元素束也将一起触发。因此,可以看到0到30的任何子集
因此,您的管道布局最自然地适用于以下情况:侧面输入表示向某个最终值的非确定性收敛(可能是无限的,因此收敛可能永远持续)。相反,我认为你会想要一个职业选手
    INFO: MaxElement=6
    Jan 12, 2017 9:22:52 PM com.sandbox.dw.WriteLogsToBQ$2 processElement
    INFO: MainInput=21 sideInput=[6]
    Jan 12, 2017 9:22:52 PM com.sandbox.dw.WriteLogsToBQ$2 processElement
    INFO: MainInput=22 sideInput=[6]
    Jan 12, 2017 9:22:53 PM com.sandbox.dw.WriteLogsToBQ$2 processElement
    INFO: MainInput=23 sideInput=[6]
    Jan 12, 2017 9:22:53 PM com.sandbox.dw.WriteLogsToBQ$1 processElement
    INFO: MaxElement=7
    Jan 12, 2017 9:22:53 PM com.sandbox.dw.WriteLogsToBQ$2 processElement
    INFO: MainInput=24 sideInput=[7]
    Jan 12, 2017 9:22:53 PM com.sandbox.dw.WriteLogsToBQ$2 processElement
    INFO: MainInput=25 sideInput=[7]
    Jan 12, 2017 9:22:53 PM com.sandbox.dw.WriteLogsToBQ$2 processElement
    INFO: MainInput=26 sideInput=[7]
    Jan 12, 2017 9:22:54 PM com.sandbox.dw.WriteLogsToBQ$1 processElement
    INFO: MaxElement=8
    Jan 12, 2017 9:22:54 PM com.sandbox.dw.WriteLogsToBQ$2 processElement
    INFO: MainInput=27 sideInput=[8]
    Jan 12, 2017 9:22:54 PM com.sandbox.dw.WriteLogsToBQ$2 processElement
    INFO: MainInput=28 sideInput=[8]
    Jan 12, 2017 9:22:54 PM com.sandbox.dw.WriteLogsToBQ$2 processElement
    INFO: MainInput=29 sideInput=[8]
    Jan 12, 2017 9:22:55 PM com.sandbox.dw.WriteLogsToBQ$2 processElement
    INFO: MainInput=30 sideInput=[8]
    Jan 12, 2017 9:22:55 PM com.sandbox.dw.WriteLogsToBQ$1 processElement
    INFO: MaxElement=9
    Jan 12, 2017 9:22:55 PM com.sandbox.dw.WriteLogsToBQ$2 processElement
    INFO: MainInput=31 sideInput=[9]
    Jan 12, 2017 9:22:55 PM com.sandbox.dw.WriteLogsToBQ$2 processElement
    INFO: MainInput=32 sideInput=[9]
    Jan 12, 2017 9:22:56 PM com.sandbox.dw.WriteLogsToBQ$2 processElement
    INFO: MainInput=33 sideInput=[9]
    Jan 12, 2017 9:22:56 PM com.sandbox.dw.WriteLogsToBQ$1 processElement
    MaxElement=61
    21:26:00.225
    MainInput=207 sideInput=[0]
    21:26:00.676
    MainInput=208 sideInput=[0]
    21:26:00.924
    MainInput=209 sideInput=[0]
    21:26:01.258
    MainInput=210 sideInput=[0]
    21:26:01.260
    MaxElement=62
    21:26:01.518
    MainInput=211 sideInput=[0]
    21:26:01.748
    MainInput=212 sideInput=[0]
    21:26:02.071
    MainInput=213 sideInput=[0]
    21:26:02.313
    MainInput=214 sideInput=[0]
    21:26:02.466
    MaxElement=63
    21:26:02.677
    MainInput=215 sideInput=[0]
    21:26:02.994
    MaxElement=64
    21:26:03.113
    MainInput=216 sideInput=[0]
    21:26:03.335
    MainInput=217 sideInput=[0]
    21:26:03.614
    MainInput=218 sideInput=[0]
    21:26:04.132
    MainInput=219 sideInput=[0]
    21:26:04.142
    MaxElement=65
    21:26:04.193
    MainInput=220 sideInput=[0]
    21:26:04.538
    MainInput=221 sideInput=[0]
    21:26:04.706
    MainInput=222 sideInput=[0]
    21:26:05.250
    MaxElement=66
    21:26:05.531
    MainInput=224 sideInput=[0]