28.DataStream API之Operators(Joining)

本文详细介绍了 Apache Flink 1.8 中的 Window Join 操作,包括 TumblingWindowJoin、SlidingWindowJoin、SessionWindowJoin 和 IntervalJoin 的工作原理和应用场景。通过实例展示了如何配置不同类型的窗口,以及如何使用 JoinFunction 处理连接后的数据。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

flink 1.8

Window Join

window join连接两个流的元素,它们共享一个公共key并位于同一个窗口中。可以使用窗口分配器 window assigner定义这些窗口,并对来自这两个流的元素求值。

然后,将来自两边的元素传递给用户定义的JoinFunction或FlatJoinFunction,用户可以在其中发出满足join条件的结果。

stream.join(otherStream)
    .where(<KeySelector>)
    .equalTo(<KeySelector>)
    .window(<WindowAssigner>)
    .apply(<JoinFunction>)

 

语义注意事项:

  1. 创建两个流元素的成对组合的行为类似于内连接,如果来自一个流的元素与另一个流没有相对应要连接的元素,则不会发出该元素。
  2. 结合在一起的那些元素将其时间戳设置为位于各自窗口中的最大时间戳。例如,以[5,10)为边界的窗口将产生连接的元素的时间戳为9。

在下一节中,我们将根据一些示例场景,来讲述不同类型的窗口连接的行为。

 

Tumbling Window Join

执行滚动窗口连接(Tumbling Window Join)时,具有公共Key和公共tumbling window的所有元素都以成对组合的形式进行连接,并传递给JoinFunction或FlatJoinFunction。因为这就像一个内连接,在滚动窗口中没有来自另一个流的元素的流的元素不会被输出!

如图所示,我们定义了一个大小为2毫秒的滚动窗口,其结果为[0,1],[2,3], ...。该图像显示了每个窗口中所有元素的成对组合,这些元素将传递给JoinFunction。注意,在翻滚窗口[6,7]中没有发出任何内容,因为在绿色流中没有元素与橙色元素⑥、⑦连接。

import org.apache.flink.api.java.functions.KeySelector;
import org.apache.flink.streaming.api.windowing.assigners.TumblingEventTimeWindows;
import org.apache.flink.streaming.api.windowing.time.Time;
 
...

DataStream<Integer> orangeStream = ...
DataStream<Integer> greenStream = ...

orangeStream.join(greenStream)
    .where(<KeySelector>)
    .equalTo(<KeySelector>)
    .window(TumblingEventTimeWindows.of(Time.milliseconds(2)))
    .apply (new JoinFunction<Integer, Integer, String> (){
        @Override
        public String join(Integer first, Integer second) {
            return first + "," + second;
        }
    });

Sliding Window Join

在执行滑动窗口连接(Sliding Window Join)时,具有公共Key和公共滑动窗口(Sliding Window )的所有元素都作为成对组合进行连接,并传递给JoinFunction或FlatJoinFunction。当前滑动窗口中没有来自另一个流的元素的流的元素不会被发出!请注意,有些元素可能会在一个滑动窗口中连接,但不会在另一个窗口中连接!

在本例中,我们使用的滑动窗口大小为2毫秒,滑动1毫秒,滑动窗口结果[1,0],[0,1],[1,2],[2、3],.... x轴以下是每个滑动窗口的Join结果将被传递给JoinFunction的元素。在这里你还可以看到橙与绿色窗口Join[2,3],但不与任何窗口Join[1,2]

import org.apache.flink.api.java.functions.KeySelector;
import org.apache.flink.streaming.api.windowing.assigners.SlidingEventTimeWindows;
import org.apache.flink.streaming.api.windowing.time.Time;

...

DataStream<Integer> orangeStream = ...
DataStream<Integer> greenStream = ...

orangeStream.join(greenStream)
    .where(<KeySelector>)
    .equalTo(<KeySelector>)
    .window(SlidingEventTimeWindows.of(Time.milliseconds(2) /* size */, Time.milliseconds(1) /* slide */))
    .apply (new JoinFunction<Integer, Integer, String> (){
        @Override
        public String join(Integer first, Integer second) {
            return first + "," + second;
        }
    });

 Session Window Join

在执行会话窗口连接时,具有相同键的所有元素(当“组合combined”时满足会话条件)都以成对的组合进行连接,并传递给JoinFunction或FlatJoinFunction。再次执行内部连接,因此如果会话窗口只包含来自一个流的元素,则不会发出任何输出!

在这里,定义一个会话窗口连接,其中每个会话被至少1ms的间隔所分割。有三个会话,在前两个会话中,来自两个流的连接元素被传递给JoinFunction。在第三次会话中绿色流没有元素,所以⑧⑨不会Join。

import org.apache.flink.api.java.functions.KeySelector;
import org.apache.flink.streaming.api.windowing.assigners.EventTimeSessionWindows;
import org.apache.flink.streaming.api.windowing.time.Time;
 
...

DataStream<Integer> orangeStream = ...
DataStream<Integer> greenStream = ...

orangeStream.join(greenStream)
    .where(<KeySelector>)
    .equalTo(<KeySelector>)
    .window(EventTimeSessionWindows.withGap(Time.milliseconds(1)))
    .apply (new JoinFunction<Integer, Integer, String> (){
        @Override
        public String join(Integer first, Integer second) {
            return first + "," + second;
        }
    });

Interval Join

interval join用一个公共Key连接两个流的元素(将它们称为A & B),其中流B的元素的时间戳具有相对于流A中的元素的时间戳。 这也可以更正式地表示为b.timestamp ∈ [a.timestamp + lowerBound; a.timestamp + upperBound] or a.timestamp + lowerBound <= b.timestamp <= a.timestamp + upperBound

