[英]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.