left join where条件_Flink 双流 Join 的3种操作示例

简介:在数据库中的静态表上做 OLAP 分析时,两表 join 是非常常见的操作。同理,在流式处理作业中,有时也需要在两条流上做 join 以获得更丰富的信息。Flink DataStream API 为用户提供了3个算子来实现双流 join,分别是:1、join();2、coGroup();3、intervalJoin()

在数据库中的静态表上做 OLAP 分析时,两表 join 是非常常见的操作。同理,在流式处理作业中,有时也需要在两条流上做 join 以获得更丰富的信息。Flink DataStream API 为用户提供了3个算子来实现双流 join,分别是:

  • join()
  • coGroup()
  • intervalJoin()

本文举例说明它们的使用方法,顺便聊聊比较特殊的 interval join 的原理。

准备数据

从 Kafka 分别接入点击流和订单流,并转化为 POJO。

DataStream<String> clickSourceStream = env   .addSource(new FlinkKafkaConsumer011<>(     "ods_analytics_access_log",     new SimpleStringSchema(),     kafkaProps   ).setStartFromLatest()); DataStream<String> orderSourceStream = env   .addSource(new FlinkKafkaConsumer011<>(     "ods_ms_order_done",     new SimpleStringSchema(),     kafkaProps   ).setStartFromLatest());  DataStream<AnalyticsAccessLogRecord> clickRecordStream = clickSourceStream   .map(message -> JSON.parseObject(message, AnalyticsAccessLogRecord.class)); DataStream<OrderDoneLogRecord> orderRecordStream = orderSourceStream   .map(message -> JSON.parseObject(message, OrderDoneLogRecord.class));

join()

join() 算子提供的语义为"Window join",即按照指定字段和(滚动/滑动/会话)窗口进行 inner join,支持处理时间和事件时间两种时间特征。以下示例以10秒滚动窗口,将两个流通过商品 ID 关联,取得订单流中的售价相关字段。

4e0259804540433896e84ecf51da52d7.png
clickRecordStream   .join(orderRecordStream)   .where(record -> record.getMerchandiseId())   .equalTo(record -> record.getMerchandiseId())   .window(TumblingProcessingTimeWindows.of(Time.seconds(10)))   .apply(new JoinFunction<AnalyticsAccessLogRecord, OrderDoneLogRecord, String>() {     @Override     public String join(AnalyticsAccessLogRecord accessRecord, OrderDoneLogRecord orderRecord) throws Exception {       return StringUtils.join(Arrays.asList(         accessRecord.getMerchandiseId(),         orderRecord.getPrice(),         orderRecord.getCouponMoney(),         orderRecord.getRebateAmount()       ), 't');     }   })   .print().setParallelism(1);

简单易用。

coGroup()

只有 inner join 肯定还不够,如何实现 left/right outer join 呢?答案就是利用 coGroup() 算子。它的调用方式类似于 join() 算子,也需要开窗,但是 CoGroupFunction 比 JoinFunction 更加灵活,可以按照用户指定的逻辑匹配左流和/或右流的数据并输出。

以下的例子就实现了点击流 left join 订单流的功能,是很朴素的 nested loop join 思想(二重循环)。

clickRecordStream   .coGroup(orderRecordStream)   .where(record -> record.getMerchandiseId())   .equalTo(record -> record.getMerchandiseId())   .window(TumblingProcessingTimeWindows.of(Time.seconds(10)))   .apply(new CoGroupFunction<AnalyticsAccessLogRecord, OrderDoneLogRecord, Tuple2<String, Long>>() {     @Override     public void coGroup(Iterable<AnalyticsAccessLogRecord> accessRecords, Iterable<OrderDoneLogRecord> orderRecords, Collector<Tuple2<String, Long>> collector) throws Exception {       for (AnalyticsAccessLogRecord accessRecord : accessRecords) {         boolean isMatched = false;         for (OrderDoneLogRecord orderRecord : orderRecords) {           // 右流中有对应的记录           collector.collect(new Tuple2<>(accessRecord.getMerchandiseName(), orderRecord.getPrice()));           isMatched = true;         }         if (!isMatched) {           // 右流中没有对应的记录           collector.collect(new Tuple2<>(accessRecord.getMerchandiseName(), null));         }       }     }   })   .print().setParallelism(1);

intervalJoin()

join() 和 coGroup() 都是基于窗口做关联的。但是在某些情况下,两条流的数据步调未必一致。例如,订单流的数据有可能在点击流的购买动作发生之后很久才被写入,如果用窗口来圈定,很容易 join 不上。所以 Flink 又提供了"Interval join"的语义,按照指定字段以及右流相对左流偏移的时间区间进行关联,即:

right.timestamp ∈ [left.timestamp + lowerBound; left.timestamp + upperBound]

ead83edcaf862c80053b8b3ee11c3bdf.png

interval join 也是 inner join,虽然不需要开窗,但是需要用户指定偏移区间的上下界,并且只支持事件时间。

示例代码如下。注意在运行之前,需要分别在两个流上应用 assignTimestampsAndWatermarks() 方法获取事件时间戳和水印。

clickRecordStream   .keyBy(record -> record.getMerchandiseId())   .intervalJoin(orderRecordStream.keyBy(record -> record.getMerchandiseId()))   .between(Time.seconds(-30), Time.seconds(30))   .process(new ProcessJoinFunction<AnalyticsAccessLogRecord, OrderDoneLogRecord, String>() {     @Override     public void processElement(AnalyticsAccessLogRecord accessRecord, OrderDoneLogRecord orderRecord, Context context, Collector<String> collector) throws Exception {       collector.collect(StringUtils.join(Arrays.asList(         accessRecord.getMerchandiseId(),         orderRecord.getPrice(),         orderRecord.getCouponMoney(),         orderRecord.getRebateAmount()       ), 't'));     }   })   .print().setParallelism(1);

由上可见,interval join 与 window join 不同,是两个 KeyedStream 之上的操作,并且需要调用 between() 方法指定偏移区间的上下界。如果想令上下界是开区间,可以调用 upperBoundExclusive()/lowerBoundExclusive() 方法。

interval join 的实现原理

以下是 KeyedStream.process(ProcessJoinFunction) 方法调用的重载方法的逻辑。

public <OUT> SingleOutputStreamOperator<OUT> process(         ProcessJoinFunction<IN1, IN2, OUT> processJoinFunction,         TypeInformation<OUT> outputType) {     Preconditions.checkNotNull(processJoinFunction);     Preconditions.checkNotNull(outputType);     final ProcessJoinFunction<IN1, IN2, OUT> cleanedUdf = left.getExecutionEnvironment().clean(processJoinFunction);     final IntervalJoinOperator<KEY, IN1, IN2, OUT> operator =         new IntervalJoinOperator<>(             lowerBound,             upperBound,             lowerBoundInclusive,             upperBoundInclusive,             left.getType().createSerializer(left.getExecutionConfig()),             right.getType().createSerializer(right.getExecutionConfig()),             cleanedUdf         );     return left         .connect(right)         .keyBy(keySelector1, keySelector2)         .transform("Interval Join", outputType, operator); }

本文转载自简书,作者:LittleMagic

原文链接

本文为阿里云原创内容,未经允许不得转载

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值