Flink Window中的 inner join、left join、right join

本文开头附:Flink 学习路线系列 ^ _ ^

Flink 中的两个流,也是可以实现 Join 操作的。如果两个流要实现 Join 操作,必须满足以下两点:

  1. 流需要能够等待,即:两个流必须在同一个窗口中;
  2. 双流等值 Join,即:两个流中,必须有一个字段相等才能够 Join 上。

1.Flink 中支持 双流join 的算子

  Flink 中支持双流 Join 的算子目前已知有5种,如下:

  1. union:union 支持双流 Join,也支持多流 Join。多个流类型必须一致;
  2. connector:connector 支持双流 Join,两个流的类型可以不一致;
  3. join:该方法只支持 inner join,即:相同窗口下,两个流中,Key都存在且相同时才会关联成功;
  4. coGroup:同样能够实现双流 Join。即:将同一 Window 窗口内的两个DataStream 联合起来,两个流按照 Key 来进行关联,并通过 apply()方法 new CoGroupFunction() 的形式,重写 join() 方法进行逻辑处理。
  5. intervalJoin:Interval Join 没有 Window 窗口的概念,直接用时间戳作为关联的条件,更具表达力。

       join() 和 coGroup() 都是 Flink 中用于连接多个流的算子,但是两者也有一定的区别,推荐能使用 coGroup 不要使用Join,因为coGroup更强大(inner join 除外。就 inner join 的话推荐使用 join ,因为在 join 的策略上做了优化,更高效

2.算子介绍

2.1 union()/connector()

       之前有介绍,请参考:Flink常用算子Transformation介绍

2.2 join()

       在 Flink 中,使用 .join() 方法来实现 InnerJoin 的功能。InnerJoin:即两个数据流中(同窗口下)都存在时才能够 Join 成功。同 SQL 中的 inner join

类型转换:

DataStream,DataStream → DataStream

语法:

//StreamA:左流
streamA
	.join(streamB)//StreamB:右流
    .where(<key selector>)//where条件:左流的Key
    .equalTo(<key selector>)//equalTo条件:右流的Key
    .window(TumblingEventTimeWindows.of(Time.seconds(3)))//开启Window窗口
    .apply (new JoinFunction () {...});//处理逻辑:JoinFunction 或 FlatJoinFunction

场景案例:

       使用 EventTime 作为时间标准,分别从 Kafka 两个topic 中读取数据,将读取到的数据,根据第一个字段进行 inner join 操作。

topicA 数据格式:a,1001,1000000050000
topicB数据格式:a,BeiJing,1000000056000

       inner join 后返回如下字段:1,1001,BeiJing,1000000050000,1000000056000

代码:

/**
 * TODO InnerJoin 实例
 *
 * @author liuzebiao
 * @Date 2020-2-21 16:11
 */
public class InnerJoinDemo {

    public static void main(String[] args) throws Exception {

        StreamExecutionEnvironment env = FlinkUtils.getEnv();
        //设置使用 EventTime 作为时间标准
        env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime);
        //设置并行度为1,此处仅用作测试使用。因为 Kafka 为并行数据流,数据只有全部填满分区才会触发窗口操作。
        env.setParallelism(1);

        ParameterTool parameters = ParameterTool.fromPropertiesFile("配置文件路径");

        DataStream<String> leftSource = FlinkUtils.createKafkaStream(parameters,"topicA","groupA", SimpleStringSchema.class);
        DataStream<String> rightSource = FlinkUtils.createKafkaStream(parameters,"topicB","groupB", SimpleStringSchema.class);

        //左流,设置水位线 WaterMark
        SingleOutputStreamOperator<String> leftStream = leftSource.assignTimestampsAndWatermarks(new BoundedOutOfOrdernessTimestampExtractor<String>(Time.milliseconds(0)) {
            @Override
            public long extractTimestamp(String s) {
                String[] split = s.split(",");
                return Long.parseLong(split[2]);
            }
        });

        SingleOutputStreamOperator<String> rightStream = rightSource.assignTimestampsAndWatermarks(new BoundedOutOfOrdernessTimestampExtractor<String>(Time.milliseconds(0)) {
            @Override
            public long extractTimestamp(String s) {
                String[] split = s.split(",");
                return Long.parseLong(split[2]);
            }
        });

        //实现 Join 操作(where、equalTo、apply 中实现部分,都可以写成单独一个类)
        DataStream<Tuple5<String, String, String, String, String>> joinDataStream = leftStream
                .join(rightStream)
                .where(new KeySelector<String, String>() {
                    @Override
                    public String getKey(String value) throws Exception {
                        String[] split = value.split(",");
                        return split[0];
                    }
                })
                .equalTo(new KeySelector<String, String>() {
                    @Override
                    public String getKey(String value) throws Exception {
                        String[] split = value.split(",");
                        return split[0];
                    }
                })
                .window(TumblingEventTimeWindows.of(Time.seconds(10)))
                .apply(new JoinFunction<String, String, Tuple5<String, String, String, String, String>>() {
                    @Override
                    public Tuple5<String, String, String, String, String> join(String leftStr, String rightStr) throws Exception {
                        String[] left = leftStr.split(",");
                        String[] right = rightStr.split(",");
                        return new Tuple5<>(left[0], left[1], right[1], left[2],right[2]);
                    }
                });

        joinDataStream.print();

        env.execute("InnerJoinDemo");
    }
}

