Java 如何实现在X分钟内未收到任何事件后发出的Flink事件时间触发器
我正在努力理解Flink触发器是如何工作的。我的数据流包含具有会话ID的事件,我基于该会话ID聚合了该会话ID。每个会话将包含一个已启动和一个已结束的事件,但有时已结束的事件将丢失 为了处理这个问题,我设置了一个触发器,它将在处理结束事件时发出聚合会话。但是,如果在2分钟内没有任何事件从该会话到达,我希望发出我们迄今为止聚合的任何消息(我们发送事件的应用程序每分钟发送一次心跳,因此如果我们没有收到任何事件,该会话将被视为丢失) 我设置了以下触发功能:Java 如何实现在X分钟内未收到任何事件后发出的Flink事件时间触发器,java,apache-flink,flink-streaming,Java,Apache Flink,Flink Streaming,我正在努力理解Flink触发器是如何工作的。我的数据流包含具有会话ID的事件,我基于该会话ID聚合了该会话ID。每个会话将包含一个已启动和一个已结束的事件,但有时已结束的事件将丢失 为了处理这个问题,我设置了一个触发器,它将在处理结束事件时发出聚合会话。但是,如果在2分钟内没有任何事件从该会话到达,我希望发出我们迄今为止聚合的任何消息(我们发送事件的应用程序每分钟发送一次心跳,因此如果我们没有收到任何事件,该会话将被视为丢失) 我设置了以下触发功能: public class EventTime
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);
}
}
所以我对整个流使用EventTime。
然后,我创建如下窗口:
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");
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");
datastreamplayersessions=sideEvents
.keyBy((KeySelector)事件->(字符串)事件.get(Field.SESSION_ID))
.window(ProcessingTimeSessionWindows.withGap(org.apache.flink.streaming.api.windowing.time.time.minutes(5)))
.trigger(新的EventTimeProcessingTimeTrigger(会话超时))
.aggregate(新SessionAggregator())
.name(“将事件聚合到会话中”)
.uid(“将事件聚合为会话”);
这种情况很复杂。我不敢确切地预测这段代码将做什么,但我可以解释其中的一些情况
第1点:您已经将时间特性设置为事件时间,安排了时间戳和水印,并在触发器中实现了onEventTime
回调。但是,没有任何地方可以创建事件时间计时器。除非我遗漏了什么,否则实际上没有任何东西使用事件时间或水印。您尚未实现事件时间触发器,我也不希望调用onEventTime
第二点:你的触发器不需要清除。作为清除窗口的一部分,Flink负责在触发器上调用clear
第三点:你的触发器试图反复触发并清除窗口,这似乎是不对的。我这样说是因为您正在为每个元素创建一个新的处理时间计时器,当每个计时器启动时,您正在启动并清除窗口。您可以随时打开车窗,但只能清除车窗一次,然后车窗就会消失
第4点:会话窗口是一种特殊的窗口,称为合并窗口。当会话合并时(在事件到达时总是发生),它们的触发器被合并,其中一个被清除。这就是为什么你会看到clear被如此频繁地调用
建议:由于您每分钟有一次保留活动,并且打算在2分钟不活动后关闭会话,因此您似乎可以将会话间隔设置为2分钟,这样可以避免一些使事情变得如此复杂的因素。让会话窗口完成它们设计的任务
假设这会起作用,那么您可以简单地扩展Flink的ProcessingTimeTrigger
,并覆盖其OneElement
方法来执行此操作:
@覆盖
public TriggerResult OneElement(HashMap元素、长时间戳、时间窗口、TriggerContext ctx)引发异常{
if(endSession.contains(element.get(Field.EVENT_TYPE))){
返回TriggerResult.FIRE\u和\u PURGE;
}
返回super(元素、时间戳、窗口、ctx);
}
以这种方式,窗口将在两分钟不活动后触发,或由显式会话结束事件触发
您应该能够简单地继承剩余的ProcessingTimeRigger
行为
如果您想使用事件时间,那么就使用EventTimeTrigger
作为超类,并且您必须找到一种方法来确保您的水印即使在流变为空闲时也能取得进展。请参阅以了解如何处理该问题。相同的问题我已将时间特性设置为处理时间和触发器:
//the trigger
.trigger(PurgingTrigger.of(TimerTrigger.of(Time.seconds(winSec))))
以下触发功能:
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);
}
}
//重写ProcessingTimeTrigger行为
公共类TimerTrigger扩展触发器{
私有静态最终长serialVersionUID=1L;
私人最终长间隔;
私有最终还原状态描述符状态描述;
私人计时器(长winInterValMills){//窗口
this.stateDesc=新的ReduceingStateDescriptor(“启动时间”,new TimerTrigger.Min(),LongSerializer.INSTANCE);
this.interval=winInterValMills;
}
public TriggerResult OneElement(对象元素、长时间戳、W窗口、TriggerContext ctx)引发异常{
如果(window.maxTimestamp()interval){//fire late
时间=(现在为%1000)+间隔-1;
}
ctx.RegisterProcessingTimer(时间);
firetimstamp.add(时间);
返回TriggerResult.CONTINUE;
}否则{
返回TriggerResult.CONTINUE;
}
}
public TriggerResult onEventTime(长时间、W窗口、TriggerContext ctx)引发异常{
如果(time==window.maxTimestamp()){
返回TriggerResult.FIRE;
}
返回TriggerResult.CONTINUE;
}
public TriggerResult on ProcessingTime(长时间、W窗口、TriggerContext ctx)引发异常{
ReductionState fireTimestamp=(ReductionState)ctx.getPartitionedState(this.stateDesc);
如果(((长)fireTimestamp.get()).equals(时间)){
firetimstamp.clear();
long maxTimestamp=Math.max(window.maxtimstamp(),time);//可能没用
如果(maxTimestamp==时间){
maxTimestamp=time+this.interval;
}
firetimstamp.add(maxtimstamp);
ctx.RegisterProcessingTimer(maxTimestamp);
返回TriggerResult.FIRE;
}否则{
返回TriggerResult.CONTINUE;
}
}
公共无效清除(W窗口,TriggerContext ctx)引发异常{