繁体   English   中英

如何实现在 X 分钟内未收到任何事件后发出的 Flink 事件时间触发器

[英]How to implement a Flink Event Time Trigger that emits after no events recieved for X minutes

我正在努力理解 Flink 触发器的工作原理。 我的数据流包含带有我基于该 sessionId 聚合的 sessionId 的事件。 每个 session 将包含一个 Started 和一个 Ended 事件,但有时 Ended 事件会丢失。

为了处理这个问题,我设置了一个触发器,每当处理结束的事件时,它将发出聚合的 session。 但是在没有事件从 session 到达 2 分钟的情况下,我想发出我们迄今为止聚合的任何内容(我们发送事件的应用程序每分钟发送一次心跳,所以如果我们没有收到任何事件,session 被认为丢失了) .

我设置了以下触发器 function:

public class EventTimeProcessingTimeTrigger extends Trigger<HashMap, TimeWindow> {
    private final long sessionTimeout;
    private long lastSetTimer;

    // Max session length set to 1 day
    public static final long MAX_SESSION_LENGTH = 1000l * 86400l;

    // End session events
    private static ImmutableSet<String> endSession = ImmutableSet.<String>builder()
            .add("Playback.Aborted")
            .add("Playback.Completed")
            .add("Playback.Error")
            .add("Playback.StartAirplay")
            .add("Playback.StartCasting")
            .build();

    public EventTimeProcessingTimeTrigger(long sessionTimeout) {
        this.sessionTimeout = sessionTimeout;
    }

    @Override
    public TriggerResult onElement(HashMap element, long timestamp, TimeWindow window, TriggerContext ctx) throws Exception {
        lastSetTimer = ctx.getCurrentProcessingTime() + sessionTimeout;
        ctx.registerProcessingTimeTimer(lastSetTimer);

        if(endSession.contains(element.get(Field.EVENT_TYPE))) {
            return TriggerResult.FIRE_AND_PURGE;
        }

        return TriggerResult.CONTINUE;
    }

    @Override
    public TriggerResult onProcessingTime(long time, TimeWindow window, TriggerContext ctx) throws Exception {
        return TriggerResult.FIRE_AND_PURGE;
    }

    @Override
    public TriggerResult onEventTime(long time, TimeWindow window, TriggerContext ctx) throws Exception {
        return time == window.maxTimestamp() ?
                TriggerResult.FIRE_AND_PURGE :
                TriggerResult.CONTINUE;
    }

    @Override
    public void clear(TimeWindow window, TriggerContext ctx) throws Exception {
        ctx.deleteProcessingTimeTimer(lastSetTimer);
    }

    @Override
    public boolean canMerge() {
        return true;
    }

    @Override
    public void onMerge(TimeWindow window,
                        OnMergeContext ctx) {
        ctx.registerProcessingTimeTimer(ctx.getCurrentProcessingTime() + sessionTimeout);
    }
}

为了为事件设置水印,我使用应用程序设置的水印,因为 appEventTime 可能与服务器上的 wallClock 不同。 我像这样提取水印:

DataStream<HashMap> playerEvents = env
                .addSource(kafkaConsumerEvents, "playerEvents(Kafka)")
                .name("Read player events from Kafka")
                .uid("Read player events from Kafka")
                .map(json -> DECODER.decode(json, TypeToken.of(HashMap.class))).returns(HashMap.class)
                .name("Map Json to HashMap")
                .uid("Map Json to HashMap")
                .assignTimestampsAndWatermarks(new BoundedOutOfOrdernessTimestampExtractor<HashMap>(org.apache.flink.streaming.api.windowing.time.Time.seconds(30))
                {
                    @Override
                    public long extractTimestamp(HashMap element)
                    {
                        long timestamp = 0L;
                        Object timestampAsObject = (Object) element.get("CanonicalTime");
                        timestamp = (long)timestampAsObject;
                        return timestamp;
                    }
                })
                .name("Add CanonicalTime as timestamp")
                .uid("Add CanonicalTime as timestamp");

现在我觉得奇怪的是,当我在调试中运行代码并在触发器的明确 function 中设置断点时,它会不断被调用。 即使在触发器中没有达到 FIRE_AND_PURGE 点。 所以感觉就像我完全误解了触发器应该如何工作。 而且我的实现根本没有做我认为它正在做的事情。

我想我的问题是,触发器什么时候应该调用 clear ? 这是实现组合 EventTimeTrigger 和 ProcessingTimeTrigger 的正确方法吗?

感谢我能得到的所有帮助。

更新 1: (2020-05-29)

为了提供有关如何设置的更多信息。 我设置我的环境如下:

final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setRestartStrategy(RestartStrategies.failureRateRestart(60, Time.of(60, TimeUnit.MINUTES), Time.of(60, TimeUnit.SECONDS)));
        env.enableCheckpointing(5000);
        env.getCheckpointConfig().setMinPauseBetweenCheckpoints(2000);
env.getCheckpointConfig().setCheckpointingMode(CheckpointingMode.EXACTLY_ONCE);
        env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime);

所以我对整个 stream 使用 EventTime。 然后我像这样创建 windows:

DataStream<PlayerSession> playerSessions = sideEvents
                .keyBy((KeySelector<HashMap, String>) event -> (String) event.get(Field.SESSION_ID))
                .window(ProcessingTimeSessionWindows.withGap(org.apache.flink.streaming.api.windowing.time.Time.minutes(5)))
                .trigger(new EventTimeProcessingTimeTrigger(SESSION_TIMEOUT))
                .aggregate(new SessionAggregator())
                .name("Aggregate events into sessions")
                .uid("Aggregate events into sessions");