测试数据:

A流:
a,1,1000000050000
a,2,1000000054000
a,3,1000000079900
a,4,1000000115000
b,5,1000000100000
b,6,1000000108000

B流:
a,hangzhou,1000000059000
b,beijing,1000000105000

测试结果:

(a,1,hangzhou,1000000050000,1000000059000)
(a,2,hangzhou,1000000054000,1000000059000)

2.3.CoGroup()

       Flink 虽然为我们提供了 .join()方法,但是它并没有为我们提供 .left join.right join 方法。

       Flink为我们提供了 .coGroup()方法来实现 left join、right join功能。此处仅从 left join 功能介绍。

类型转换:

DataStream,DataStream → DataStream

2.3.1 实现 left join 功能

语法:

//StreamA:左流
streamA
	.coGroup(streamB)//StreamB:右流
    .where(<key selector>)//where条件:左流的Key
    .equalTo(<key selector>)//equalTo条件:右流的Key
    .window(TumblingEventTimeWindows.of(Time.seconds(3)))//开启Window窗口
    .apply (new CoGroupFunction () {...});//处理逻辑:JoinFunction 或 FlatJoinFunction

场景案例:

       使用 EventTime 作为时间标准,分别从 Kafka 两个topic 中读取数据,将读取到的数据,根据第一个字段进行 inner join 操作。

topicA 数据格式:a,1001,1000000050000
topicB数据格式:a,BeiJing,1000000056000

       inner join 后返回如下字段:1,1001,BeiJing,1000000050000,1000000056000

代码:

/**
 * TODO coGroup()方法,实现 Left Join 功能
 *
 * @author liuzebiao
 * @Date 2020-2-21 16:11
 */
public class LeftJoinDemo {

    public static void main(String[] args) throws Exception {

        StreamExecutionEnvironment env = FlinkUtils.getEnv();
        //设置使用 EventTime 作为时间标准
        env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime);
        //设置并行度为1,此处仅用作测试使用。因为 Kafka 为并行数据流,数据只有全部填满分区才会触发窗口操作。
        env.setParallelism(1);

        ParameterTool parameters = ParameterTool.fromPropertiesFile("配置文件路径");

        DataStream<String> leftSource = FlinkUtils.createKafkaStream(parameters,"topicA","groupA", SimpleStringSchema.class);
        DataStream<String> rightSource = FlinkUtils.createKafkaStream(parameters,"topicB","groupB", SimpleStringSchema.class);

        //左流,设置水位线 WaterMark
        SingleOutputStreamOperator<String> leftStream = leftSource.assignTimestampsAndWatermarks(new BoundedOutOfOrdernessTimestampExtractor<String>(Time.milliseconds(0)) {
            @Override
            public long extractTimestamp(String s) {
                String[] split = s.split(",");
                return Long.parseLong(split[2]);
            }
        });

        SingleOutputStreamOperator<String> rightStream = rightSource.assignTimestampsAndWatermarks(new BoundedOutOfOrdernessTimestampExtractor<String>(Time.milliseconds(0)) {
            @Override
            public long extractTimestamp(String s) {
                String[] split = s.split(",");
                return Long.parseLong(split[2]);
            }
        });

