Apache flink Flink窗口:聚合并输出到接收器

Apache flink Flink窗口:聚合并输出到接收器,apache-flink,flink-streaming,Apache Flink,Flink Streaming,我们有一个数据流,其中每个元素都属于这种类型: id: String type: Type amount: Integer 我们希望聚合此流并每周输出一次amount的总和 当前解决方案: flink管道的示例如下所示: stream.keyBy(type) .window(TumblingProcessingTimeWindows.of(Time.days(7))) .reduce(sumAmount()) .addSink(someOutput())

我们有一个数据流,其中每个元素都属于这种类型:

id: String
type: Type
amount: Integer
我们希望聚合此流并每周输出一次
amount
的总和

当前解决方案:

flink管道的示例如下所示:

stream.keyBy(type)
      .window(TumblingProcessingTimeWindows.of(Time.days(7)))
      .reduce(sumAmount())
      .addSink(someOutput())
| id | weekNumber |
| 1  | 1          |
| 2  | 1          |
| 3  | 1          |
| 4  | 2          |
| 5  | 2          |
stream.keyBy(type)
  .window(TumblingProcessingTimeWindows.of(Time.days(7)))
  .reduce(sumAmount(), new WrapWithWeek())
  .addSink(someOutput())

private static class WrapWithWeek
  extends ProcessWindowFunction<Event, Tuple3<Type, Long, Long>, Type, TimeWindow> {

      public void process(Type key,
                Context context,
                Iterable<Event> reducedEvents,
                Collector<Tuple3<Type, Long, Long>> out) {
          Long sum = reducedEvents.iterator().next();
          out.collect(new Tuple3<Type, Long, Long>(key, context.window.getStart(), sum));
      }
}
输入

| id | type | amount |
| 1  | CAT  | 10     |
| 2  | DOG  | 20     |
| 3  | CAT  | 5      |
| 4  | DOG  | 15     |
| 5  | DOG  | 50     |
如果窗口在记录
3
4
之间结束,我们的输出将是:

| TYPE | sumAmount |
| CAT  | 15        | (id 1 and id 3 added together)
| DOG  | 20        | (only id 2 as been 'summed')
| TYPE | sumAmount |
| CAT  | 15        | (of last week)
| DOG  | 20        | (of last week)
| DOG  | 65        | (id 4 and id 5 added together)
Id
4
5
仍将在flink管道中,并将在下周输出

因此,下周我们的总产量将是:

| TYPE | sumAmount |
| CAT  | 15        | (id 1 and id 3 added together)
| DOG  | 20        | (only id 2 as been 'summed')
| TYPE | sumAmount |
| CAT  | 15        | (of last week)
| DOG  | 20        | (of last week)
| DOG  | 65        | (id 4 and id 5 added together)
新要求:

我们现在还想知道每个记录在哪一周被处理。换句话说,我们的新产出应该是:

| TYPE | sumAmount | weekNumber |
| CAT  | 15        | 1          |
| DOG  | 20        | 1          |
| DOG  | 65        | 2          |
但我们还需要这样的额外输出:

stream.keyBy(type)
      .window(TumblingProcessingTimeWindows.of(Time.days(7)))
      .reduce(sumAmount())
      .addSink(someOutput())
| id | weekNumber |
| 1  | 1          |
| 2  | 1          |
| 3  | 1          |
| 4  | 2          |
| 5  | 2          |
stream.keyBy(type)
  .window(TumblingProcessingTimeWindows.of(Time.days(7)))
  .reduce(sumAmount(), new WrapWithWeek())
  .addSink(someOutput())

private static class WrapWithWeek
  extends ProcessWindowFunction<Event, Tuple3<Type, Long, Long>, Type, TimeWindow> {

      public void process(Type key,
                Context context,
                Iterable<Event> reducedEvents,
                Collector<Tuple3<Type, Long, Long>> out) {
          Long sum = reducedEvents.iterator().next();
          out.collect(new Tuple3<Type, Long, Long>(key, context.window.getStart(), sum));
      }
}
如何处理这个问题?

弗林克有办法做到这一点吗?我想我们会有一个聚合函数,它可以对金额进行求和,但也可以输出每个记录的当前周数,例如,但我在文档中找不到这样做的方法

