文章目录
- Operators概述
- DataStream Transformations
- Map
- FlatMap
- Filter
- KeyBy
- Reduce
- Window
- WindowAll
- Window Apply
- WindowReduce
- Union
- Window Join
- Interval Join
- Window CoGroup
- Connect
- CoMap, CoFlatMap
- Iterate
- Physical Partitioning(物理分区)
- 自定义分区
- 随机分区
- Rescaling
- Broadcasting
- Task Chaining and Resource Groups(任务链和资源组)
- 算子链形成条件
- Start New Chain
- Disable Chaining
- Set Slot Sharing Group(设置slot共享组)
Operators概述
算子将一个或多个 DataStream
转换为新的 DataStream
。程序可以将多个转换组合成复杂的数据流拓扑。
本节描述了基本转换、应用这些转换后的有效物理分区以及对 Flink 算子链的深入了解。
DataStream Transformations
Map
DataStream → DataStream
取一个元素并产生一个元素。将输入流的值乘以二的Map
函数:
DataStream<Integer> dataStream = //...
dataStream.map(new MapFunction<Integer, Integer>() {
@Override
public Integer map(Integer value) throws Exception {
return 2 * value;
}
});
FlatMap
DataStream → DataStream
取一个元素并产生零个、一个或多个元素。将句子拆分为单词的 flatmap
函数:
dataStream.flatMap(new FlatMapFunction<String, String>() {
@Override
public void flatMap(String value, Collector<String> out)
throws Exception {
for(String word: value.split(" ")){
out.collect(word);
}
}
});
Filter
DataStream → DataStream
为每个元素计算一个布尔的函数,并保留那些返回true的元素。过滤掉0
值的过滤器函数:
dataStream.filter(new FilterFunction<Integer>() {
@Override
public boolean filter(Integer value) throws Exception {
return value != 0;
}
});
KeyBy
DataStream → KeyedStream
在逻辑上将流划分为不相交的分区。所有具有相同键的记录都分配到同一个分区。在内部,keyBy()
是通过哈希分区实现的。有多种specify keys的方法。
dataStream.keyBy(value -> value.getSomeKey());
dataStream.keyBy(value -> value.f0);
以下类型不能作为key
:
- 它是
POJO
类型,但不覆盖hashCode()
方法,并依赖于Object.hashCode()
实现。 - 它是数组类型。
Reduce
KeyedStream → DataStream
在键控数据流上做滚动计算。
keyedStream.reduce(new ReduceFunction<Integer>() {
@Override
public Integer reduce(Integer value1, Integer value2)
throws Exception {
return value1 + value2;
}
});
Window
KeyedStream → WindowedStream
Windows
可以在已经分区的KeyedStreams
上定义。Windows
根据某些特征(例如,最近5秒内到达的数据)对每个键中的数据进行分组。有关窗口的完整描述,请参阅“windows ”。
dataStream
.keyBy(value -> value.f0)
.window(TumblingEventTimeWindows.of(Time.seconds(5)));
WindowAll
DataStreamStream → AllWindowedStream
Windows
可以在通用的数据流上定义。Windows
根据某些特征(例如,最近5秒内到达的数据)对所有流事件进行分组。有关窗口的完整描述,请参阅“windows”。
提示:在许多情况下,这是一种非并行转换。windowAll
算子将在一个任务中收集所有记录。
dataStream
.windowAll(TumblingEventTimeWindows.of(Time.seconds(5)));
Window Apply
WindowedStream → DataStream
AllWindowedStream → DataStream
将通用函数应用于整个窗口。下面是一个手动对窗口元素求和的函数。
提示:如果您使用 windowAll
转换,则需要改用 AllWindowFunction
。
windowedStream.apply(new WindowFunction<Tuple2<String,Integer>, Integer, Tuple, Window>() {
public void apply (Tuple tuple,
Window window,
Iterable<Tuple2<String, Integer>> values,
Collector<Integer> out) throws Exception {
int sum = 0;
for (value t: values) {
sum += t.f1;
}
out.collect (new Integer(sum));
}
});
// applying an AllWindowFunction on non-keyed window stream
allWindowedStream.apply (new AllWindowFunction<Tuple2<String,Integer>, Integer, Window>() {
public void apply (Window window,
Iterable<Tuple2<String, Integer>> values,
Collector<Integer> out) throws Exception {
int sum = 0;
for (value t: values) {
sum += t.f1;
}
out.collect (new Integer(sum));
}
});
WindowReduce
WindowedStream → DataStream
对窗口应用reduce function
,并返回reduce
后的值
windowedStream.reduce (new ReduceFunction<Tuple2<String,Integer>>() {
public Tuple2<String, Integer> reduce(Tuple2<String, Integer> value1, Tuple2<String, Integer> value2) throws Exception {
return new Tuple2<String,Integer>(value1.f0, value1.f1 + value2.f1);
}
});
Union
DataStream → DataStream*
合并两个或者多个流而形成一个新的流。
Window Join
DataStream,DataStream → DataStream
在给定的key和普通窗口上连接两个流。
dataStream.join(otherStream)
.where(<key selector>).equalTo(<key selector>)
.window(TumblingEventTimeWindows.of(Time.seconds(3)))
.apply (new JoinFunction () {...});
Interval Join
KeyedStream,KeyedStream → DataStream
// this will join the two streams so that
// key1 == key2 && leftTs - 2 < rightTs < leftTs + 2
keyedStream.intervalJoin(otherKeyedStream)
.between(Time.milliseconds(-2), Time.milliseconds(2)) // lower and upper bound
.upperBoundExclusive(true) // optional
.lowerBoundExclusive(true) // optional
.process(new IntervalJoinFunction() {...});
Window CoGroup
DataStream,DataStream → DataStream
将给定键和公共窗口上的两个数据流组合在一起。
dataStream.coGroup(otherStream)
.where(0).equalTo(1)
.window(TumblingEventTimeWindows.of(Time.seconds(3)))
.apply (new CoGroupFunction () {...});
Connect
DataStream,DataStream → ConnectedStream
连接两个数据流,并保留其数据类型。连接的两个流可以共享其状态。
DataStream<Integer> someStream = //...
DataStream<String> otherStream = //...
ConnectedStreams<Integer, String> connectedStreams = someStream.connect(otherStream);
CoMap, CoFlatMap
ConnectedStream → DataStream
类似于连接数据流上的 map 和 flatMap。
connectedStreams.map(new CoMapFunction<Integer, String, Boolean>() {
@Override
public Boolean map1(Integer value) {
return true;
}
@Override
public Boolean map2(String value) {
return false;
}
});
connectedStreams.flatMap(new CoFlatMapFunction<Integer, String, String>() {
@Override
public void flatMap1(Integer value, Collector<String> out) {
out.collect(value.toString());
}
@Override
public void flatMap2(String value, Collector<String> out) {
for (String word: value.split(" ")) {
out.collect(word);
}
}
});
Iterate
DataStream → IterativeStream → ConnectedStream
通过将一个算子的输出重定向到某个先前的运算符,在流中创建一个“反馈”循环。这对于定义持续更新模型的算法特别有用。下面的代码从一个流开始,并不断地应用迭代体。大于 0 的元素被发送回反馈通道,其余元素被转发到下游。
IterativeStream<Long> iteration = initialStream.iterate();
DataStream<Long> iterationBody = iteration.map (/*do something*/);
DataStream<Long> feedback = iterationBody.filter(new FilterFunction<Long>(){
@Override
public boolean filter(Long value) throws Exception {
return value > 0;
}
});
iteration.closeWith(feedback);
DataStream<Long> output = iterationBody.filter(new FilterFunction<Long>(){
@Override
public boolean filter(Long value) throws Exception {
return value <= 0;
}
});
Physical Partitioning(物理分区)
Flink还通过以下函数提供了对转换后流进行分区(如果需要的话)。
自定义分区
DataStream → DataStream
使用用户定义的Partitioner
为每个元素选择目标任务。
dataStream.partitionCustom(partitioner, "someKey");
dataStream.partitionCustom(partitioner, 0);
随机分区
DataStream → DataStream
根据均匀分布随机划分元素。
dataStream.shuffle();
Rescaling
DataStream → DataStream
元素以循环方式分区到下游操作的子集中。如果您希望使用管道,例如,将源的每个并行实例扇出到多个映射器的子集,以分配负载,但不希望rebalance()
重新平衡,那么这非常有用。这将只需要本地数据传输,而不需要通过网络传输数据,具体取决于其他配置值,如TaskManager
的slots
数
上游操作发送元素的下游操作取决于上游和下游操作的并行度。例如,如果上游操作的并行度为2,而下游操作的并行度为6,那么一个上游操作将把元素分配给三个下游操作,而另一个上游操作将分配给其他三个下游操作。另一方面,如果下游操作的并行度为2,而上游操作的并行度为6,那么三个上游操作将分布到一个下游操作,而其他三个上游操作将分布到另一个下游操作。
在不同并行不是彼此的倍数的情况下,一个或多个下游操作将具有来自上游操作的不同数量的输入。
dataStream.rescale();
Broadcasting
DataStream → DataStream
向每个分区广播元素。
dataStream.broadcast();
Task Chaining and Resource Groups(任务链和资源组)
链接两个转换意味着将它们放在同一线程中以获得更好的性能。如果可能的话,Flink 默认链接操作符(例如,两个串联的map转换)。如果需要,API 可以对链接进行细粒度控制:
如果你想在整个作业中禁用链接,请使用StreamExecutionEnvironment.disableOperatorChaining()
。对于更细粒度的控制,可以使用以下函数。注意,这些函数只能在DataStream
转换之后使用,因为它们引用前面的转换。例如,你可以使用someStream.map(…). startnewchain()
,但是你不能使用someStream.startNewChain()
。
资源组是 Flink 中的一个槽,参见槽。如果需要,您可以将算子隔离在单独的slot
中。
算子链形成条件
算子链是Flink执行任务阶段中由StreamGraph生成JobGrap的过程中(Flink UI中看到的执行图)形成的。以下是算子链形成的主要条件:
- 上下游算子的并行度一致
- 下游节点的入度为1
- 上下游节点都在同一个 slot group 中
- 下游节点的 chain 策略为 ALWAYS(可以与上下游链接,map、flatmap、filter等默认是ALWAYS)
- 上游节点的 chain 策略为 ALWAYS 或 HEAD(只能与下游链接,不能与上游链接,Source默认是HEAD)
- 两个节点间数据分区方式是 forward
- 用户没有禁用 chain(代码中是否配置disableChain())
Start New Chain
开始一个新的链,从这个操作符开始。这两个映射器将被链接起来,而过滤器不会被链接到第一个映射器。
someStream.filter(...).map(...).startNewChain().map(...);
Disable Chaining
不要链接map
算子。
someStream.map(...).disableChaining();
Set Slot Sharing Group(设置slot共享组)
设置算子的slot共享组。Flink 会将具有相同slot
共享组的算子放在同一个槽中。而将没有slot共享组的操作保留在其他slot中。这可以用来隔离slot
,如果所有的输入操作都在同一个slot
共享组中,则slot
共享组从输入操作继承。默认插槽共享组的名称为“default”,可以通过调用slotSharingGroup("default")
将操作显式放入该组。
someStream.filter(...).slotSharingGroup("name");