这是我参与8月更文挑战的第4天,活动详情查看:8月更文挑战
什么是Watermark?
Watermaker就是给数据再额外的加的一个时间列,也就是Watermaker是个时间戳!
如何计算Watermaker?
Watermaker = 数据的事件时间 - 最大允许的延迟时间或乱序时间
注意:后面通过源码会发现,准确来说:
Watermaker = 当前窗口的最大的事件时间 - 最大允许的延迟时间或乱序时间
这样可以保证Watermaker水位线会一直上升(变大),不会下降
Watermark有什么用?
窗口如果按照系统时间来触发计算的,如: [10:00:00 ~ 10:00:10) 的窗口,
一但系统时间到了10:00:10就会触发计算,那么可能会导致延迟到达的数据丢失!
那么现在有了Watermaker,窗口就可以按照Watermaker来触发计算!
也就是说Watermaker是用来触发窗口计算的!
Watermaker如何触发窗口计算的?
窗口计算的触发条件为:
1.窗口中有数据
2.Watermaker >= 窗口的结束时间
3.Watermaker = 当前窗口的最大的事件时间 - 最大允许的延迟时间或乱序时间
也就是说只要不断有数据来,就可以保证Watermaker水位线是会一直上升/变大的,不会下降/减小的
所以最终一定是会触发窗口计算的
注意:上面的触发公式进行如下变形:
1、Watermaker >= 窗口的结束时间
2、Watermaker = 当前窗口的最大的事件时间 - 最大允许的延迟时间或乱序时间
3、当前窗口的最大的事件时间 - 最大允许的延迟时间或乱序时间 >= 窗口的结束时间
4、当前窗口的最大的事件时间 >= 窗口的结束时间 + 最大允许的延迟时间或乱序时间
图解Watermark
代码演示
env
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
source
DataStreamSource<Order> orderDS = env.addSource(new SourceFunction<Order>() {
private boolean flag = true;
@Override
public void run(SourceContext<Order> sourceContext) throws Exception {
Random random = new Random();
while (flag) {
String orderId = UUID.randomUUID().toString();
int userId = random.nextInt(2);
int money = random.nextInt(101);
long eventTime = System.currentTimeMillis() - random.nextInt(5) * 1000;
System.out.println("发送的数据为:" + userId + " : " + df.format(eventTime));
sourceContext.collect(new Order(orderId, userId, money, eventTime));
TimeUnit.SECONDS.sleep(1);
}
}
@Override
public void cancel() {
flag = false;
}
}).setParallelism(1);
逻辑处理
正常处理
SingleOutputStreamOperator<Order> orderDSWithWM = orderDS.assignTimestampsAndWatermarks(WatermarkStrategy.<Order>forBoundedOutOfOrderness(Duration.ofSeconds(5))
.withTimestampAssigner((order, l) -> order.getEventTime()));
SingleOutputStreamOperator<Order> result = orderDSWithWM.keyBy(order -> order.getUserId()).window(TumblingEventTimeWindows.of(Time.seconds(5))).sum("money");
重写方法增加打印观察数据
SingleOutputStreamOperator<Order> waterDS = orderDS.assignTimestampsAndWatermarks(new WatermarkStrategy<Order>() {
@Override
public WatermarkGenerator<Order> createWatermarkGenerator(WatermarkGeneratorSupplier.Context context) {
return new WatermarkGenerator<Order>() {
private int userId = 0;
private long eventTime = 0L;
private final long outOfOrdernessMillis = 3000L;
private long maxTimestamp = Long.MIN_VALUE + outOfOrdernessMillis + 1;
@Override
public void onEvent(Order order, long eventTimestamp, WatermarkOutput watermarkOutput) {
userId = order.getUserId();
eventTime = order.getEventTime();
maxTimestamp = Math.max(maxTimestamp, eventTimestamp);
}
@Override
public void onPeriodicEmit(WatermarkOutput watermarkOutput) {
Watermark watermark = new Watermark(maxTimestamp - outOfOrdernessMillis - 1);
System.out.println("key:" + userId + ",系统时间:" + df.format(System.currentTimeMillis()) + ",事件事件:" + df.format(eventTime) + ",水印时间:" + df.format(watermark.getTimestamp()));
watermarkOutput.emitWatermark(watermark);
}
};
}
}.withTimestampAssigner(((order, l) -> order.getEventTime())));
SingleOutputStreamOperator<String> result = waterDS.keyBy(Order::getUserId)
.window(TumblingEventTimeWindows.of(Time.seconds(5)))
.apply(new WindowFunction<Order, String, Integer, TimeWindow>() {
@Override
public void apply(Integer key, TimeWindow timeWindow, Iterable<Order> iterable, Collector<String> collector) throws Exception {
List<String> eventTimeList=new ArrayList<>();
for (Order order:iterable){
Long eventTime= order.getEventTime();
eventTimeList.add(df.format(eventTime));
}
String outStr=String.format("key:%s,窗口开始结束:[%s~%s),属于该窗口的事件时间:%s",key.toString(),df.format(timeWindow.getStart()),df.format(timeWindow.getEnd()),eventTimeList.toString());
collector.collect(outStr);
}
});
sink
result.print();