flink 多流join 触发时机详解

flink 多流join 触发时机详解

    flink多流join代码很简单,但是对于初学者可能会遇到window窗口计算不能触发的"假象",这往往是由于对flink window eventtime processtime理解不到位引起的,以下示例将详述join在不同时间下的触发过程.

join+window+processtime

代码


import java.text.SimpleDateFormat

import cn.chinaunicom.ops.common.Utils
import org.apache.flink.api.common.functions.MapFunction
import org.apache.flink.streaming.api.TimeCharacteristic
import org.apache.flink.streaming.api.functions.timestamps.BoundedOutOfOrdernessTimestampExtractor
import org.apache.flink.streaming.api.scala._
import org.apache.flink.streaming.api.windowing.assigners.{TumblingEventTimeWindows, TumblingProcessingTimeWindows}
import org.apache.flink.streaming.api.windowing.time.Time
import org.apache.flink.util.Collector


/**
  * @Auther: huowang
  * @Date: 14:15 2019/08/02
  * @DES: 演示 flink join window在不同时间条件下的触发
  *      flink 版本1.7 scala 2.11
  *      环境介绍 通过flink监听虚拟机192.168.217.128两个网络端口 9000和9001
  *      之后通过 nc -lk 9000 命令向端口发送网络数据 发送格式为 字符串,时间戳
  *      例如 a1,1564713277270
  *          b1,1564713277270
  *       之后格式化字符串为二元组
  * @Modified By:
  */
object JoinTest {


  def main(args: Array[String]): Unit = {
//    if(args.length != 3){
//      System.err.println("缺少必要参数")
//      return
//    }
//    val hostname = args(0)
//    val port1 = args(1).toInt
//    val port2 = args(2).toInt
    val hostname ="192.168.217.128"
    val port1 = 9000
    val port2 = 9001

    //指定运行环境
    val env = StreamExecutionEnvironment.getExecutionEnvironment
    env.setStreamTimeCharacteristic(TimeCharacteristic.ProcessingTime)
    // 接入两个网络输入流
    val a = env.socketTextStream(hostname, port1)
    val b = env.socketTextStream(hostname, port2)

    //将字符串转换成二元组
    val as = a.map(x=>{
      println("a=>"+x+",时间=>"+Utils.getStringDate3(x.split(",")(1).toLong))
      ("key",x.split(",")(0),x.split(",")(1).toLong)
    })
//      .assignTimestampsAndWatermarks(new BoundedOutOfOrdernessTimestampExtractor[(String, String, Long)](Time.seconds(1)) {
//      override def extractTimestamp(t: (String, String, Long)): Long = t._3
//    })

    val bs = b.map(x=>{
      println("b=>"+x+",时间=>"+Utils.getStringDate3(x.split(",")(1).toLong))
      ("key",x.split(",")(0),x.split(",")(1).toLong)
    })
//      .assignTimestampsAndWatermarks(new BoundedOutOfOrdernessTimestampExtractor[(String, String, Long)](Time.seconds(1)) {
//      override def extractTimestamp(t: (String, String, Long)): Long = t._3
//    })

    val joinedStreams = as
      .join(bs)
      .where(_._1)
      .equalTo(_._1)
      .window(TumblingProcessingTimeWindows.of(Time.seconds(10)))
      .apply{
        (t1 : (String,String, Long), t2 : (String,String, Long), out : Collector[(String,String, Long,Long)]) =>
          println("t1=>"+t1+",时间=>"+Utils.getStringDate3(t1._3))
          println("t2=>"+t2+",时间=>"+Utils.getStringDate3(t2._3))
            // 直接拼接到一起
            out.collect((t1._2,t2._2,t1._3,t2._3))
      }.name("joinedStreams")

    // 结果输出
    joinedStreams.print()
    // 执行程序
    env.execute("joinedStreams")

  }

}

前提我们的三元组 ("key",string,st),的第一个元素是固定的,并且我们的join条件就是第一个元素

不触发的情况,key不相等,和明显这种情况在上述代码中不存在,因为key都是固定的

不触发的情况,a流和b流的速率不一样,没有数据可以落到同一个时间窗口上,我们现在的时间窗口是30秒,如果我们调成3秒然后再Linux上向流中输入数据,手速慢一点,两个窗口的数据数据时间间隔 大于3秒,这样就不会触发计算 因为同一窗口内没有满足条件数据

触发的情况,key相等并且在同一时间窗口上有相关数据

join+window+eventtime

如果使用eventtime需要注意的事情比较多,否则会出现十分诡异的不触发计算的情况,直接看如下示例代码

package cn.chinaunicom.ops.test.flinkJoin

import java.text.SimpleDateFormat

