文章目录

  • 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() 重新平衡,那么这非常有用。这将只需要本地数据传输,而不需要通过网络传输数据,具体取决于其他配置值,如TaskManagerslots

上游操作发送元素的下游操作取决于上游和下游操作的并行度。例如,如果上游操作的并行度为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");