Apache flink Flink能每小时生成聚合/滚动/累积数据的快照吗?

Apache flink Flink能每小时生成聚合/滚动/累积数据的快照吗?,apache-flink,stream-processing,Apache Flink,Stream Processing,流处理的教科书示例是一个带时间戳的字数计算程序。使用以下数据示例 mario 10:00 luigi 10:01 mario 11:00 mario 12:00 我在以下几家公司看到过单词计数程序: 总数据集 mario 3 luigi 1 一组时间窗口分区 mario 10:00-11:00 1 luigi 10:00-11:00 1 mario 11:00-12:00 1 mario 12:00-13:00 1 但是,我没有发现滚动时间窗口上的字数计算程序示例,即我希望从时间开始每小时

流处理的教科书示例是一个带时间戳的字数计算程序。使用以下数据示例

mario 10:00
luigi 10:01
mario 11:00
mario 12:00
我在以下几家公司看到过单词计数程序:

总数据集

mario 3
luigi 1
一组时间窗口分区

mario 10:00-11:00 1
luigi 10:00-11:00 1
mario 11:00-12:00 1
mario 12:00-13:00 1
但是,我没有发现滚动时间窗口上的字数计算程序示例,即我希望从时间开始每小时为每个字生成一个字数:

mario 10:00-11:00 1
luigi 10:00-11:00 1
mario 11:00-12:00 2
luigi 11:00-12:00 1
mario 12:00-13:00 3
luigi 12:00-13:00 1
这在ApacheFlink或任何其他流处理库中都是可能的吗?谢谢

编辑:

到目前为止,我尝试了David Anderson方法的一种变体,只改变事件时间的处理时间,因为数据是timesSamped。但它并没有像我预期的那样工作。下面是代码、示例数据、它提供的结果以及我的后续问题:

public static void main(String[] args) throws Exception {
    final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment()
            .setParallelism(1)
            .setMaxParallelism(1);

    env.setStreamTimeCharacteristic(EventTime);


    String fileLocation = "full file path here";
    DataStreamSource<String> rawInput = env.readFile(new TextInputFormat(new Path(fileLocation)), fileLocation);

    rawInput.flatMap(parse())
            .assignTimestampsAndWatermarks(new AssignerWithPunctuatedWatermarks<TimestampedWord>() {
                @Nullable
                @Override
                public Watermark checkAndGetNextWatermark(TimestampedWord lastElement, long extractedTimestamp) {
                    return new Watermark(extractedTimestamp - 1);
                }

                @Override
                public long extractTimestamp(TimestampedWord element, long previousElementTimestamp) {
                    return element.getTimestamp();
                }
            })
            .keyBy(TimestampedWord::getWord)
            .process(new KeyedProcessFunction<String, TimestampedWord, Tuple3<String, Long, Long>>() {
                private transient ValueState<Long> count;

                @Override
                public void open(Configuration parameters) throws Exception {
                    count = getRuntimeContext().getState(new ValueStateDescriptor<>("counter", Long.class));
                }

                @Override
                public void processElement(TimestampedWord value, Context ctx, Collector<Tuple3<String, Long, Long>> out) throws Exception {
                    if (count.value() == null) {
                        count.update(0L);
                    }

                    long l = ((value.getTimestamp() / 10) + 1) * 10;
                    ctx.timerService().registerEventTimeTimer(l);

                    count.update(count.value() + 1);
                }

                @Override
                public void onTimer(long timestamp, OnTimerContext ctx, Collector<Tuple3<String, Long, Long>> out) throws Exception {
                    long currentWatermark = ctx.timerService().currentWatermark();
                    out.collect(new Tuple3(ctx.getCurrentKey(), count.value(), currentWatermark));
                }
            })
            .addSink(new PrintlnSink());

    env.execute();
}

private static long fileCounter = 0;

private static FlatMapFunction<String, TimestampedWord> parse() {
    return new FlatMapFunction<String, TimestampedWord>() {
        @Override
        public void flatMap(String value, Collector<TimestampedWord> out) {
            out.collect(new TimestampedWord(value, fileCounter++));
        }
    };
}

private static class TimestampedWord {
    private final String word;
    private final long timestamp;

    private TimestampedWord(String word, long timestamp) {
        this.word = word;
        this.timestamp = timestamp;
    }

    public String getWord() {
        return word;
    }

    public long getTimestamp() {
        return timestamp;
    }
}

private static class PrintlnSink implements org.apache.flink.streaming.api.functions.sink.SinkFunction<Tuple3<String, Long, Long>> {
    @Override
    public void invoke(Tuple3<String, Long, Long> value, Context context) throws Exception {
        System.out.println(value.getField(0) + "=" + value.getField(1) + " at " + value.getField(2));
    }
}
显然出了问题。对于
anna
,我在20处得到了3的计数,尽管单词
anna
的第三个实例直到位置22才出现。奇怪的是,
edu
只出现在最后一个快照中,即使它出现在安娜的第三个实例之前。即使没有消息到达(即应生成相同的数据),我如何触发每10个“时间单位”生成一个快照


如果有人能给我指出正确的方向,我将非常感激

是的,这不仅适用于Flink,而且很容易。您可以使用KeyedProcessFunction来实现这一点,该函数将计数器保持在keyed状态,以记录到目前为止每个字/键在输入流中出现的次数。然后使用计时器触发报告

下面是一个使用处理时间计时器的示例。它每10秒打印一份报告

public class DSExample {
    public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment env =
            StreamExecutionEnvironment.getExecutionEnvironment();

