1、背景
如果多并行度,其实针对不同分区的Watermark,会取最小值
2、代码
public class WatermarkLateDemo {
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setParallelism(1);
SingleOutputStreamOperator<WaterSensor> sourceStream = env
.socketTextStream("localhost", 7777)
.map(new WaterSensorMapFunction());
WatermarkStrategy<WaterSensor> watermarkStrategy = WatermarkStrategy
.<WaterSensor>forBoundedOutOfOrderness(Duration.ofSeconds(3)) // 这里是最大乱序程度,其实就是认为最大乱序是3s
.withTimestampAssigner(((element, recordTimestamp) -> element.getTs() * 1000L)); // 周期性的从数据中获取时间
SingleOutputStreamOperator<WaterSensor> sensorWithWatermark = sourceStream.assignTimestampsAndWatermarks(watermarkStrategy);
OutputTag<WaterSensor> lateTag = new OutputTag<>("late-data", Types.POJO(WaterSensor.class));
SingleOutputStreamOperator<String> processStream = sensorWithWatermark.keyBy(sensor -> sensor.getId())
.window(TumblingEventTimeWindows.of(Time.seconds(10)))
.allowedLateness(Time.seconds(2)) // 推迟2s关窗
.sideOutputLateData(lateTag)
.process(new ProcessWindowFunction<WaterSensor, String, String, TimeWindow>() {
@Override
public void process(String key, ProcessWindowFunction<WaterSensor, String, String, TimeWindow>.Context context, Iterable<WaterSensor> elements, Collector<String> out) throws Exception {
long startTs = context.window().getStart();
long endTs = context.window().getEnd();
String windowStart = DateFormatUtils.format(startTs, "yyyy-MM-dd HH:mm:ss.SSS");
String windowEnd = DateFormatUtils.format(endTs, "yyyy-MM-dd HH:mm:ss.SSS");
long count = elements.spliterator().estimateSize();
out.collect("key=" + key + "的窗口[" + windowStart + "," + windowEnd + "]包含" + count + "条数据===>" + elements.toString());
}
});
processStream.print();
// 从流获取侧输出流,打印
processStream.getSideOutput(lateTag).printToErr("关闭后的迟到数据");
env.execute();
}
}
事件时间针对迟到数据
1、设置乱序程度 forBoundedOutOfOrderness
2、设置迟到,窗口关闭时间 allowedLateness
3、设置侧输出流 sideOutputLateData
输入数据
qiaozhanwei@192 ~ % nc -lk 7777
s1,1,1
s2,5,5
s1,10,10
s1,13,5
s1,2,2
s1,15,15
s1,3,3
s1,4,4
输出
key=s1的窗口[1970-01-01 08:00:00.000,1970-01-01 08:00:10.000]包含1条数据===>[WaterSensor{id='s1', ts=1, vc=1}]
key=s2的窗口[1970-01-01 08:00:00.000,1970-01-01 08:00:10.000]包含1条数据===>[WaterSensor{id='s2', ts=5, vc=5}]
key=s1的窗口[1970-01-01 08:00:00.000,1970-01-01 08:00:10.000]包含2条数据===>[WaterSensor{id='s1', ts=1, vc=1}, WaterSensor{id='s1', ts=2, vc=2}]
关闭后的迟到数据> WaterSensor{id='s1', ts=3, vc=3}
关闭后的迟到数据> WaterSensor{id='s1', ts=4, vc=4}
3、分析
当 Apache Flink 的全局并行度设置为 1 时,所有的数据处理都会在一个单独的任务中进行,即整个作业只有一个并行子任务来处理所有的 key。在这种情况下,不同 key 的 Watermark 推进方式如下:
3.1、全局并行度为 1 的 Watermark 推进机制
- 单个 Watermark 管理:由于所有的数据都在同一个并行子任务中处理,整个作业中只有一个全局的 Watermark。在 Flink 中,Watermark 是用来标识事件时间的进度。在并行度为 1 时,无论数据中有多少不同的 key,都只有一个 Watermark 在全局控制事件时间的推进
- Watermark 统一推进:所有 key 的事件会共享同一个 Watermark,也就是说,来自不同 key 的事件时间不会独立推进,而是整个数据流中最小的事件时间决定 Watermark 的推进。这意味着 Flink 的 Watermark 机制在这种情况下是基于所有 key 中的最小时间戳来前进的
3.2、具体 Watermark 推进逻辑
在并行度为 1 的情况下:
- 所有 key 的数据都会在同一个任务中被处理,这意味着 Flink 只会维护一个 Watermark
- Watermark 的值取决于这个任务接收到的所有数据中的最小事件时间戳。例如,如果任务收到的数据中有多个 key,并且这些 key 的事件时间分别是 T1、T2、T3,则 Watermark 将会取其中的最小值来更新
例子:
假设你有三个 key,key1、key2 和 key3,它们的事件时间分别是:
• key1: 事件时间 1000ms
• key2: 事件时间 1200ms
• key3: 事件时间 800ms
在这种情况下,全局的 Watermark 将是 800ms,因为这是所有 key 中的最小事件时间
3.3、 Watermark 对窗口的影响
由于 Flink 的窗口操作(如滚动窗口、滑动窗口等)依赖于 Watermark 来触发窗口的计算,当并行度为 1 时:
- 不同 key 的事件仍然可以被分组到不同的窗口中,依据事件时间分配窗口。
- 但窗口计算的触发是基于全局 Watermark 的。这意味着,只有当全局 Watermark 超过某个窗口的结束时间时,Flink 才会触发该窗口的计算
如果有某些 key 的事件时间较晚到达,它们会延迟 Watermark 的推进,从而延迟所有 key 的窗口计算
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。