自定义一个watermark生成策略
watermark生成策略一般有两种,一种是自定义周期性的watermark,另一种是触发式的watermark。
WatermarkGenerator接口代码如下:
@Public
public interface WatermarkGenerator<T> {
/**
* 每来一条事件数据调用一次,可以检查或者记录事件的时间戳,或者也可以基于事件数据本身去生成 watermark。
*/
void onEvent(T event, long eventTimestamp, WatermarkOutput output);
/**
* 周期性的调用,也许会生成新的 watermark,也许不会。
*
* <p>调用此方法生成 watermark 的间隔时间由 {@link ExecutionConfig#getAutoWatermarkInterval()} 决定。
* env.getConfig.getAutoWatermarkInterval();
* <p>可以通过 env.getConfig.getAutoWatermarkInterval() 设置触发间隔
*/
void onPeriodicEmit(WatermarkOutput output);
}
自定义watermark
package org.example.watermark;
import org.apache.flink.api.common.eventtime.Watermark;
import org.apache.flink.api.common.eventtime.WatermarkGenerator;
import org.apache.flink.api.common.eventtime.WatermarkOutput;
public class MyStrategy implements WatermarkGenerator<Integer> {
long cur;
@Override
public void onEvent(Integer integer, long l, WatermarkOutput watermarkOutput) {
// 每来一条数据即更新一下当前的event-time
cur = Math.max(cur, l);
}
@Override
public void onPeriodicEmit(WatermarkOutput watermarkOutput) {
// 触发生成新的watermark
System.out.println("生成新的watermark: " + cur);
watermarkOutput.emitWatermark(new Watermark(cur));
}
}
使用watermark
package org.example.watermark;
import org.apache.flink.api.common.eventtime.WatermarkStrategy;
import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.windowing.assigners.TumblingEventTimeWindows;
import org.apache.flink.streaming.api.windowing.time.Time;
public class MyDemo1 {
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setParallelism(1);
env.getConfig().setAutoWatermarkInterval(5000);
// 1. 输入100,此时watermark是100
// 2. 再输入3000,此时触发window计算;输出100,watermark是3000
// 3. 再输入300,此时watermark是3000
// 4. 再输入6000,此时触发window计算;输出3000,watermark是6000,300已经被丢弃
DataStream<String> strSource = env.socketTextStream("127.0.0.1", 9000);
DataStream<Integer> source = strSource.map(Integer::valueOf);
// 使用自定义的watermark策略
DataStream<Integer> sourceWithWatermark = source.assignTimestampsAndWatermarks(
((WatermarkStrategy<Integer>) context -> new MyStrategy())
.withTimestampAssigner((elm, l) -> elm)
);
sourceWithWatermark.keyBy(k -> k % 5)
.window(TumblingEventTimeWindows.of(Time.seconds(3)))
.reduce(Integer::sum)
.print();
env.execute();
}
}
另一个案例
/* MyDemo1.java */
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import org.apache.flink.api.common.eventtime.WatermarkStrategy;
import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.windowing.assigners.TumblingEventTimeWindows;
import org.apache.flink.streaming.api.windowing.time.Time;
public class MyDemo1 {
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setParallelism(1);
// 设置每5s生成一个watermark
env.getConfig().setAutoWatermarkInterval(5000);
DataStream<String> strSource = env.socketTextStream("127.0.0.1", 9901);
// {"id": 1, "name": "tom", "val": 1, "ts": "2023-09-28 17:53:00"}
DataStream<MyObject> source = strSource.map(
str -> {
Gson gson = new Gson();
return gson.fromJson(str, new TypeToken<MyObject>(){}.getType());
}
);
DataStream<MyObject> sourceWithWatermark = source.assignTimestampsAndWatermarks(
((WatermarkStrategy<MyObject>) context -> new MyStrategy())
.withTimestampAssigner((elm, l) -> elm.getTs().getTime())
);
sourceWithWatermark.keyBy(MyObject::getId)
.window(TumblingEventTimeWindows.of(Time.seconds(3)))
.reduce((r1, r2) -> r1.val > r2.val ? r1 : r2)
.print();
env.execute();
}
}
/* MyStrategy.java */
import org.apache.flink.api.common.eventtime.Watermark;
import org.apache.flink.api.common.eventtime.WatermarkGenerator;
import org.apache.flink.api.common.eventtime.WatermarkOutput;
import java.text.SimpleDateFormat;
import java.util.Date;
public class MyStrategy implements WatermarkGenerator<MyObject> {
long cur;
Date date;
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
@Override
public void onEvent(MyObject myObject, long l, WatermarkOutput watermarkOutput) {
cur = Math.max(cur, myObject.getTs().getTime());
}
@Override
public void onPeriodicEmit(WatermarkOutput watermarkOutput) {
long mark = cur - 60000;
date = new Date(mark);
String format = sdf.format(date);
System.out.println("生成新的watermark: " + format);
watermarkOutput.emitWatermark(new Watermark(mark));
}
}
/* MyObject.java */
@Data
@ToString
public class MyObject implements Serializable {
Integer id;
String name;
Double val;
Timestamp ts;
}
在这个例子中,使用了自定义的watermark strategy,可接受数据的最大时延为1分钟。对数据按id hash后,添加了一个3秒的滑动窗口。算窗口内的value最大值。并输出。
知识点1
3秒1个点窗口是怎么划分的?
- 此处,当我们输入了第一条数据,例如为:
{"id": 1, "name": "tom", "val": 1, "ts": "2023-09-28 17:53:00"}
, - 那么窗口就是:
2023-09-28 17:53:00 ~ 2023-09-28 17:53:03
,注意是前闭后开。(注意,这里窗口范围是我随口邹的,实际窗口起点是有flink的内置算法结束的。实际的窗口范围计算方式,可以看org.apache.flink.streaming.api.windowing.assigners.WindowAssigner#assignWindows
类的子类实现方式) - 下一个窗口是:
2023-09-28 17:53:03 ~ 2023-09-28 17:53:06
。
何时会触发窗口计算
- 两个必要条件:
- watermark >= window end time,注意窗口是前闭后开。所以当等于时,就已经离开窗口了
- 窗口内有数据
watermark怎么确定的?
- 根据source算子,1. 所定义的watermark策略;2. 所实际接受到的数据的event time。共同计算得出。例如:第一条数据为:
{"id": 1, "name": "tom", "val": 1, "ts": "2023-09-28 17:53:00"}
,那么此时生成的watermark就是1695894720000,对应的物理时间就是:2023-09-28 17:52:00(因为我们有1分钟的延迟哦)。
迟到数据怎么算?
- 假如按照demo中的策略,有如下几条数据,输出结果是什么?
- 输入:
{"id": 1, "name": "tomA1", "val": 1, "ts": "2023-09-28 17:53:00"}
- 输入:
{"id": 1, "name": "tomA2", "val": 2, "ts": "2023-09-28 17:53:01"}
- 输入:
{"id": 1, "name": "tomX", "val": 2, "ts": "2023-09-28 17:50:00"}
- 输入:
{"id": 1, "name": "tomB", "val": 1, "ts": "2023-09-28 17:52:30"}
- 输入:
{"id": 1, "name": "tomC", "val": 1, "ts": "2023-09-28 17:53:33"}
- 触发窗口计算,输出:
{"id": 1, "name": "tomB", "val": 1, "ts": "2023-09-28 17:52:30"}
- 输入:
{"id": 1, "name": "tomD", "val": 1, "ts": "2023-09-28 17:54:03"}
- 触发窗口计算,输出:
{"id": 1, "name": "tomA2", "val": 2, "ts": "2023-09-28 17:53:01"}
- 总结一下大概的思路就是:
- 输入一条数据之后,结合策略,看是否要更新watermark。
- 结合watermark策略来看,这条数据是否过期:event time < watermark。注意,等于也不算过期。过期就丢弃,未过期,就暂存到对应窗口中。
- 看watermark是否
>=
窗口结束时间,若大于,就触发一次计算。否则,不管是当前窗口的,还是未来窗口的,都在内存中暂存着,等待watermark触发