        // coGroup() 方法实现 left join 操作
        DataStream<Tuple5<String, String, String, String, String>> joinDataStream = leftStream
                .coGroup(rightStream)
                .where(new KeySelector<String, String>() {
                    @Override
                    public String getKey(String value) throws Exception {
                        String[] split = value.split(",");
                        return split[0];
                    }
                })
                .equalTo(new KeySelector<String, String>() {
                    @Override
                    public String getKey(String value) throws Exception {
                        String[] split = value.split(",");
                        return split[0];
                    }
                })
                .window(TumblingEventTimeWindows.of(Time.seconds(10)))
                .apply(new CoGroupFunction<String, String, Tuple5<String, String, String, String, String>>() {
                    //重写 coGroup() 方法,来实现 left join 功能。
                    @Override
                    public void coGroup(Iterable<String> leftElement, Iterable<String> rightElement, Collector<Tuple5<String, String, String, String, String>> out) throws Exception {
                        boolean hasElement = false;
                        //leftElement为左流中的数据
                        for (String leftStr : leftElement) {
                            String[] left = leftStr.split(",");
                            //如果 左边的流 join 上右边的流,rightStream 就不能为空
                            for (String rightStr : rightElement) {
                                String[] right = rightStr.split(",");
                                //将 join 的数据输出
                                out.collect(Tuple5.of(left[0], left[1], right[1], left[2], right[2]));
                                hasElement = true;
                            }
                            if (!hasElement) {
                                out.collect(Tuple5.of(left[0], left[1], "null", left[2], "null"));
                            }
                        }
                    }
                });

        joinDataStream.print();

        env.execute("LeftJoinDemo");
    }
}

测试数据:

   同 .join() 方法测试数据

测试结果:

因为 b 数据超时的原因,导致舍弃,所以 此处没有 b 的数据。

(a,1,hangzhou,1000000050000,1000000059000)
(a,2,hangzhou,1000000054000,1000000059000)
(a,3,null,1000000079900,null)
(a,4,null,1000000115000,null)

如果你设置一个合理的超时时间,此处便会有 b 的数据

(a,1,hangzhou,1000000050000,1000000059000)
(a,2,hangzhou,1000000054000,1000000059000)
(a,3,null,1000000079900,null)
(a,4,null,1000000115000,null)
(b,5,beijing,1000000100000,1000000105000)
(b,6,beijing,1000000108000,1000000105000)

2.3.2 实现 right join 功能

       实现 right join 功能,只需要对 2.3.1 中 left join 功能做一部分修改即可。代码如下所示:

代码:(仅展示修改部分,其他部分同 2.3.1 代码)

// coGroup() 方法实现 right join 操作
DataStream<Tuple5<String, String, String, String, String>> joinDataStream = rightStream
        .coGroup(leftStream)
        .where(new KeySelector<String, String>() {
            @Override
            public String getKey(String value) throws Exception {
                String[] split = value.split(",");
                return split[0];
            }
        })
        .equalTo(new KeySelector<String, String>() {
            @Override
            public String getKey(String value) throws Exception {
                String[] split = value.split(",");
                return split[0];
            }
        })
        .window(TumblingEventTimeWindows.of(Time.seconds(10)))
        .apply(new CoGroupFunction<String, String, Tuple5<String, String, String, String, String>>() {
            //重写 coGroup() 方法,来实现 left join 功能。
            @Override
            public void coGroup(Iterable<String> rightElement, Iterable<String> leftElement, Collector<Tuple5<String, String, String, String, String>> out) throws Exception {
                boolean hasElement = false;
                //leftElement为左流中的数据
                for (String rightStr : rightElement) {
                    String[] right = rightStr.split(",");
                    //如果 左边的流 join 上右边的流,rightStream 就不能为空
                    for (String leftStr : leftElement) {
                        String[] left = leftStr.split(",");
                        //将 join 的数据输出
                        out.collect(Tuple5.of(right[0], right[1], left[1], right[2], left[2]));
                        hasElement = true;
                    }
                    if (!hasElement) {
                        out.collect(Tuple5.of(right[0], right[1], "null", right[2], "null"));
                    }
                }
            }
        });

测试数据:

   同 .join() 方法测试数据

测试结果:

(a,hangzhou,1,1000000059000,1000000050000)
(a,hangzhou,2,1000000059000,1000000054000)
(b,beijing,5,1000000105000,1000000100000)
(b,beijing,6,1000000105000,1000000108000)

博主写作不易,来个关注呗

求关注、求点赞,加个关注不迷路 ヾ(◍°∇°◍)ノ゙

博主不能保证写的所有知识点都正确,但是能保证纯手敲,错误也请指出,望轻喷 Thanks♪(・ω・)ノ

你可能感兴趣的:(Flink)