Watermark和代码是基于 Flink1.12版本的,概念相似。有时间会陆续完善
Watermark包含几个重要的知识点
抽时间积累点知识,网上文档很多,仅根据自己的理解,概括性的记录下
名称: 水位线 , 水印都行,就一个名字而已
下文中的事件,指代数据库记录,log日志,流量日志,度量信息等等
Watermark应用场景,如果这两个场景不满足,则不太需要考虑Watermark
1. window算子
2. EventTime作为计算时间
注:Flink有三个时间,IngestionTime, ProcessTime和EventTime
IngestionTime:进入Flink的时间,也就是在source里面生成
ProcessTime:算子执行的时间,取机器本地时间,没有特殊要求可以考虑这个,简单暴力
EventTime:事件时间,取事件生成时的创建时间,这个与现实最接近,日常生产考虑使用这个
为什么要用EventTime作为Flink计算时间?
真实场景下,数据可能乱序和迟到,没有window的场景下,只需要经过ETL过程,直接流到下游即可
有window的场景下,需要确定窗口能正常关闭,并执行统计逻辑。
假设ProcessTime作为Flink时间,window范围[1000,5000),机器时间已经>=5000,窗口触发执行,然后来了一条事件(假设事件时间:4500),理论上这个事件属于这个窗口,但是该窗口已经执行完成了,这条事件则未统计到。
所以,为了尽可能的保证生成的数据能被Flink处理到,选择EventTime作为Flink计算时间
Watermark是什么样的?
Flink包含四种类型的数据(继承自抽象类StreamElement),Watermark是其中的一种类型,该类仅包含唯一一个long类型的timestamp变量,该表量记录时间,单调递增。
Watermark的作用
Watermark计算方式
Watermark=max(窗口内所有事件时间) - 延迟执行时间
Window触发的充分必要条件
1. Window内有数据
2. Watermark >= Window的EndTime
开发中涉及Watermark的相关代码,参考如下:
/** 数据样例
1,zhangsan,18,2-2,61,1000
1,zhangsan,18,2-2,70.5,6000
1,zhangsan,18,2-2,20,2000
1,zhangsan,18,2-2,30,8000
1,zhangsan,18,2-2,40,9000
1,zhangsan,18,2-2,50.5,4000
1,zhangsan,18,2-2,60,10000
1,zhangsan,18,2-2,100,13000
1,zhangsan,18,2-2,200,18000
2,lisi,18,2-2,200,3000
2,lisi,18,2-2,200,18000
*/
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setParallelism(1);
// 设置周期性生成Watermark的时间
env.getConfig().setAutoWatermarkInterval(100);
DataStreamSource<String> source = env.socketTextStream("localhost", 9999);
SingleOutputStreamOperator<Item> mapSource = source.map(new MapFunction<String, Item>() {
Item item = new Item();
@Override
public Item map(String value) throws Exception {
String[] ss = value.split(",");
item.setId(Integer.parseInt(ss[0]));
item.setName(ss[1]);
item.setAge(Integer.parseInt(ss[2]));
item.setClassName(ss[3]);
item.setScore(Float.parseFloat(ss[4]));
item.setEventTime(Long.parseLong(ss[5]));
return item;
}
});
// 如下给出了几种生成Watermark的方法,常用的就是第一种。forBoundedOutOfOrderness表示window延迟执行的时间
WatermarkStrategy<Item> watermarkStrategy1 = WatermarkStrategy.<Item>forBoundedOutOfOrderness(Duration.ofSeconds(0)).withTimestampAssigner(new SerializableTimestampAssigner<Item>() {
@Override
public long extractTimestamp(Item element, long recordTimestamp) {
return element.getEventTime();
}
}).withIdleness(Duration.ofSeconds(10));
WatermarkStrategy<Item> watermarkStrategy2 = WatermarkStrategy.<Item>forBoundedOutOfOrderness(Duration.ofSeconds(5)).withTimestampAssigner((event, timestamp) -> event.getEventTime());
WatermarkStrategy<Item> watermarkStrategy3 = WatermarkStrategy.<Item>forBoundedOutOfOrderness(Duration.ofSeconds(5)).withTimestampAssigner(new TimestampAssignerSupplier<Item>() {
@Override
public TimestampAssigner<Item> createTimestampAssigner(Context context) {
return new TimestampAssigner<Item>() {
@Override
public long extractTimestamp(Item element, long recordTimestamp) {
return element.getEventTime();
}
};
// return (element, recordTimestamp) -> element.getEventTime(); // lambda表达式
}
});
// 应用Watermark
KeyedStream<Item, Integer> keyedStream = mapSource.assignTimestampsAndWatermarks(watermarkStrategy1).keyBy(new KeySelector<Item, Integer>() {
@Override
public Integer getKey(Item value) throws Exception {
return value.getId();
}
});
SingleOutputStreamOperator<String> processStream = keyedStream.window(TumblingEventTimeWindows.of(Time.seconds(5)))
.process(new ProcessWindowFunction<Item, String, Integer, TimeWindow>() {
@Override
public void process(Integer integer, Context context, Iterable<Item> elements, Collector<String> out) throws Exception {
StringBuilder sb = new StringBuilder();
float sum = 0;
int count = 0;
String name = "";
for (Item item : elements) {
name = item.getName();
count += 1;
sum += item.getScore();
}
sb.append(integer).append(",")
.append(name).append(",")
.append(count).append(",")
.append(sum)
.append("\t****** 窗口范围:").append(context.window().getStart()).append(" - ").append(context.window().getEnd())
.append("\t****** 窗口水位线:").append(context.currentWatermark());
out.collect(sb.toString());
}
});
processStream.print();
env.execute("Watermark测试");