提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
前言
在窗口中引入watermark,使用EventTime,传入数据时不触发窗口的结束。也是自己平时不注意细节导致踩了一个很‘憨’的坑。
flink 初学者,有问题欢迎讨论
一、问题记录
1.出错代码
代码如下(示例):
val env = StreamExecutionEnvironment.getExecutionEnvironment
env.setParallelism(1)
//watermark默认生成时间为200ms
// env.getConfig.setAutoWatermarkInterval(200)
val df = env.socketTextStream("82.156.186.40", 6666)
//数据格式:1,9,1547718210
val fdf = df.map(row => {
val a: Array[String] = row.split(",")
(a(0), a(1).toInt, a(2).toLong)
})
.assignTimestampsAndWatermarks(WatermarkStrategy.forBoundedOutOfOrderness(Duration.ofSeconds(5))
.withTimestampAssigner(new SerializableTimestampAssigner[(String, Int, Long)] {
override def extractTimestamp(element: (String, Int, Long), recordTimestamp: Long): Long = {
element._3
}
}))
.keyBy(data => data._1)
.window(TumblingEventTimeWindows.of(Time.seconds(10)))
.allowedLateness(Time.minutes(1))
.sideOutputLateData(lateTag)
.reduce((o, n) => {
(o._1, Math.min(o._2, n._2), n._3)
})
//上述代码逻辑
//10秒一个窗口 watermark200ms生成一次,watermark最大容错率为5s
//打印迟到数据
fdf.getSideOutput(lateTag).print("late")
//打印窗口数据
fdf.print("result")
env.execute()
2.输入数据
输入数据如下(示例):
1,1,1547718201
1,2,1547718210
1,3,1547718211
1,4,1547718212
1,5,1547718215
1,6,1547718221
1,7,1547718215
1,8,1547718231
可以看到上面我输入的数据最大的相差时间有30s,但是所有数据输入完后没有任何一个窗口被激发,并且我设置了最大线程数为1,也不存在,因为我数据少导致某个分区watermark取不到的问题。
既然如此开始传统艺能看看源码,我们知道窗口什么时候关闭是由watermark决定的来看一下BoundedOutOfOrdernessWatermarks的生成策略
public class BoundedOutOfOrdernessWatermarks<T> implements WatermarkGenerator<T> {
//记录最大时间戳
private long maxTimestamp;
//最大乱序程度
private final long outOfOrdernessMillis;
/**
* Creates a new watermark generator with the given out-of-orderness bound.
*
* @param maxOutOfOrderness The bound for the out-of-orderness of the event timestamps.
*/
public BoundedOutOfOrdernessWatermarks(Duration maxOutOfOrderness) {
checkNotNull(maxOutOfOrderness, "maxOutOfOrderness");
checkArgument(!maxOutOfOrderness.isNegative(), "maxOutOfOrderness cannot be negative");
this.outOfOrdernessMillis = maxOutOfOrderness.toMillis();
// start so that our lowest watermark would be Long.MIN_VALUE.
this.maxTimestamp = Long.MIN_VALUE + outOfOrdernessMillis + 1;
}
// ------------------------------------------------------------------------
//每一个数据处理时 都会调用该方法,在这里用于更新最大时间戳,记录你处理过的数据中最大的时间戳
@Override
public void onEvent(T event, long eventTimestamp, WatermarkOutput output) {
maxTimestamp = Math.max(maxTimestamp, eventTimestamp);
}
//周期行调用的方法 默认为200ms,该方就是生成watermark的方法
//从该方法中我们可以看到 每次生成的水位线的时间为:
//maxTimestamp - outOfOrdernessMillis - 1
//maxTimestamp 为我们所记录的最大时间戳
//outOfOrdernessMillis是我们设置的最大乱序程度
//最后为什么要减一呢?举个例子,时间乱序度忽略不计,当前时间戳为1547718231,转换为毫秒为1547718231000,如果只来了一条这个数据,就判定为watermark为1547718231000,那么后面来的数据时间为1547718231000的数据不就全迟到了?
@Override
public void onPeriodicEmit(WatermarkOutput output) {
output.emitWatermark(new Watermark(maxTimestamp - outOfOrdernessMillis - 1));
}
}
看上面的代码我们可以发现
this.outOfOrdernessMillis = maxOutOfOrderness.toMillis();
将我们设置的最大乱序程度转为了毫秒进行处理,而我们提取的时间戳为秒级别,输入数据的最大时间戳为1547718231,那么最后生成的watermark为1547713231,所以没有触发窗口的关闭
3.解决问题
在提取时间的时候将时间戳转化为毫秒就行了QAQ