import cn.chinaunicom.ops.common.Utils
import org.apache.flink.api.common.functions.MapFunction
import org.apache.flink.streaming.api.TimeCharacteristic
import org.apache.flink.streaming.api.functions.timestamps.BoundedOutOfOrdernessTimestampExtractor
import org.apache.flink.streaming.api.scala._
import org.apache.flink.streaming.api.windowing.assigners.{TumblingEventTimeWindows, TumblingProcessingTimeWindows}
import org.apache.flink.streaming.api.windowing.time.Time
import org.apache.flink.util.Collector


/**
  * @Auther: huowang
  * @Date: 14:15 2019/08/02
  * @DES: 演示 flink join window在不同时间条件下的触发
  *      flink 版本1.7 scala 2.11
  *      环境介绍 通过flink监听虚拟机192.168.217.128两个网络端口 9000和9001
  *      之后通过 nc -lk 9000 命令向端口发送网络数据 发送格式为 字符串,时间戳
  *      例如 a1,1564713277270
  *          b1,1564713277270
  *       之后格式化字符串为二元组
  * @Modified By:
  */
object JoinTest {


  def main(args: Array[String]): Unit = {
//    if(args.length != 3){
//      System.err.println("缺少必要参数")
//      return
//    }
//    val hostname = args(0)
//    val port1 = args(1).toInt
//    val port2 = args(2).toInt
    val hostname ="192.168.217.128"
    val port1 = 9000
    val port2 = 9001

    //指定运行环境
    val env = StreamExecutionEnvironment.getExecutionEnvironment
    env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)
    env.setParallelism(1);
    // 这里 一定要设置 并行度为1
    // 接入两个网络输入流
    val a = env.socketTextStream(hostname, port1)
    val b = env.socketTextStream(hostname, port2)

    //将字符串转换成二元组
    val as = a.map(x=>{
      println("a=>"+x+",时间=>"+Utils.getStringDate3(x.split(",")(1).toLong))
      ("key",x.split(",")(0),x.split(",")(1).toLong)
    }).assignTimestampsAndWatermarks(new BoundedOutOfOrdernessTimestampExtractor[(String, String, Long)](Time.seconds(1)) {
      override def extractTimestamp(t: (String, String, Long)): Long = t._3
    })

    val bs = b.map(x=>{
      println("b=>"+x+",时间=>"+Utils.getStringDate3(x.split(",")(1).toLong))
      ("key",x.split(",")(0),x.split(",")(1).toLong)
    }).assignTimestampsAndWatermarks(new BoundedOutOfOrdernessTimestampExtractor[(String, String, Long)](Time.seconds(1)) {
      override def extractTimestamp(t: (String, String, Long)): Long = t._3
    })

       as
      .join(bs)
      .where(_._1)
      .equalTo(_._1)
      .window(TumblingEventTimeWindows.of(Time.seconds(3)))
      .apply{
        (t1 : (String,String, Long), t2 : (String,String, Long), out : Collector[(String,String, Long,Long)]) =>
          println("t1=>"+t1+",时间=>"+Utils.getStringDate3(t1._3))
          println("t2=>"+t2+",时间=>"+Utils.getStringDate3(t2._3))
            // 直接拼接到一起
            out.collect((t1._2,t2._2,t1._3,t2._3))
      }.name("joinedStreams")
      .map(x=>{
        println("结果输出=>"+x)
      })

    // 执行程序
    env.execute("joinedStreams")

  }

}

要触发eventtime window计算需要以下条件缺一不可

  1. env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime) 设置eventtime为flink时间特性
  2. 注册 数据中的时间戳为事件时间,注意这里必须数据本身当中包含时间戳,且必须是毫秒时间戳,不能是秒
  3. 同 processtime 必须关联key相等 并且在同一个时间窗口上有符合要求的两个流的数据
  4. 注意即使满足了上述3点,eventtime的window计算还是不会触发,因为eventtime 需要我们自己控制时间线,事件的水位线必须要大于window的end time才会触发计算,也就是说 如果你两个网络端口只各自模拟一条数据 是永远不会触发计算的,必须要有下一条满足条件的数据到达,并且把水位线升高到end time以上,才会触发计算,结合实际考虑 如果你的数据流数据不是连续到达或者中间有较大间隔,eventtime的滚动窗口可能不适合,因为"最后一个"window可能不能及时触发,注意需要join的两个流都有数据水位线高于window endtime才会触发
  5. 满足了上述4点 在本地调试的时候还是可能会触发不了window,这是为什么呢??!!!,因为如果本地idea环境下如果不设置并行度,会默认cpu的核数为并行度,这样流的数据可能被随机分配到不同的pipeline中去执行,因此匹配不到数据就无法满足window的触发条件,如果是产线环境我们势必要多并行,可以根据keyby,把目标数据分到同一个pipeline中

最终正常的调试结果

57b30094374a6b5a3185015244612920632.jpg

转载于:https://my.oschina.net/u/2969788/blog/3082677

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值