这种情况很复杂。 我犹豫要准确预测这段代码会做什么,但我可以解释一些正在发生的事情。

要点1:您已将时间特征设置为事件时间,安排了时间戳和水印,并在您的触发器中实现了onEventTime回调。 但是您在任何地方都没有创建事件时间计时器。 除非我错过了什么,否则实际上并没有使用事件时间或水印。 您还没有实现事件时间触发器,我不希望onEventTime会被调用。

第 2 点:您的触发器不需要调用 clear。 Flink 负责在触发器上调用 clear 作为清除 windows 的一部分。

第 3 点:您的触发器试图反复触发和清除 window,这似乎不正确。 我这样说是因为您正在为每个元素创建一个新的处理时间计时器,并且当每个计时器触发时,您正在触发并清除 window。 您可以随意触发 window,但您只能清除 window 一次,之后它就消失了。

第4点:Session windows是一种特殊的window,称为合并Z0F4137ED15252B5045D60B。 当会话合并时(这种情况一直发生,随着事件的到来),它们的触发器被合并,其中一个被清除。 这就是为什么你看到 clear 被如此频繁地调用的原因。

建议:由于您有每分钟一次的保活,并且打算在 2 分钟不活动后关闭会话,因此您似乎可以将 session 间隙设置为 2 分钟,这样可以避免一些使事情变得如此复杂的原因. 让 session windows 做他们设计的事情。

假设这可行,那么您可以简单地扩展 Flink 的ProcessingTimeTrigger并覆盖其onElement方法来执行此操作:

@Override
public TriggerResult onElement(HashMap element, long timestamp, TimeWindow window, TriggerContext ctx) throws Exception {

    if (endSession.contains(element.get(Field.EVENT_TYPE))) {
        return TriggerResult.FIRE_AND_PURGE;
    }

    return super(element, timestamp, window, ctx);
}

以这种方式,window 将在两分钟不活动后或由明确的会话结束事件触发。

您应该能够简单地继承ProcessingTimeTrigger的行为的 rest 。

如果您想使用事件时间,则使用EventTimeTrigger作为超类,并且您必须找到一种方法来确保即使 stream 空闲时您的水印也会继续进行。 请参阅此答案以了解如何处理。

同样的问题
我已将时间特征设置为处理时间和触发器:

//the trigger  

.trigger(PurgingTrigger.of(TimerTrigger.of(Time.seconds(winSec))))

以下触发 function:

//override the ProcessingTimeTrigger behavior
public class TimerTrigger<W extends Window> extends Trigger<Object, W> {
    private static final long serialVersionUID = 1L;
    private final long interval;
    private final ReducingStateDescriptor<Long> stateDesc;

    private TimerTrigger(long winInterValMills) { //window
        this.stateDesc = new ReducingStateDescriptor("fire-time", new TimerTrigger.Min(), LongSerializer.INSTANCE);
        this.interval = winInterValMills;
    }

    public TriggerResult onElement(Object element, long timestamp, W window, TriggerContext ctx) throws Exception {
        if (window.maxTimestamp() <= ctx.getCurrentWatermark()) {
            // if the watermark is already past the window fire immediately
            return TriggerResult.FIRE;
        }
        long now = System.currentTimeMillis();
        ReducingState<Long> fireTimestamp = (ReducingState) ctx.getPartitionedState(this.stateDesc);
        if (fireTimestamp.get() == null) {
            long time = Math.max(timestamp, window.maxTimestamp()) + interval;
            if (now-window.maxTimestamp()>interval){ // fire late
                time = (now-now%1000) + interval-1;
            }
            ctx.registerProcessingTimeTimer(time);
            fireTimestamp.add(time);
            return TriggerResult.CONTINUE;
        } else {
            return TriggerResult.CONTINUE;
        }
    }

    public TriggerResult onEventTime(long time, W window, TriggerContext ctx) throws Exception {
        if (time == window.maxTimestamp()){  
            return TriggerResult.FIRE;
        }
        return TriggerResult.CONTINUE;
    }

    public TriggerResult onProcessingTime(long time, W window, TriggerContext ctx) throws Exception {
        ReducingState<Long> fireTimestamp = (ReducingState) ctx.getPartitionedState(this.stateDesc);
        if (((Long) fireTimestamp.get()).equals(time)) {
            fireTimestamp.clear();
            long maxTimestamp = Math.max(window.maxTimestamp(), time); //maybe useless
            if (maxTimestamp == time) {
                maxTimestamp = time + this.interval;
            }
            fireTimestamp.add(maxTimestamp);
            ctx.registerProcessingTimeTimer(maxTimestamp);
            return TriggerResult.FIRE;
        } else {
            return TriggerResult.CONTINUE;
        }
    }

    public void clear(W window, TriggerContext ctx) throws Exception {
        ReducingState<Long> fireTimestamp = (ReducingState) ctx.getPartitionedState(this.stateDesc);
        long timestamp = (Long) fireTimestamp.get();
        ctx.deleteProcessingTimeTimer(timestamp);
        fireTimestamp.clear();
    }

    public boolean canMerge() {
        return true;
    }

    public void onMerge(W window, OnMergeContext ctx) {
        ctx.mergePartitionedState(this.stateDesc);
    }

    @VisibleForTesting
    public long getInterval() {
        return this.interval;
    }

    public String toString() {
        return "TimerTrigger(" + this.interval + ")";
    }

    public static <W extends Window> TimerTrigger<W> of(Time interval) {
        return new TimerTrigger(interval.toMilliseconds());
    }

    private static class Min implements ReduceFunction<Long> {
        private static final long serialVersionUID = 1L;

        private Min() {
        }

        public Long reduce(Long value1, Long value2) throws Exception {
            return Math.min(value1, value2);
        }
    }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM