flink 乱序数据处理
对于乱序数据,flink window 可以延迟一定的时间来进行触发(设置乱序延迟时间)和窗口触发后相较于当前窗口的最大数据时间,允许当前输入数据迟到一定时间可正常纳入到上一个窗口(设置允许迟到时间),从而规避一定程度的乱序数据影响。
BoundedOutOfOrderness 乱序时间
如下:
如图蓝色窗口大小为10s, 没有设置乱序延迟时间,其中数据类型为Tuple2<String, timestamp>对于第二个窗口中的黄色框数据
a, 3
,和红色框中的数据a,7
, 由于第二个窗口的有效数据范围为[10,20),所以其在窗口触发计算时会被剔除。
如图紫色窗口的大小为10s并设置了乱序延迟时间5s(上图只表示窗口触发的时机,数据范围的表示并不准确),所以其第一个窗口触发范围为[0,15), 其相较于第一个蓝色窗口而言延迟了5s触发,在这5s中第一个窗口计算时候的数据有效范围仍然为[0, 10),所以a,3
可以参与第一个窗口的计算,而第二窗口的有效计算数据范围为[10,20),所以蓝,紫色的窗口中该数据都会被剔除,可以使用侧流输出,将(迟到)数据保存起来
窗口触发计算的时间
设窗口大小为a, 乱序延迟时间为d, 当前窗口为n
窗口有效数据范围:[(n-1)*a, a*
n)
无乱序时间窗口触发数据范围(watermark):[(n-1)*a, a*
n)
有乱序时间窗口触发数据范围: [ (n-1)*a, a*
n+d)
当数据范围中的最大值大于等于右边界的价窗口就会触发计算
当该窗口中的数据不在窗口的有效数据范围内在窗口触发计算时数据就会被剔除,不会参于当前窗口的计算中
/**
* eventTime window
* e.g: window.size 5 seconds
* .type tumbling
* 窗口大小为5s,代数表达式为[n, n+5)
* 触发:
* 窗口内的数据达到窗口边界(>n+5)触发,边界值并不参与改成窗口的计算
* 如:输入
* a 1
a 4
a 5
a 6
a 4
a 10
a 20
输出:
(a_4,5) //a 5 到达时触发,并将a 5 计入下个窗口,当前参与计算的窗口内容为a 1, a 4
(a_6,11) //a 10 到达触发,小于窗口左边界的数据将丢弃(窗口大小:[5,10)), 窗口就算内容a 5, a 6。输入的a 4被丢弃
(a,10) //a 20 到达触发,窗口计算内容a 10,a 20计入下个窗口
* 乱序延迟boundedOutOfOrderness:
* 其会将允许设置延迟的数据((n-dayle, n+5+dayle))保留
* e.g 允许最大延迟 2s(dayle)
* 触发:
* 窗口内的数据达到窗口边界(>n+5)触发
* 允许延迟的数据:
* >窗口左边界-dayle的保留,<窗口左边界-dayle的丢弃
* 如:输入
a 1
a 5
a 6
a 7 // 触发,到达触发边界 size:5 + dayle:2
a 3 // 丢弃,< 左边界 (n-1)*size-dayle: (2-1)*5-2
a 4
a 10
a 12 //触发
a 10
a 9
a 8
a 22 //触发
输出:
w1:(a,1)
w2:(a_6_7,18)
w3:(a_12_10,32)
*/
public class TransformOperator {
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setParallelism(1);
DataStreamSource<Tuple2<String, Integer>> dataStreams = env
.addSource(new SourceFunction<Tuple2<String, Integer>>() {
Random random = new Random();
int tp2 = 0;
int speed = 0;
int f1 = -1;
@Override
public void run(SourceContext<Tuple2<String, Integer>> ctx) throws Exception {
while (true) {
TimeUnit.SECONDS.sleep(1);
tp2 = Math.abs(random.nextInt() % 7);
f1 = Math.abs(++speed + tp2);
ctx.collect(Tuple2.of("a", f1));
System.out.println("source generator :\t" + f1);
/* f1++;
ctx.collect(Tuple2.of("a", f1));
System.out.println("source generator:\t" + f1);*/
}
}
@Override
public void cancel() {
}
});
// ReduceWindowPrint(dataStreams);
//延迟数据触发边界测试
SingleOutputStreamOperator<Tuple2<String, Integer>> socketTextStream = env
.socketTextStream("10.164.29.143", 10086)
.flatMap(new FlatMapFunction<String, Tuple2<String, Integer>>() {
@Override
public void flatMap(String value, Collector<Tuple2<String, Integer>> out) throws Exception {
try {
String[] infos = value.split("\\s+");
if (infos.length == 2) {
out.collect(Tuple2.of(infos[0], Integer.valueOf(infos[1])));
}
} catch (NumberFormatException e) {
System.out.println("format exception by:\t" + value);
}
}
});
// ReduceWindowPrint(socketTextStream, 0);
ReduceWindowPrint(socketTextStream, 5);
env.execute("event time process ");
}
private static void ReduceWindowPrint(SingleOutputStreamOperator<Tuple2<String, Integer>> dataStreams, int dayleTime) {
dataStreams
.assignTimestampsAndWatermarks(
WatermarkStrategy
.<Tuple2<String,Integer>>forBoundedOutOfOrderness(Duration.ofSeconds(5))
.withTimestampAssigner((e, t) -> e.f1*1000)
.withIdleness(Duration.ofSeconds(10))
)
.keyBy(e -> e.f0)
.window(TumblingEventTimeWindows.of(Time.seconds(10)))
.reduce(new ReduceFunction<Tuple2<String, Integer>>() {
@Override
public Tuple2<String, Integer> reduce(Tuple2<String, Integer> value1, Tuple2<String, Integer> value2) throws Exception {
return Tuple2.of(value1.f0+"_"+value2.f1, value2.f1+value1.f1);
}
})
.print();
}
}
allowedLateness 允许迟到时间
合理的设置allowedLateness可以保留那个图中的红色被删除数据,比如 3
窗口大小: 10s
watermark 乱序延迟时间: 2s
窗口数据允许迟到时间:5s
输入数据:
a 1
a 10
a 12 -- 第一个窗口触发
a 9 -- 迟到数据
a 16 -- 第二个窗口内的数据, < 10 + 5 + 2, 属于上一个窗口数据范围内的数据仍然有效
a 9 -- 迟到数据
a 17 -- >= 10 + 5 + 2 使当前输入中的上一个窗口数据范围内的数据失效,将会被剔除
a 9 -- 迟到且未能被上一个窗口处理,即被剔除
a 22 -- 第二个窗口触发
输出:
(maina,1)
===========第一个窗口触发,下面数据 < 10的都是迟到数据=========== end by: 10000
(maina,1) -- 上一个窗口中的数据
(maina,9) -- 新输入的迟到数据
========================== end by: 10000
(maina,1)
(maina,9)
(maina,9) -- 新输入的迟到数据
========================= end by: 10000
later_(a,9) -- 新输入的迟到数据,上一个窗口失效,未能纳入上一个窗口
(maina,10)
(maina,12)
(maina,16)
(maina,17)
==========第二个窗口触发========== end by: 20000
代码:
public static void main(String[] args) throws Exception {
OutputTag<String> exceptionDataOutput = new OutputTag("exceptionDataSideOut", Types.STRING()){};
OutputTag<Tuple2<String, Integer>> lateDataOutput = new OutputTag("lateDataSideOut", TypeInformation.of(new TypeHint<Tuple2<String, Integer>>() {
})){};
StreamExecutionEnvironment env = Envs.getDataStreamEnv();
env.setParallelism(1);
DataStreamSource<String> textStream = env.socketTextStream("localhost", 10010);
SingleOutputStreamOperator<Tuple2<String, Integer>> tuple2Stream = textStream
//输入数据处理成tuple2<String, Integer>
.process(new Tuple2Flatmap(exceptionDataOutput))
.assignTimestampsAndWatermarks(WatermarkStrategy
.<Tuple2<String, Integer>>forBoundedOutOfOrderness(Duration.ofSeconds(2))
.withTimestampAssigner((tuple2, timestamp) -> tuple2.f1 * 1000)
)
.keyBy(tuple2 -> tuple2.f0)
.window(TumblingEventTimeWindows.of(Time.seconds(10)))
/*
设置了迟到数据延时,当当前窗口中的数据最大值 >= 窗口左边界+laterTime+outOfOrdernessTime就会使上一个窗口有效数据范围在本窗口中失效
max(in window data ) >= leftBound + allowedLateness:5 + forBounderOutOfOrderness:2, 当前窗口中 上一个窗口范围内的数据失效
*/
.allowedLateness(Time.seconds(5))
.sideOutputLateData(lateDataOutput)
.apply(new WindowFunction<Tuple2<String, Integer>, Tuple2<String, Integer>, String, TimeWindow>() {
/**
*
* @param s key
* @param window
* @param input input data set
* @param out outputCollector
* @throws Exception
*/
@Override
public void apply(String s, TimeWindow window, Iterable<Tuple2<String, Integer>> input, Collector<Tuple2<String, Integer>> out) throws Exception {
input.forEach(e -> out.collect(e));
System.out.println("========================== end by: " + window.getEnd());
}
});
//主流
tuple2Stream.process(new ProcessFunction<Tuple2<String, Integer>, Tuple2<String, Integer>>() {
@Override
public void processElement(Tuple2<String, Integer> value, ProcessFunction<Tuple2<String, Integer>, Tuple2<String, Integer>>.Context ctx, Collector<Tuple2<String, Integer>> out) throws Exception {
value.setField("main" + value.f0, 0);
out.collect(value);
}
}).print();
// 异常流
// tuple2Stream
// .getSideOutput(exceptionDataOutput)
// .print();
//允许延迟5秒流
tuple2Stream
.getSideOutput(lateDataOutput)
.map(e -> "later_"+e)
.print();
env.execute("timeWindowDemo");
}
设:
窗口大小 s
乱序延迟时间:d
允许迟到时间: L
上一个窗口结束边界 e
当前窗口最大值 c
上一个窗口数据失效时间为:max© >= e+L+d
失效的迟到数据可以通过window.sideOutputLateData来进行输出