时间属性
Flink中有三种不同的时间概念来处理流数据
- 处理时间(Processing Time ):使用的是机器本身的时间作为标准
- 事件时间(Event Time): 需要处理的流中的数据发生的时间,以数据中带的时间戳为标准
- 摄取时间(Ingestion Time):*事件进入Flink的时间;在内部它的处理类似于事件时间。
Flink默认使用的是处理时间,如果想使用其他两个时间,可以在执行环境中指定
final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setStreamTimeCharacteristic(TimeCharacteristic.ProcessingTime); // 默认
// env.setStreamTimeCharacteristic(TimeCharacteristic.IngestionTime);
// env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime);
处理时间
有两种方法定义处理时间
使用DataStream转换到Table时定义
注意到第二行后面的UserActionTime.proctime,它是在末尾附加一个逻辑字段UserActionTime.proctime,然后调用Table API时就在这个附加的字段上面定义时间窗口
DataStream<Tuple2<String, String>> stream = ...;
// 将附加的逻辑字段声明为处理时间属性
Table table = tEnv.fromDataStream(stream, "Username, Data, UserActionTime.proctime");
WindowedTable windowedTable = table.window(Tumble.over("10.minutes").on("UserActionTime").as("userActionWindow"));
使用TableSource定义
这种相当与直接指定一个source
,然后转化成Table
,注册到Table
环境里面,需要实现StreamTableSource
和DefinedProctimeAttribute
这两个接口
// 使用processing属性定义表源
public class UserActionSource implements StreamTableSource<Row>, DefinedProctimeAttribute {
@Override
public TypeInformation<Row> getReturnType() {
String[] names = new String[] {"Username" , "Data"};//这里可以根据具体业务调整
TypeInformation[] types = new TypeInformation[] {Types.STRING(), Types.STRING()};
return Types.ROW(names, types);
}
@Override
public DataStream<Row> getDataStream(StreamExecutionEnvironment execEnv) {
// 创建一个stream
DataStream<Row> stream = ...;
return stream;
}
@Override
public String getProctimeAttribute() {
// 附加的定义处理时间的字段
return "UserActionTime";
}
}
// 注册table source
tEnv.registerTableSource("UserActions", new UserActionSource());
WindowedTable windowedTable = tEnv
.scan("UserActions")
.window(Tumble.over("10.minutes").on("UserActionTime").as("userActionWindow"));
事件时间
事件时间根据流中的数据的字段定义窗口的时间标准,这样做的好处就是,当要处理的数据由于网络抖动等原因,延迟到达,或者数据本身是无序的情况下,还能顺序地处理数据
为了处理无序数据和区分数据流中的准时数据和延迟数据,需要根据数据本身的字段,提取出一个时间戳,定义为一个水印
跟处理时间类似,事件时间也有两种定义方式
使用DataStream转换到Table时定义
首先必须在DataStream上定义时间戳和水印
DataStream<Tuple2<String, Long>> stream = ...;
DataStream<Tuple2<String, Long>> data = stream.assignTimestampsAndWatermarks(new TimestampsAndWatermarks()); //TimestampsAndWatermarks为我们自己写的实现添加水印的接口的类
/**
* 添加时间水印
*/
private static class TimestampsAndWatermarks extends BoundedOutOfOrdernessTimestampExtractor<Tuple2<String, String>> {
//最大时间延迟
public TimestampsAndWatermarks() {
super(Time.milliseconds(1000));
}
@Override
public long extractTimestamp(Tuple2<String, Long> time) {
return time.f1;
}
}
然后在将数据流转化为表时,也有两种定义时间属性的方法,取决于指定的.rowtime字段名是否存在于DataStream字段中,无论哪种情况,事件时间戳字段都将保存DataStream事件时间戳的值
- 作为新字段添加到模式
- 替换现有字段。
// 添加时间戳水印
DataStream<Tuple2<String, String>> stream = inputStream.assignTimestampsAndWatermarks(...);
// 将附加的逻辑字段声明为事件时间属性
Table table = tEnv.fromDataStream(stream, "Username, Data, UserActionTime.rowtime");
// 用第一个Long作为时间戳,不需要再附加逻辑字段
DataStream<Tuple3<Long, String, String>> stream = inputStream.assignTimestampsAndWatermarks(...);
Table table = tEnv.fromDataStream(stream, "UserActionTime.rowtime, Username, Data");
//可以这样使用
WindowedTable windowedTable = table.window(Tumble.over("10.minutes").on("UserActionTime").as("userActionWindow"));
使用TableSource
类似于处理时间的使用方法
public class UserActionSource implements StreamTableSource<Row>, DefinedRowtimeAttribute {
@Override
public TypeInformation<Row> getReturnType() {
String[] names = new String[] {"Username", "Data", "UserActionTime"};
TypeInformation[] types =
new TypeInformation[] {Types.STRING(), Types.STRING(), Types.LONG()};
return Types.ROW(names, types);
}
@Override
public DataStream<Row> getDataStream(StreamExecutionEnvironment execEnv) {
// create stream
// ...
// 这里需要定义时间戳水印
DataStream<Row> stream = inputStream.assignTimestampsAndWatermarks(...);
return stream;
}
@Override
public String getRowtimeAttribute() {
// 将“UserActionTime”属性标记为事件时间属性。
return "UserActionTime";
}
}
// 注册table source
tEnv.registerTableSource("UserActions", new UserActionSource());
WindowedTable windowedTable = tEnv
.scan("UserActions")
.window(Tumble.over("10.minutes").on("UserActionTime").as("userActionWindow"));