本文开头附:Flink 学习路线系列 ^ _ ^
Flink 中的两个流,也是可以实现 Join 操作的。如果两个流要实现 Join 操作,必须满足以下两点:
Flink 中支持双流 Join 的算子目前已知有5种,如下:
union
:union 支持双流 Join,也支持多流 Join。多个流类型必须一致;connector
:connector 支持双流 Join,两个流的类型可以不一致;join
:该方法只支持 inner join,即:相同窗口下,两个流中,Key都存在且相同时才会关联成功;coGroup
:同样能够实现双流 Join。即:将同一 Window 窗口内的两个DataStream 联合起来,两个流按照 Key 来进行关联,并通过 apply()方法 new CoGroupFunction() 的形式,重写 join() 方法进行逻辑处理。intervalJoin
:Interval Join 没有 Window 窗口的概念,直接用时间戳作为关联的条件,更具表达力。 join() 和 coGroup() 都是 Flink 中用于连接多个流的算子,但是两者也有一定的区别,推荐能使用 coGroup 不要使用Join,因为coGroup更强大(inner join 除外。就 inner join 的话推荐使用 join ,因为在 join 的策略上做了优化,更高效
)
之前有介绍,请参考:Flink常用算子Transformation介绍
在 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)
Flink 虽然为我们提供了 .join()方法
,但是它并没有为我们提供 .left join
、.right join
方法。
Flink为我们提供了 .coGroup()方法来实现 left join、right join功能。此处仅从 left join 功能介绍。
类型转换:
DataStream,DataStream → DataStream
语法:
//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)
实现 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♪(・ω・)ノ