Flink的旁路输出(SideOutput)实战
概述
旁路输出在Flink中叫作SideOutput,用途类似于DataStream#split,本质上是一个数据流的切分行为,按照条件将DataStream切分为多个子数据流,子数据流叫作旁路输出数据流,每个旁路输出数据流可以有自己的下游处理逻辑。
旁路输出
[读取数据]---->[处理逻辑1]---------->[数据输出]---->[HDFS]
\--------->[数据输出]----->[mysql]
\-------->[数据输出]----->[clickhouse]
旁路输出数据流的元素的数据类型可以与上游数据流不同,多个旁路输出数据流的数据类型也不必相同。
旁路输出的作用
- 对数据流进行分割,但又不会复制数据流的一种分流机制。
- 对延迟迟到的数据进行处理,这样就可以不用丢弃迟到的数据。
- 能有效解决Split算子不能进行连续分流的问题。
实战
public class SideOutputBaseUse {
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
DataStream<Integer> input = env.fromElements(1, 2, 3,4,5,6,7,8);
// 定义旁路输出的tag,旁路输出时会绑定该tag。这里需要定义tag的名称。
final OutputTag<String> sideOutputTag1 = new OutputTag<String>("sideOutput1") {};
final OutputTag<String> sideOutputTag2 = new OutputTag<String>("sideOutput2") {};
// 处理数据时,输出到一个或多个侧输出流
SingleOutputStreamOperator<Integer> mainDataStream = input.process(
new ProcessFunction<Integer, Integer>() {
@Override
public void processElement(Integer value,
Context ctx,
Collector<Integer> out) throws Exception {
// 发出数据到常规输出
out.collect(value);
// 发出数据到侧流tag: sideOutputTag1
ctx.output(sideOutputTag1, "sideout1-" + String.valueOf(value));
// 发出数据到侧流tag: sideOutputTag2
ctx.output(sideOutputTag2, "sideout2-" + String.valueOf(value));
}
}
);
// 侧输出流1: 进行过滤,然后输出
mainDataStream.getSideOutput(sideOutputTag1).filter(
new FilterFunction<String>() {
@Override
public boolean filter(String s) throws Exception {
if (s.endsWith("2")) {
return false;
}
return true;
}
}
).print("sideoutTagStream1");
// 侧输出流2: 直接打印
mainDataStream.getSideOutput(sideOutputTag2).print("sideoutTagStream2");
env.execute("test side output");
}
}
以上代码的输出:
sideoutTagStream1:5> sideout1-1
sideoutTagStream2:5> sideout2-1
sideoutTagStream1:4> sideout1-8
sideoutTagStream1:3> sideout1-7
sideoutTagStream1:7> sideout1-3
sideoutTagStream1:2> sideout1-6
sideoutTagStream2:2> sideout2-6
sideoutTagStream2:6> sideout2-2
sideoutTagStream1:8> sideout1-4
sideoutTagStream2:8> sideout2-4
sideoutTagStream2:7> sideout2-3
sideoutTagStream2:3> sideout2-7
sideoutTagStream2:4> sideout2-8
sideoutTagStream1:1> sideout1-5
sideoutTagStream2:1> sideout2-5
这里要注意的是,定义好OutputTag之后,只有在特定的函数中才能使用旁路输出,如下:
1)ProcessFunction;
2)KeyedProcessFunction;
3)CoProcessFunction;
4)ProcessWindowFunction;
5)ProcessAllWindowFunction;
6)ProcessJoinFunction;
7)KeyedCoProcessFunction。
只有在上述函数中才可以通过Context上下文对象,向OutputTag定义的旁路中输出emit数据。
旁路输出编程范式
从以上实战的代码可以看出:旁路输出的数据(DataStream)可以被下游获取,还可以将旁路输出DataStream当作一般的DataStream进行处理。
从以上代码可以看出侧输出流的通用代码框架如下:
- 第1步:定义侧输出流的outputTag
final OutputTag<String> sideOutputTag1 = new OutputTag<String>("sideOutput1") {};
- 第2步:把主处理流程的流输出到侧输出流的Tag中(相当于与定义的outputTag绑定)
ctx.output(sideOutputTag1, "sideout1-" + String.valueOf(value));
注意:这一步是在ProcessFunction等函数中进行的。
- 第3步:获取已绑定的outputTag
mainDataStream.getSideOutput(sideOutputTag1).xxx
这样可以获取一个DataStream对象
- 第4步:对不同的侧输出流进行不同的处理,或则sink到不同的地方。
xxx.addsink(xxx);
也可以这样写。
总结
旁路输出是在不复制数据流的情况下。把一个数据流分割成多个流,并且不同的流进行不同的处理。通过这种方式来同一个流进行不同的处理非常方便,也是非常常用的一种流处理策略。