        env.addSource(new SocketTextStreamFunction("localhost", 9999, "\n", -1))
            .keyBy(x -> x)
            .process(new KeyedProcessFunction<String, String, Tuple3<Long, String, Integer>>() {
                private transient ValueState<Integer> counter;

                @Override
                public void open(Configuration parameters) throws Exception {
                    counter = getRuntimeContext().getState(new ValueStateDescriptor<>("counter", Integer.class));
                }

                @Override
                public void processElement(String s, Context context, Collector<Tuple3<Long, String, Integer>> collector) throws Exception {
                    if (counter.value() == null) {
                        counter.update(0);
                        long now = context.timerService().currentProcessingTime();
                        context.timerService().registerProcessingTimeTimer((now + 10000) - (now % 10000));
                    }
                    counter.update(counter.value() + 1);
                }

                @Override
                public void onTimer(long timestamp, OnTimerContext context, Collector<Tuple3<Long, String, Integer>> out) throws Exception {
                    long now = context.timerService().currentProcessingTime();
                    context.timerService().registerProcessingTimeTimer((now + 10000) - (now % 10000));
                    out.collect(new Tuple3(now, context.getCurrentKey(), counter.value()));
                }
            })
            .print();

        env.execute();
    }
}
通过这些更改,我得到了以下结果:

mario=4 at 10
luigi=1 at 10
fred=1 at 10
bob=2 at 10
vilma=1 at 10
dan=1 at 10
vilma=1 at 20
luigi=1 at 20
dylan=2 at 20
carl=1 at 20
bambam=1 at 20
mario=6 at 20
summer=1 at 20
anna=2 at 20
bob=2 at 20
fred=2 at 20
dan=1 at 20
fred=2 at 9223372036854775807
dan=1 at 9223372036854775807
carl=1 at 9223372036854775807
dylan=2 at 9223372036854775807
vilma=1 at 9223372036854775807
edu=1 at 9223372036854775807
anna=7 at 9223372036854775807
summer=1 at 9223372036854775807
bambam=1 at 9223372036854775807
luigi=1 at 9223372036854775807
bob=2 at 9223372036854775807
mario=6 at 9223372036854775807

现在,如果您需要实际处理无序事件,这将变得相当复杂。有必要让水印滞后于时间戳一个真实的量,以反映流中存在的实际无序量,这将需要能够处理一次打开多个窗口。任何给定的事件/字可能不属于下一个将关闭的窗口,因此不应增加其计数器。例如,您可以将这些“早期”事件缓冲在另一个状态(例如ListState)中,或者以某种方式维护多个计数器(可能在MapState中)。此外,有些事件可能会延迟,从而使早期报告无效,您需要定义一些处理策略。

Hi@David,非常感谢您的回复。我尝试了一种类似于您的方法,只使用事件时间作为我的数据的时间戳(我编辑了我的原始帖子)。我的要求是每10个时间单位生成一个单词计数数据的快照。快照必须精确到所述时间(即对t=10,20,30…处的快照感兴趣,但对t=11处的快照不感兴趣)。如果一段时间后没有消息到达,我仍然需要在要求的时间生成快照,但数据不会更改。消息到达的延迟最多为2个时间单位。非常感谢您的帮助!顺便说一句,没有必要将并行度设置为1——如果让它并行运行,您将获得准确的结果。给定密钥的所有事件都将被串行处理。我只将parallelism设置为1以简化事情,我的实际应用程序将使用kafka,因此事件将被时间戳和排序。对了,回答得很好,谢谢。从文档中我了解到,t的水印意味着所有新消息的时间必须严格大于t,如果允许两个事件具有相同的时间戳,这会起作用吗?最后一个问题是,假设应用程序时钟的最大延迟为2个时间单位,那么即使没有新消息到达,我如何能够以10个时间单位的间隔生成快照?
@Override
public void processElement(TimestampedWord value, Context ctx, Collector<Tuple3<String, Long, Long>> out) throws Exception {
    if (count.value() == null) {
        count.update(0L);
        setTimer(ctx.timerService(), value.getTimestamp());
    }

    count.update(count.value() + 1);
}

@Override
public void onTimer(long timestamp, OnTimerContext ctx, Collector<Tuple3<String, Long, Long>> out) throws Exception {
    long currentWatermark = ctx.timerService().currentWatermark();
    out.collect(new Tuple3(ctx.getCurrentKey(), count.value(), currentWatermark));
    if (currentWatermark < Long.MAX_VALUE) {
        setTimer(ctx.timerService(), currentWatermark);
    }
}

private void setTimer(TimerService service, long t) {
    service.registerEventTimeTimer(((t / 10) + 1) * 10);
}
mario=4 at 10
luigi=1 at 10
fred=1 at 10
bob=2 at 10
vilma=1 at 10
dan=1 at 10
vilma=1 at 20
luigi=1 at 20
dylan=2 at 20
carl=1 at 20
bambam=1 at 20
mario=6 at 20
summer=1 at 20
anna=2 at 20
bob=2 at 20
fred=2 at 20
dan=1 at 20
fred=2 at 9223372036854775807
dan=1 at 9223372036854775807
carl=1 at 9223372036854775807
dylan=2 at 9223372036854775807
vilma=1 at 9223372036854775807
edu=1 at 9223372036854775807
anna=7 at 9223372036854775807
summer=1 at 9223372036854775807
bambam=1 at 9223372036854775807
luigi=1 at 9223372036854775807
bob=2 at 9223372036854775807
mario=6 at 9223372036854775807