其中a和b是A和B中共享一个公钥的元素。下界和上界都可以是负的或正的,只要下界小于或等于上界。interval连接目前只执行内部连接。

当将一对元素传递给ProcessJoinFunction时,它们将给两个元素分配更大的时间戳(可以通过ProcessJoinFunction.Context访问)。

注意:间隔连接目前只支持事件时间。

 

在上面的示例中,我们将“橙色”和“绿色”两个流连接起来,它们的下界为-2毫秒,上界为+1毫秒。默认情况下,这些是包含边界的,但是可以通过.lowerboundexclusive()和. upperboundexclusive()进行设置,如果设置了,则不包含边界。

包含边界:再用更正式的符号来表示angeElem.ts + lowerBound <= greenElem.ts <= orangeElem.ts + upperBound 如三角形所示。

不包含边界:通过.lowerboundexclusive()和. upperboundexclusive()进行设置,angeElem.ts + lowerBound < greenElem.ts <=orangeElem.ts + upperBound

import org.apache.flink.api.java.functions.KeySelector;
import org.apache.flink.streaming.api.functions.co.ProcessJoinFunction;
import org.apache.flink.streaming.api.windowing.time.Time;

...

DataStream<Integer> orangeStream = ...
DataStream<Integer> greenStream = ...

orangeStream
    .keyBy(<KeySelector>)
    .intervalJoin(greenStream.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(first + "," + second);
        }
    });

https://www.jianshu.com/p/ba19e4d1d802

https://ci.apache.org/projects/flink/flink-docs-release-1.9/dev/stream/operators/joining.html

<think>好的,我现在需要解决用户提到的Flink程序中出现的java.lang.reflect.InaccessibleObjectException异常,尤其是与ClosureCleaner的clean方法和JDK模块化相关的问题。首先,我得先理解这个异常的原因。 根据用户提供的引用,特别是引用[2]和引用[4],这个异常通常是因为Java反射试图访问不可访问的对象,而JDK的高版本(尤其是9及以上)加强了模块化系统的访问控制。在Flink中,ClosureCleaner使用反射来清理闭包中的非序列化字段,防止序列化问题。但模块化之后,默认不允许反射访问非导出包中的类,导致InaccessibleObjectException。 接下来,我需要考虑解决方案。引用[2]提到可以通过setAccessible(true)来绕过访问限制,但用户可能已经在代码中这样做了,或者问题出在Flink内部使用的反射。这时候可能需要更全局的解决方法,比如JVM参数调整,如引用[4]中提到的降级JDK版本,但用户可能不想降级,所以需要其他方法。 查看JDK模块系统的文档,记得可以通过添加JVM参数--add-opens来开放特定模块的包,允许反射访问。例如,如果异常涉及到java.base/java.lang模块,就需要开放相应的包。用户遇到的ClosureCleaner问题可能涉及到Flink内部使用的某些类所在的模块,需要确定具体是哪个模块导致的错误。 另外,检查Flink的版本是否兼容当前JDK版本。如果用户使用的是较旧的Flink版本,可能不支持高版本JDK的模块化系统,升级Flink到兼容的版本可能解决问题。同时,确保依赖项如Hadoop、Kafka等与JDK版本兼容。 用户可能需要修改模块信息(module-info.java),添加必要的opens语句,允许Flink的类反射访问特定的包。不过,如果项目没有使用模块化,可能不需要这个步骤。此外,在运行Flink程序时,添加JVM参数如--add-opens java.base/java.lang=ALL-UNNAMED,可以解决访问问题。 还需要考虑代码中的自定义函数或闭包是否包含私有字段或嵌套类,这些可能需要显式清理或使用Flink的注解如@Public标注,确保ClosureCleaner正确处理。 总结可能的解决步骤:检查JDK版本,添加JVM参数,升级Flink版本,调整模块配置,检查代码中的序列化问题。同时,需要验证这些步骤的有效性,确保解决方案可靠。</think>### 解决Flink中`java.lang.reflect.InaccessibleObjectException`异常的方法 #### 问题背景 在Flink程序中,`ClosureCleaner`负责清理闭包中可能存在的非序列化字段,确保任务序列化时不会出错。但在JDK 9及以上版本中,模块化系统(JPMS)限制了反射对未开放模块的访问,导致`InaccessibleObjectException`。具体报错可能类似: ``` Caused by: java.lang.reflect.InaccessibleObjectException: Unable to make field private final ... accessible: module XYZ does not "opens" to module flink.runtime ``` #### 解决步骤 1. **添加JVM启动参数** 通过`--add-opens`参数开放相关模块的包,允许反射访问。 - **示例**(根据异常提示的模块调整): ```bash --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED ``` - **Flink作业提交时指定参数**(例如YARN模式): ```bash flink run -Djvm.args="--add-opens java.base/java.lang=ALL-UNNAMED" ... ``` 2. **升级Flink版本** Flink从1.14版本开始增强了对JDK 11+的兼容性。若使用旧版本(如1.13以下),建议升级到最新稳定版。 3. **检查模块化配置** - 若项目使用模块化(含`module-info.java`),需显式开放包: ```java module your.module { opens com.yourpackage.to.flink; requires org.apache.flink.core; } ``` 4. **代码层调整** - 避免在闭包中使用私有字段或嵌套类,或通过`@Public`注解标记需保留的字段[^2]。 - 显式清理闭包(示例): ```java ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment(); env.getConfig().enableClosureCleaner(); ``` 5. **降级JDK版本** 若临时解决方案不可行,可降级至JDK 8(需与Flink兼容性匹配)[^4]。 #### 验证方案 - **本地测试**:在IDE中添加JVM参数后运行作业,观察异常是否消失。 - **日志分析**:检查异常堆栈中涉及的模块和包,精准添加`--add-opens`参数。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值