本文来自官网翻译: Joining
- Window Join(窗口join)
- Tumbling Window Join(翻滚窗口join)
- Sliding Window Join(滑动窗口join)
- Session Window Join(会话窗口join)
- Interval Join(时间间隔join)
Window Join
窗口join 连接两个共享公共key 并位于同一窗口中的流的元素。可以使用窗口分配器定义这些窗口,两个流的元素都会触发。
然后将来自双方的元素传递给用户定义的 JoinFunction
或者FlatJoinFunction,
用户可以发出满足连接条件的结果。
一般用法可归纳如下:
stream.join(otherStream) .where(<KeySelector>) .equalTo(<KeySelector>) .window(<WindowAssigner>) .apply(<JoinFunction>)
关于语义的一些注释:
- 两个流的元素的成对组合的表现像内连接,意味着如果它们没有来自要连接的另一个流的对应元素,则不会发出来当前流的元素。
- 那些加入的元素将在其时间戳包含于相应窗口中的最大时间戳中。例如,
[5, 10)
具有其边界的窗口将导致连接的元素具有9作为其时间戳。
在下一节中,我们将使用一些示例性场景概述不同类型的窗口连接的行为。
Tumbling Window Join
当执行翻滚窗口join时,具有相同key和相同翻滚窗口的所有元素作为成对组合被连接并传递给JoinFunction
或FlatJoinFunction
。因为它的行为类似于内连接,所以一个流的元素在其翻滚窗口中没有来自另一个流的元素是不会发出的。
如图所示,我们定义了一个大小为2毫秒的翻滚窗口,这导致了窗体的窗口[0,1], [2,3], ...
图像显示了每个窗口中所有元素的成对组合,这些元素将被传递给JoinFunction
。请注意,在翻滚窗口中[6,7]
没有任何物体被发出,因为绿色流中不存在与橙色元素⑥和⑦连接的元素。
import org.apache.flink.streaming.api.windowing.assigners.SlidingEventTimeWindows; import org.apache.flink.streaming.api.windowing.time.Time; ... val orangeStream: DataStream[Integer] = ... val greenStream: DataStream[Integer] = ... orangeStream.join(greenStream) .where(elem => /* select key */) .equalTo(elem => /* select key */) .window(TumblingEventTimeWindows.of(Time.milliseconds(2))) .apply { (e1, e2) => e1 + "," + e2 }
Sliding Window Join
执行滑动窗口连接时,具有相同key和相同滑动窗口的所有元素将作为成对组合连接并传递给JoinFunction
或FlatJoinFunction
。当前滑动窗口中没有来自对应流的元素时,流元素不会发出!请注意,某些元素可能在一个滑动窗口中连接而在另一个滑块窗口中不连接。
在这个例子中,我们使用大小为2毫秒的滑动窗口并将它们滑动一毫秒,从而产生滑动窗口[-1, 0],[0,1],[1,2],[2,3], …
。x轴下方的连接元素是传递给JoinFunction
每个滑动窗口的元素。在这里,您还可以看到橙色②与窗口中的绿色③ 在窗口[2,3]
连接,但未与任何内容在窗口[1,2] 连接
。
import org.apache.flink.streaming.api.windowing.assigners.SlidingEventTimeWindows; import org.apache.flink.streaming.api.windowing.time.Time; ... val orangeStream: DataStream[Integer] = ... val greenStream: DataStream[Integer] = ... orangeStream.join(greenStream) .where(elem => /* select key */) .equalTo(elem => /* select key */) .window(SlidingEventTimeWindows.of(Time.milliseconds(2) /* size */, Time.milliseconds(1) /* slide */)) .apply { (e1, e2) => e1 + "," + e2 }
Session Window Join
在执行会话窗口连接时,具有相同键的所有元素“组合”满足会话条件时以成对组合方式连接并传递给JoinFunction
或FlatJoinFunction
。同样,这会执行内连接,因此如果存在仅包含来自一个流的元素的会话窗口,则不会发出任何输出!
这里我们定义一个会话窗口连接,其中每个会话除以至少1ms的间隙。有三个会话,在前两个会话中,两个流的连接元素都传递给JoinFunction
。在第三阶段,绿色流中没有元素,所以⑧和⑨没有连接!
import org.apache.flink.streaming.api.windowing.assigners.EventTimeSessionWindows; import org.apache.flink.streaming.api.windowing.time.Time; ... val orangeStream: DataStream[Integer] = ... val greenStream: DataStream[Integer] = ... orangeStream.join(greenStream) .where(elem => /* select key */) .equalTo(elem => /* select key */) .window(EventTimeSessionWindows.withGap(Time.milliseconds(1))) .apply { (e1, e2) => e1 + "," + e2 }
Interval Join
(时间)间隔连接使用公共密钥连接两个流的元素(我们现在称它们为A和B),并且流B的元素具有时间戳,该时间戳位于流A中元素的时间戳的相对时间间隔中。
这也可以更正式地表达为
b.timestamp ∈ [a.timestamp + lowerBound; a.timestamp + upperBound]
或
a.timestamp + lowerBound <= b.timestamp <= a.timestamp + upperBound
其中a和b是共享相同key 的A和B的元素。只要下限总是小于或等于上限,下限和上限都可以是负数或正数。间隔连接当前仅支持内连接。
当一对元素传递给ProcessJoinFunction
它们时,它们将被赋予ProcessJoinFunction.Context
两个元素较大的时间戳(可以通过它访问)。
注意:时间间隔连接当前仅支持事件时间。
在上面的例子中,我们连接两个流'orange'和'green',下限为-2毫秒,上限为+1毫秒。缺省情况下,这些界限是包容性的(包含上下界),方法lowerBoundExclusive()和
upperBoundExclusive
可以改变应用行为。
再次使用更正式的符号,这将转化为
orangeElem.ts + lowerBound <= greenElem.ts <= orangeElem.ts + upperBound
如上图三角形所示。
import org.apache.flink.streaming.api.functions.co.ProcessJoinFunction; import org.apache.flink.streaming.api.windowing.time.Time; ... val orangeStream: DataStream[Integer] = ... val greenStream: DataStream[Integer] = ... orangeStream .keyBy(elem => /* select key */) .intervalJoin(greenStream.keyBy(elem => /* select key */)) .between(Time.milliseconds(-2), Time.milliseconds(1)) .process(new ProcessJoinFunction[Integer, Integer, String] { override def processElement(left: Integer, right: Integer, ctx: ProcessJoinFunction[Integer, Integer, String]#Context, out: Collector[String]): Unit = { out.collect(left + "," + right); } }); });