Flink个人学习整理-WaterMark篇(六)
1、Flink中的时间语义
1.12之前时间语义分为3种
I、处理时间(默认)
II、事件时间
III、数据进入时间
1.12进行了更改 时间语义合并为2种
I、处理时间
II、事件时间(默认)
处理时间
处理时间是指的执行操作的各个设备的时间。
处理时间是最简单时间语义, 数据流和设备之间不需要做任何的协调.。
他提供了最好的性能和最低的延迟.。但是, 在分布式和异步的环境下, 处理时间没有办法保证确定性, 容易受到数据传递速度的影响: 事件的延迟和乱序
事件时间
事件时间是指的这个事件发生的时间。
在事件时间体系中, 时间的进度依赖于数据本身, 和任何设备的时间无关。作用就相当于现实时间的时钟
注意:
在1.12之前默认的时间语义是处理时间, 从1.12开始, Flink内部已经把默认的语义改成了事件时间
WaterMark是一条特殊的数据,时间戳
watermark作用:
1、处理乱序问题。(通过延迟关窗)
2、在系统中会认为小于等于该事件时间的已经全部到齐,其内部会减1毫秒,单调递增。
在乱序流中,需要通过设置延迟时间,延迟通过数据间的最大时间差确定。
自定义WaterMark,有两种周期型(periodic)默认每200ms生成一个、间歇性(punctuated)每条数据生成一个
水位线策略:WaterStrategy
自增:.< T >forMonotonousTimestamps
乱序延迟:< T >forBoundedOutOfOrderness
public class Flink_Time_EventTumblingWindows {
public static void main(String[] args) throws Exception {
// 获取运行时环境
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setParallelism(1);
// 设置水位线生成器,为自增类型 允许延迟为0
WatermarkStrategy<Sensor> waterSensorWatermarkStrategy = WatermarkStrategy.<Sensor>forMonotonousTimestamps()
// 分配时间戳
.withTimestampAssigner(new SerializableTimestampAssigner<Sensor>() {
@Override
public long extractTimestamp(Sensor element, long recordTimestamp) {
// 因为数据时间戳是10位 为s,watermark需要的是毫秒。因此 * 1000
return element.getTs() * 1000L;
}
});
// 设置水位线生成器,为乱序,增加延迟2s,延迟不会改变数据在哪个窗口,只会延迟窗口的关闭
// 延迟通过数据间的最大时间差确定。
WatermarkStrategy<Sensor> waterSensorWatermarkStrategy1 = WatermarkStrategy.<Sensor>forBoundedOutOfOrderness(Duration.ofSeconds(2))
.withTimestampAssigner(new SerializableTimestampAssigner<Sensor>() {
@Override
public long extractTimestamp(Sensor element, long recordTimestamp) {
// 因为数据时间戳是10位 为s,watermark需要的是毫秒。因此 * 1000
return element.getTs() * 1000L;
}
});
env.socketTextStream("localhost",9999)
// 将数据转化为JavaBean
.map(new MapFunction<String, Sensor>() {
@Override
public WaterSensor map(String value) throws Exception {
String[] strings = value.split(",");
return new Sensor(
strings[0],
Long.parseLong(strings[1]),
Integer.parseInt(strings[2]));
}
})
// 提取数据中的时间戳字段
.assignTimestampsAndWatermarks(waterSensorWatermarkStrategy1)
.keyBy(new KeySelector<Sensor, String>() {
@Override
public String getKey(Sensor value) throws Exception {
return value.getId();
}
})
.window(TumblingEventTimeWindows.of(Time.seconds(5)))
.sum("vc")
.print("Event-TumblingWindow");
env.execute();
}
}
保障数据完整性:
1、水位线策略:乱序延迟:< T >forBoundedOutOfOrderness
2、允许迟到数据,增量计算
3、侧输出流
public class Flink_Time_EventTumbling_LastAndSideOutPut {
public static void main(String[] args) throws Exception {
// 获取运行时环境
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setParallelism(1);
SingleOutputStreamOperator<Sensor> result = env.socketTextStream("localhost", 9999)
.map(new MapFunction<String, Sensor>() {
@Override
public Sensor map(String value) throws Exception {
String[] strings = value.split(",");
return new Sensor(
strings[0],
Long.parseLong(strings[1]),
Integer.parseInt(strings[2])
);
}
})
// 设置水位线模式,延迟
.assignTimestampsAndWatermarks(WatermarkStrategy.<Sensor>forBoundedOutOfOrderness(Duration.ofSeconds(2))
.withTimestampAssigner(new SerializableTimestampAssigner<Sensor>() {
@Override
public long extractTimestamp(Sensor element, long recordTimestamp) {
// 因为数据是10位时间戳,然而水位线需要的是ms,*1000L
return element.getTs() * 1000L;
}
}))
.keyBy(new KeySelector<Sensor, String>() {
@Override
public String getKey(Sensor value) throws Exception {
return value.getId();
}
})
.window(TumblingEventTimeWindows.of(Time.seconds(5)))
// 配置允许迟到数据 2s内 数据变为增量计算 在WaterMark基础上再延迟2s
.allowedLateness(Time.seconds(2))
// 注意写:{}
.sideOutputLateData(new OutputTag<Sensor>("Site"){})
.sum("vc");
// 正常数据流
result.print();
// 侧输出流 窗口关闭后,延迟的数据每条数据依次输出
// 注意写:{}
DataStream<Sensor> site = result.getSideOutput(new OutputTag<Sensor>("Site"){});
site.print("Site");
env.execute();
}
}
自定义WaterMark
1、周期型
2、间歇型
public class Flink_Time_EventTumblingWindows_CustomerPeriod {
public static void main(String[] args) throws Exception {
// 获取运行时环境
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setParallelism(1);
// 设置WaterMark自动提交时间
// env.getConfig().setAutoWatermarkInterval(500L);
// 自定义WaterMark
WatermarkStrategy<Sensor> watermarkStrategy = new WatermarkStrategy<Sensor>() {
@Override
public WatermarkGenerator<Sensor> createWatermarkGenerator(WatermarkGeneratorSupplier.Context context) {
// 延迟2s
return new MyPeriod(2000L);
}
}.withTimestampAssigner(new SerializableTimestampAssigner<Sensor>() {
@Override
public long extractTimestamp(Sensor element, long recordTimestamp) {
return element.getTs()*1000L;
}
});
env.socketTextStream("localhost",9999)
// 将数据转化为JavaBean
.map(new MapFunction<String, Sensor>() {
@Override
public Sensor map(String value) throws Exception {
String[] strings = value.split(",");
return new Sensor(
strings[0],
Long.parseLong(strings[1]),
Integer.parseInt(strings[2]));
}
})
// 提取数据中的时间戳字段
.assignTimestampsAndWatermarks(watermarkStrategy)
.keyBy(new KeySelector<Sensor, String>() {
@Override
public String getKey(Sensor value) throws Exception {
return value.getId();
}
})
.window(TumblingEventTimeWindows.of(Time.seconds(5)))
.sum("vc")
.print("Event-TumblingWindow周期型的WaterMark");
env.execute();
}
// 自定义周期型的WaterMark生成器
public static class MyPeriod implements WatermarkGenerator<Sensor>{
// 不能直接在这赋值为Long的最小值,如果设置后就变为了MINLONG-一个数,那么就会变为MAXLONG
private Long maxTs;
// 延迟时间
private Long maxDelay = 0L;
public MyPeriod(Long maxDelay) {
this.maxDelay = maxDelay;
this.maxTs = Long.MIN_VALUE + maxDelay + 1;
}
// 当数据来的时候调用
@Override
public void onEvent(Sensor event, long eventTimestamp, WatermarkOutput output) {
maxTs = Math.max(eventTimestamp,maxTs);
}
// 周期性的把WaterMark发射出去, 默认周期是200ms
@Override
public void onPeriodicEmit(WatermarkOutput output) {
output.emitWatermark(new Watermark(maxTs-maxDelay));
}
}
}
public class Flink_Time_EventTumblingWindows_CustomerPunct {
public static void main(String[] args) throws Exception {
// 获取运行时环境
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setParallelism(1);
// 自定义WaterMark
WatermarkStrategy<Sensor> watermarkStrategy = new WatermarkStrategy<Sensor>() {
@Override
public WatermarkGenerator<Sensor> createWatermarkGenerator(WatermarkGeneratorSupplier.Context context) {
// 延迟2s
return new MyPunct(2000L);
}
}.withTimestampAssigner(new SerializableTimestampAssigner<Sensor>() {
@Override
public long extractTimestamp(Sensor element, long recordTimestamp) {
return element.getTs()*1000L;
}
});
env.socketTextStream("localhost",9999)
// 将数据转化为JavaBean
.map(new MapFunction<String, Sensor>() {
@Override
public Sensor map(String value) throws Exception {
String[] strings = value.split(",");
return new Sensor(
strings[0],
Long.parseLong(strings[1]),
Integer.parseInt(strings[2]));
}
})
// 提取数据中的时间戳字段
.assignTimestampsAndWatermarks(watermarkStrategy)
.keyBy(new KeySelector<Sensor, String>() {
@Override
public String getKey(Sensor value) throws Exception {
return value.getId();
}
})
.window(TumblingEventTimeWindows.of(Time.seconds(5)))
.sum("vc")
.print("Event-TumblingWindow间歇性的WaterMark");
env.execute();
}
// 自定义间歇性的WaterMark生成器
public static class MyPunct implements WatermarkGenerator<Sensor>{
// 不能直接在这赋值为Long的最小值,如果设置后就变为了MINLONG-一个数,那么就会变为MAXLONG
private Long maxTs;
// 延迟时间
private Long maxDelay = 0L;
public MyPunct(Long maxDelay) {
this.maxDelay = maxDelay;
this.maxTs = Long.MIN_VALUE + maxDelay + 1;
}
// 当数据来的时候调用
@Override
public void onEvent(Sensor event, long eventTimestamp, WatermarkOutput output) {
maxTs = Math.max(eventTimestamp,maxTs);
output.emitWatermark(new Watermark(maxTs-maxDelay));
}
// 周期性的把WaterMark发射出去, 默认周期是200ms
@Override
public void onPeriodicEmit(WatermarkOutput output) {
}
}
}
非1并行度下WaterMark的传递问题
WaterMark传递机制
1、WaterMark是向下游广播的
2、在多并行度条件下,下游接收多个WaterMark,以最小的那个为准
注意:在Source后和Map后提取WaterMark所造成的结果是不一样的
3、当WaterMark值没有增长的时候,不会向下游传递,生成不变