Flink之多流转换

169 阅读3分钟

1. union

所有流的数据类型必须一致。

 stream1.union(stream2, stream3, ...)

对于合流之后的水位线,也是要以原有的流中最小的那个为准,这样才可以保证所有流都不会再传来之前的数据。换句话说,多流合并时处理的时效性是以最慢的那个流为准的。

2. connect

连接两个不同类型的数据流,connect()算子得到一个ConnectedStreams ,之后可以通过map/flatMap/process转换成同一种数据类型的流。

 connectedStreams.map(new CoMapFunction<...>())
     
 connectedStreams.flatMap(new CoFlatMapFunction<...>())
     
 connectedStreams.process(new CoProcessFunction<>())

值得一提的是, ConnectedStreams 也可以直接调用.keyBy()进行按键分区的操作,得到的还是一个ConnectedStreams:

 connectedStreams.keyBy(keySelector1, keySelector2);

这里传入两个参数 keySelector1 和 keySelector2,是两条流中各自的键选择器;ConnectedStreams 进行 keyBy 操作,其实就是把两条流中 key 相同的数据放到了一起,然后针对来源的流再做各自处理,这在一些场景下非常有用。另外,我们也可以在合并之前就将两条流分别进行 keyBy,得到的 KeyedStream 再进行连接(connect)操作,效果是一样的。要注意两条流定义的键的类型必须相同,否则会抛出异常。

connect用于连接广播流,广播流中的所有数据,在连接流的每个并行度上都能获取,从而实现一些规则数据的操作。

3. 基于时间的合流—双流联结(Join)

1)窗口联结

 stream1.join(stream2)
     .where(<KeySelector>)
     .equalTo(<KeySelector>)
     .window(<WindowAssigner>)
     .apply(<JoinFunction>)

两条流中的数据按照时间窗口,根据key进行join,因此需要进行开窗操作,三种时间窗口都可以用在这里:滚动 窗口(tumbling window)、滑动窗口(sliding window)和会话窗口(session window)。 注意:只能使用apply算子来处理联结后的数据。

两条流的数据到来之后,首先会按照 key 分组、进入对应的窗口中存储;当到达窗口结束时间时,算子会先统计出窗口内两条流的数据的所有组合,也就是对两条流中的数据做一个笛卡尔积(相当于表的交叉连接, cross join),然后进行遍历,把每一对匹配的数据,作为参数(first, second)传入 JoinFunction 的.join()方法进行计算处理,所以窗口中每有一对数据成功联结匹配, JoinFunction 的.join()方法就会被调用一次,并输出一个结果。

2)间隔联结

间隔联结具体的定义方式是,我们给定两个时间点,分别叫作间隔的“上界”(upperBound)和“下界”(lowerBound);于是对于一条流(不妨叫作 A)中的任意一个数据元素 a,就可以开辟一段时间间隔: [a.timestamp + lowerBound, a.timestamp + upperBound],即以 a 的时间戳为中心,下至下界点、上至上界点的一个闭区间:我们就把这段时间作为可以匹配另一条流数据的“窗口”范围。所以对于另一条流(不妨叫 B)中的数据元素 b,如果它的时间戳落在了这个区间范围内, a 和 b 就可以成功配对,进而进行计算输出结果。所以匹配的条件为:a.timestamp + lowerBound <= b.timestamp <= a.timestamp + upperBound。

这里需要注意,做间隔联结的两条流 A 和 B,也必须基于相同的 key;下界 lowerBound应该小于等于上界 upperBound,两者都可正可负;间隔联结目前只支持事件时间语义

  • 两个联结的流必须是keyedStream。
 stream1
     .keyBy(<KeySelector>)
     .intervalJoin(stream2.keyBy(<KeySelector>))
     .between(Time.milliseconds(-2), Time.milliseconds(1))
     .process (new ProcessJoinFunction<Integer, Integer, String(){
         @Override
         public void processElement(Integer left, Integer right, Context ctx,
         Collector<String> out) {
             out.collect(left + "," + right);
         }
     }
 );

3) 窗口同组联结(Window CoGroup)

除窗口联结和间隔联结之外, Flink 还提供了一个“窗口同组联结”(window coGroup)操作。它的用法跟 window join 非常类似,也是将两条流合并之后开窗处理匹配的元素,调用时只需要将.join()换为.coGroup()就可以了。

 stream1.coGroup(stream2)
     .where(<KeySelector>)
     .equalTo(<KeySelector>)
     .window(TumblingEventTimeWindows.of(Time.hours(1)))
     .apply(<CoGroupFunction>)  

在CoGroupFunction处理匹配元素的方法中

 public interface CoGroupFunction<IN1, IN2, O> extends Function, Serializable {
     
     void coGroup(Iterable<IN1> first, Iterable<IN2> second, Collector<O> out)  throws Exception;
     
 }
  • first:表示窗口中,Stream1的某个key对应的数据集合。
  • second:表示窗口中,Stream2中和first匹配的key对应的数据集合。

也就是说,现在不会再去计算窗口中两条流数据集的笛卡尔积,而是直接把收集到的所有数据一次性传入, 至于要怎样配对完全是自定义的。这样.coGroup()方法只会被调用一次,而且即使一条流的数据没有任何另一条流的数据匹配,也可以出现在集合中、当然也可以定义输出结果了。

所以能够看出, coGroup 操作比窗口的 join 更加通用,不仅可以实现类似 SQL 中的“内连接”(inner join),也可以实现左外连接(left outer join)、右外连接(right outer join)和全外连接(full outer join)。事实上,窗口 join 的底层,也是通过 coGroup 来实现的。