指的是数据产生的时间或是说是数据发生的时间。
在Flink中有三种时间分别是:
Event Time:事件时间,数据产生的时间,可以反应数据真实发生的时间
Infestion Time:事件接收时间
Processing Time:事件处理时间
因为当使用Processing Time(事件的处理时间)来对数据进行处理,此时数据可能会乱序,没有办法还原数据本身的时间顺序,这种情况在Flink中会可能导致数据丢失,如果使用事件时间,它会根据事件真实发生的时间对数据排序,就不会出现数据乱序的情况。
总结来说,数据产生的时间就是事件时间,现实中实时的时间就是事件的处理时间
处理时间是接收数据过后对数据操作的时间。处理时间的会按照实时的时间触发。
public class Demo03ProcessingTime {
public static void main(String[] args) throws Exception{
/**
* 数据处理时间:一般会结合窗口使用,一般值的是接受数据后对数据操作的时间
* 需求:每过5秒中统计15秒内的单词的数量
*/
//构建Flink的环境
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
//使用socket模拟实时的操作
DataStreamSource wordDS = env.socketTextStream("master", 8888);
//将接受的数据的转换成kv的格式
SingleOutputStreamOperator> kvDS = wordDS
.map(word -> Tuple2.of(word, 1), Types.TUPLE(Types.STRING,Types.INT));
//按照单词进行分组
KeyedStream, String> keyByDS = kvDS
.keyBy(key -> key.f0);
//划分窗口,窗口的大小是10秒钟,滑动的时间是5秒钟
WindowedStream, String, TimeWindow> windowDS = keyByDS .
window(SlidingProcessingTimeWindows.of(Time.seconds(10), Time.seconds(5)));
//对统计的单词进行求和
SingleOutputStreamOperator> countDS = windowDS.sum(1);
countDS.print();
//启动Flink
env.execute();
}
}
数据产生的时间就是事件时间,不过在使用的时候使用的是时间戳。需要注意的是数据的时间与现实的时间是不一致的。
在使用事件时间的时候需要注意的是打入数据的数据时间是需要按照时间的顺序打入,否则数据就会丢失(也可以不按照顺序打入,后面有解决办法)
java,1699035731000
java,1699035732000
java,1699035735000
java,1699035733000
java,1699035736000
java,1699035737000
java,1699035740000
例如上述:数据总共有两个部分组成,前面是单词,后面的是单词数据产生的时间戳
public class Demo04EventTime {
public static void main(String[] args) throws Exception{
/**
* 需求:统计5秒内的单词的数量,使用的是事件时间滚动窗口
* 触发的条件是事件时间5秒
*/
//构建flink的环境
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
//需要并行度改成一
env.setParallelism(1);
//使用socket模拟实时的环境
DataStreamSource lineDS = env.socketTextStream("master", 8888);
/**
* java,1699035731000
* java,1699035732000
* java,1699035735000
* java,1699035733000
* java,1699035736000
* java,1699035737000
* java,1699035740000
*/
//此时的数据的格式并不是某一个单词,需要告诉flink哪一个是事件时间
//首先对数据进行格式处理
SingleOutputStreamOperator> kvDS = lineDS.map(line -> {
String[] split = line.split(",");
String word = split[0];
// String time = split[1];
long time1 = Long.parseLong(split[1]);
return Tuple2.of(word, time1);
}, Types.TUPLE(Types.STRING, Types.LONG));
//告诉Flink,哪一个是事件的时间
SingleOutputStreamOperator> assDS = kvDS.assignTimestampsAndWatermarks(
WatermarkStrategy
.>forBoundedOutOfOrderness(Duration.ofSeconds(5))
//指定事件时间
.withTimestampAssigner((kv, ts) -> kv.f1)
);
//统计5秒钟的单词的数量
DataStream>keyByDS = assDS
.map(kv -> Tuple2.of(kv.f0, 1),Types.TUPLE(Types.STRING,Types.INT));
//按照单词进行分组
KeyedStream, String> keyByDS1= keyByDS.keyBy(kv -> kv.f0);
//开窗
WindowedStream, String, TimeWindow> windowDS = keyByDS1.window(TumblingEventTimeWindows.of(Time.seconds(5)));
//对单词的数量进行统计
SingleOutputStreamOperator> countDS = windowDS.sum(1);
//打印数据
countDS.print();
//执行Flink的环境
env.execute();
}
}
1、水位线需要大于等于窗口的结束时间
2、窗口里面要存在数据
3、窗口的划分时间是从1970年1月1日0时0分0秒开始的,按照窗口的大小轮替
假设一个时间窗口是5秒,如果将此时的水位线向后推移5秒,假设4进入的时候,此时的水位线就变成-3,但是此时就不满足触发窗口的条件,此时假设遗漏的数据是3,此时的水位线依旧是小于窗口的时间,依旧不会触发窗口。
但是不能完全的保证数据不丢失,推移的时间越久,对于Flink的延迟就会越大。
//1、需要告诉flink哪一个字段是时间字段
//设置时间字段和水位线
DataStream> assDS = wordAndTsDS.assignTimestampsAndWatermarks(
WatermarkStrategy
//1、指定水位线等于时间最新一条数据的时间戳,数据不存在乱序的时候使用,如果数据乱序,可能会丢失数据
.>forMonotonousTimestamps()
//指定时间字段
.withTimestampAssigner((kv, ts) -> kv.f1)
);
//1、需要告诉flink哪一个字段是时间字段
//设置时间字段和水位线
DataStream> assDS = wordAndTsDS.assignTimestampsAndWatermarks(
WatermarkStrategy
//1、水位线生成方式:最新一条数据的时间戳减去5秒,会导致计算延迟触发
.>forBoundedOutOfOrderness(Duration.ofSeconds(5))
//指定时间字段
.withTimestampAssigner((kv, ts) -> kv.f1)
);
上图表示的是以Flink的流程图,图中总共有两个并行度,每一个Task上面都带着任务的时间,在Flink中,会将任务的时间向后传递,当途中上游map(1)将任务时间传递给下游window(1)时,下面的上游map(2)也会任务时间传递给下游window(1)(上游的任务是并行的),此时下游window(1)就会产生两个任务时间,此时就会选择时间最小的时间的作为水位线。因为当选择时间大的作为水位线,那么对于时间较小的数据可能会丢失。
因为上游的任务是并行执行的,指的时对于上游的所有的Task的水位线都需要逐步的向后推移。