flink 多流join 触发时机详解

推荐大家去看原文博主的文章,条理清晰阅读方便,转载是为了方便以后个人查阅

https://my.oschina.net/u/2969788/blog/3082677

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中

最终正常的调试结果

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
flink中的join操作可以将两个或多个数据流中的元素进行关联,从而生成一个新的数据流。flink支持多种类型的join操作,包括inner join、left join、right join和full outer join等。下面是一个简单的flink join流的例子: ```python # 导入必要的库 from pyflink.common.serialization import SimpleStringSchema from pyflink.datastream import StreamExecutionEnvironment from pyflink.datastream.connectors import FlinkKafkaConsumer from pyflink.table import StreamTableEnvironment, EnvironmentSettings # 创建StreamExecutionEnvironment env = StreamExecutionEnvironment.get_execution_environment() env.set_parallelism(1) # 创建StreamTableEnvironment settings = EnvironmentSettings.new_instance().in_streaming_mode().use_blink_planner().build() table_env = StreamTableEnvironment.create(env, environment_settings=settings) # 定义Kafka数据源 source_topic = "source_topic" sink_topic = "sink_topic" properties = { "bootstrap.servers": "localhost:9092", "group.id": "test-group" } source_schema = SimpleStringSchema() source = FlinkKafkaConsumer(source_topic, source_schema, properties=properties) # 读取数据流 source_stream = env.add_source(source) # 将数据流转换为Table source_table = table_env.from_data_stream(source_stream, ['key', 'value']) # 定义第二个数据流 second_source_topic = "second_source_topic" second_source_schema = SimpleStringSchema() second_source = FlinkKafkaConsumer(second_source_topic, second_source_schema, properties=properties) # 读取第二个数据流 second_source_stream = env.add_source(second_source) # 将第二个数据流转换为Table second_source_table = table_env.from_data_stream(second_source_stream, ['key', 'second_value']) # 定义第三个数据流 third_source_topic = "third_source_topic" third_source_schema = SimpleStringSchema() third_source = FlinkKafkaConsumer(third_source_topic, third_source_schema, properties=properties) # 读取第三个数据流 third_source_stream = env.add_source(third_source) # 将第三个数据流转换为Table third_source_table = table_env.from_data_stream(third_source_stream, ['key', 'third_value']) # 将第一个数据流和第二个数据流进行join操作 join_table = source_table.join(second_source_table).where('key == key').select('key, value, second_value') # 将join结果和第三个数据流进行join操作 result_table = join_table.join(third_source_table).where('key == key').select('key, value, second_value, third_value') # 将结果写入到Kafka中 result_schema = SimpleStringSchema() result = result_table.select('key, value, second_value, third_value'). \ .write_to_format('kafka') \ .with_properties(properties) \ .with_topic(sink_topic) \ .with_schema(result_schema) # 执行任务 env.execute("Flink Join Stream Example") ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值