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