文章目录
1.时间语义
1.1 图示与解释
- Event Time:事件创建的时间
- Ingestion Time:数据进入Flink的时间
- Processing Time:执行操作算子的本地系统时间,与机器相关。机器时间在分布式系统中又叫做 “墙上时钟”
1.2 设置事件时间
val env = StreamExecutionEnvironment.getExecutionEnvironment
env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)
如果要使用 processing time,将 TimeCharacteristic.EventTime 替换为
TimeCharacteristic.ProcessingTIme 就可以了
2.水位线(Watermark)
2.1 watermark概念
- Watermark是用于处理乱序事件的,正确的处理乱序事件,通常用Watermark机制结合window来实现
- window的执行是由Watermark触发的,WaterMark代表着timestamp小于其的数据都到达了
- 只有事件时间才需要水位线
- 水位线产生公式:水位线 = 系统观察到的最大事件时间 - 最大延迟时间
- 当水位线超过窗口结束时间时,窗口将不再接收事件,然后触发计算,计算完毕,窗口就被销毁
2.2 watermark 的特点
- watermark 是一条特殊的数据记录
- watermark 必须单调递增,以确保任务的事件时间时钟在向前推进,而不是在后退
- watermark 与数据的时间戳相关
2.3 watermark 的引入
- Event Time 的使用一定要指定数据源中的时间戳
- 可以调用 TimestampAssigner 接口,自定义如何从事件数据中抽取
时间戳。一般来说,时间戳分配器需要在 source 操作符后马上进行调用:
因为时间戳分配器看到的元素的顺序应该和 source 操作符产生数据的顺序是一 样的,否则就乱了。任何分区操作都会将元素的顺序打乱,例如并行度改变,keyBy() 操作等等,窄依赖不会,例如map,filter。
- 调用 assignTimestampAndWatermarks 方法,传入一个 BoundedOutOfOrdernessTimestampExtractor,就可以指定 watermark
对于排好序的数据,不需要延迟触发,可以只指定时间戳就行了
2.3.1 案例:水位线测试
import java.sql.Timestamp
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.scala.function.ProcessWindowFunction
import org.apache.flink.streaming.api.windowing.time.Time
import org.apache.flink.streaming.api.windowing.windows.TimeWindow
import org.apache.flink.util.Collector
/**
* @Author jaffe
* @Date 2020/06/11 09:28
*/
object WatermarkTest {
def main(args: Array[String]): Unit = {
val env = StreamExecutionEnvironment.getExecutionEnvironment
// 应用程序使用事件时间
env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)
env.setParallelism(1)
// 注释掉下行代码,默认200ms插入一次水位线,可以设置系统每隔一分钟的机器时间插入一次水位线
//env.getConfig.setAutoWatermarkInterval(60000)
val stream = env
.socketTextStream("hadoop103",9999,'\n')
.map(line =>{
val arr = line.split(" ")
// 第二个元素是时间戳,必须转换成毫秒单位
(arr(0),arr(1).toLong * 1000)
})
// 抽取时间戳和插入水位线
// 插入水位线的操作一定要紧跟source算子
.assignTimestampsAndWatermarks(
// 最大延迟时间设置为5s
new BoundedOutOfOrdernessTimestampExtractor[(String, Long)](Time.seconds(5)) {
override def extractTimestamp(t: (String, Long)): Long = t._2
}
)
.keyBy(_._1)
.timeWindow(Time.seconds(10))
.process(new MyProcess)
stream.print()
env.execute()
}
class MyProcess extends ProcessWindowFunction[(String,Long),String,String,TimeWindow]{
override def process(key: String, context: Context, elements: Iterable[(String, Long)], out: Collector[String]): Unit = {
out.collect("窗口结束时间为:" + new Timestamp(context.window.getEnd) + " 的窗口中共有 " + elements.size + " 条数据")
}
}
}
2.3.2 周期性插入水位线(AssignerWithPeriodicWatermarks)
- 周期性的生成 watermark:系统会周期性的将 watermark 插入到流中
- 默认周期是200毫秒,可以使用 ExecutionConfig.setAutoWatermarkInterval() 方法进行设置
- 升序和前面乱序的处理 BoundedOutOfOrderness ,都是基于周期性 watermark 的
案例演示:
import com.jaffe.day02.{SensorReading, SensorSource}
import org.apache.flink.streaming.api.TimeCharacteristic
import org.apache.flink.streaming.api.functions.AssignerWithPeriodicWatermarks
import org.apache.flink.streaming.api.scala._
import org.apache.flink.streaming.api.scala.function.ProcessWindowFunction
import org.apache.flink.streaming.api.watermark.Watermark
import org.apache.flink.streaming.api.windowing.time.Time
import org.apache.flink.streaming.api.windowing.windows.TimeWindow
import org.apache.flink.util.Collector
/**
* @Author jaffe
* @Date 2020/06/11 15:40
*/
object PeriodicInsertWatermarks {
def main(args: Array[String]): Unit = {
val env = StreamExecutionEnvironment.getExecutionEnvironment
env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)
env.setParallelism(1)
val stream = env
.addSource(new SensorSource)
.assignTimestampsAndWatermarks(new MyAssigner)
.keyBy(_.id)
.timeWindow(Time.seconds(5))
.process(new MyProcess)
stream.print()
env.execute()
}
// `BoundedOutOfOrdernessTimestampExtractor`的底层实现
class MyAssigner extends AssignerWithPeriodicWatermarks[SensorReading]{
val bound = 1000L// 最大延迟时间
var maxTs = Long.MinValue + bound// 观察到的最大时间戳
// 每来一条元素就要调用一次
override def extractTimestamp(t: SensorReading, l: Long): Long = {
maxTs = maxTs.max(t.timestamp)
t.timestamp
}
// 产生水位线的函数,默认200ms调用一次
override def getCurrentWatermark: Watermark = {
// 水位线 = 观察到的最大时间戳 - 最大延迟时间
new Watermark(maxTs - bound)
}
}
class MyProcess extends ProcessWindowFunction[SensorReading,String,String,TimeWindow]{
override def process(key: String, context: Context, elements: Iterable[SensorReading], out: Collector[String]): Unit = {
out.collect(elements.size.toString)
}
}
}
2.3.3 不规则插入水位线(AssignerWithPunctuatedWatermarks)
没有时间周期规律,对每条数据进行间断式地生成watermark
class PunctuatedAssigner
extends AssignerWithPunctuatedWatermarks[SensorReading] {
val bound: Long = 60 * 1000
// 每来一条数据就调用一次
override def checkAndGetNextWatermark(r: SensorReading,
extractedTS: Long): Watermark = {
if (r.id == "sensor_1") {//只在id == "sensor_1"时产生水位线,id是随机生成的
// 抽取的时间戳 - 最大延迟时间
new Watermark(extractedTS - bound)
} else {
null
}
}
// 每来一条数据就调用一次
override def extractTimestamp(r: SensorReading,
previousTS: Long): Long = {
r.timestamp
}
}