我想使用Apache Beam完成以下工作:
每5秒钟计算最近一分钟从pubsub中读取的事件
目标是对费率数据的输入进行半实时查看。然后可以将其扩展到更复杂的用例。
搜索后,我还没有找到解决这个看似简单的问题的方法。无效的东西:
关于如何解决此问题的任何建议?
答案 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());
}
请注意,来自不同窗口的结果可能是无序的(无论如何,在写入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是循环计时器。