(注意:我们每周处理约1亿条记录,因此理想情况下,我们只希望在一周内将总量保持在flink的状态,而不是所有单个记录)

编辑:

我选择了安东在下面描述的解决方案:

DataStream<Element> elements = 
  stream.keyBy(type)
        .process(myKeyedProcessFunction());

elements.addSink(outputElements());
elements.getSideOutput(outputTag)
        .addSink(outputAggregates())
DataStream元素=
stream.keyBy(类型)
.process(myKeyedProcessFunction());
addSink(outputElements());
元素。getSideOutput(outputTag)
.addSink(outputAggregates())
KeyedProcessFunction看起来像:

class MyKeyedProcessFunction extends KeyedProcessFunction<Type, Element, Element>
    private ValueState<ZonedDateTime> state;
    private ValueState<Integer> sum;

    public void processElement(Element e, Context c, Collector<Element> out) {
        if (state.value() == null) {
            state.update(ZonedDateTime.now());
            sum.update(0);
            c.timerService().registerProcessingTimeTimer(nowPlus7Days);
        }
        element.addAggregationId(state.value());
        sum.update(sum.value() + element.getAmount());
    }

    public void onTimer(long timestamp, OnTimerContext c, Collector<Element> out) {
        state.update(null);
        c.output(outputTag, sum.value()); 
    }
} 
类MyKeyedProcessFunction扩展了KeyedProcessFunction 私人价值观国家; 私人价值;国家金额; 公共void processElement(元素e、上下文c、收集器out){ if(state.value()==null){ state.update(ZonedDateTime.now()); 更新总数(0); c、 timerService().RegisterProcessingTimer(现在加7天); } 元素addAggregationId(state.value()); sum.update(sum.value()+element.getAmount()); } 公共void onTimer(长时间戳、OnTimerContext c、收集器输出){ state.update(null); c、 输出(outputTag,sum.value()); } }
reduce方法有一个变体,它将ProcessWindowFunction作为第二个参数。您可以这样使用它:

stream.keyBy(type)
      .window(TumblingProcessingTimeWindows.of(Time.days(7)))
      .reduce(sumAmount())
      .addSink(someOutput())
| id | weekNumber |
| 1  | 1          |
| 2  | 1          |
| 3  | 1          |
| 4  | 2          |
| 5  | 2          |
stream.keyBy(type)
  .window(TumblingProcessingTimeWindows.of(Time.days(7)))
  .reduce(sumAmount(), new WrapWithWeek())
  .addSink(someOutput())

private static class WrapWithWeek
  extends ProcessWindowFunction<Event, Tuple3<Type, Long, Long>, Type, TimeWindow> {

      public void process(Type key,
                Context context,
                Iterable<Event> reducedEvents,
                Collector<Tuple3<Type, Long, Long>> out) {
          Long sum = reducedEvents.iterator().next();
          out.collect(new Tuple3<Type, Long, Long>(key, context.window.getStart(), sum));
      }
}
stream.keyBy(类型)
.window(TumblingProcessingTimeWindows.of(Time.days(7)))
.reduce(sumAmount(),new WrapWithWeek())
.addSink(someOutput())
私有静态类WrapWithWeek
扩展ProcessWindowFunction{
公共作废流程(类型键,
语境,
可还原事件,
收集器(输出){
Long sum=reducedEvents.iterator().next();
collect(新的Tuple3(key,context.window.getStart(),sum));
}
}
通常,ProcessWindowFunction会传递一个Iterable,其中包含窗口收集的所有事件,但如果您使用reduce或aggregate函数来预聚合窗口结果,则只会将该单个值传递给Iterable。这方面的文档只是文档中的示例,目前有一个小bug,我在这里的示例中已经修复了这个bug


但是考虑到对第二个输出的新要求,我建议您放弃使用Windows进行此操作的想法,而是使用键控。每个key-ValueState需要两个数据块:一个按周计算,另一个用于存储总和。您需要一个每周触发一次的计时器:当它触发时,它应该发出类型、总和和周数,然后增加周数。同时,process element方法将简单地输出每个传入事件的ID以及周计数器的值。

也许周数是错误的术语,我的意思是
窗口号
,因此在窗口打开之前进行拆分将不起作用。有可能有一个带有侧面输出的ProcessWindowfunction吗?我已经修改了我的答案。