Catalog
Flink的三种时间语义
- 事件生成时间 Event time :事件自身的时间,一般就是数据本身携带的时间
- 事件接入时间 Ingestion time :事件进入Flink的时间,在数据源操作处(进入 Flink source 时),每个事件将进入 Flink 时当时的时间作为时间戳
- 事件处理时间 Processing time :是每一个执行基于时间操作的算子的本地系统时间,与机器相关,默认的时间属性就是Processing Time
例如,一条日志进入Flink 的时间为2017-11-12 10:00:00.123,到达Window 的系统时间为2017-11-12 10:00:01.234,日志的内容如下:
2017-11-02 18:37:15.624 INFO Fail over to rm2
Event time :2017-11-02 18:37:15.624
Ingestion time :2017-11-12 10:00:00.123
Processing time:2017-11-12 10:00:01.234
在Flink的流式处理中,绝大部分业务都会使用event Time,一般只在event Time无法使用时,才会被迫使用Processing Time或者Ingestion Time。
如果要使用EventTime,那么需要引入EventTime的时间属性,引入EventTime的方法:
val env = StreamExecutionEnvironment.getExecutionEnvironment
//从调用时刻开始给env创建的每一个stream追加时间内特性
env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)
Window
Streaming流式计算是一种被设计用于处理无限数据集的数据处理引擎,而无限数据集是指一种不断增加的本质上无限的数据集,而window是一种切割无限数据为有限块进行处理的手段。
Window是无限数据流处理的核心,Window将一个无限的stream拆分成有限大小的“buckets”桶,我们可以再这些桶上做计算操作。
窗口window是从 Streaming(流) 到 Batch(批) 的一个桥梁。
Window可以是时间驱动的:Time Window(时间窗口):按照时间生成Window;也可以是数据驱动的:Count Window(计数窗口):按照指定的数据条数生成一个Window,与时间无关
Window的分类
-
Tumbling Window(滚动窗口) 窗口长度固定,没有重叠
-
Sliding Window(滑动窗口) 窗口长度固定,可以有重叠
-
Session Window(会话窗口) 时间无对齐
滚动窗口:将数据依据固定的窗口长度对数据进行切片
翻滚窗口能将数据流切分成不重叠的窗口,每一个事件只能属于一个窗口。窗口左闭右开[ )。
滑动窗口:固定窗口的更广义的一种形式,滑动窗口由固定的窗口长度和滑动间隔组成。
滑动窗口和翻滚窗口类似,区别在于:滑动窗口可以有重叠的部分。
会话窗口:由一系列事件组合一个指定的时间长度的timeout间隙组成,一段时间没有接收新数据就会生成新的窗口
没有固定的开始时间和结束时间,只有当一段时间内没有接收新数据就会关闭窗口,下次来新数据的时候开启新窗口。
本质还是TimeWindow,将某段时间内活跃度比较高的数据聚合成一个窗口进行计算,窗口触发的条件是Session Gap,Session Gap规定了不活跃数据的时间上限。
Window API
创建一个窗口(可以基于这个window去做一些聚合或者其他操作)
- 通用方法 .window() window方法必须在keyBy()之后才能用
- 简单方法 .timeWindow() .countWindow() 不能用来创建会话窗口
window()方法接收的输入参数是一个WindowAssigner,它负责将每一条输入的数据分发到正确的window中
Flink提供了通用的WindowAssigner
- Tumbling window
- Sliding window
- Session window
- Global window
Global window将所有相同key的数据分配到单个窗口中计算结果,窗口没有起始和结束时间,窗口需要借助trigger来触发计算。还需要有指定的数据清理机制,否则数据将一直留在内存中。所有的窗口都会在同一个task中计算,也就是并行度parallesim 为1,Global window不常用。
window方法创建窗口的代码实现:
import org.apache.flink.streaming.api.scala.{StreamExecutionEnvironment, createTypeInformation}
import org.apache.flink.streaming.api.windowing.assigners.{SlidingEventTimeWindows, SlidingProcessingTimeWindows, TumblingEventTimeWindows, TumblingProcessingTimeWindows}
import org.apache.flink.streaming.api.windowing.time.Time
case class sensorReading(id:String,timestamp:Long,temperature:Double)
object window_test {
def main(args: Array[String]): Unit = {
val env = StreamExecutionEnvironment.getExecutionEnvironment
env.setParallelism(1)
val inputdatastream = env.socketTextStream("localhost",7777)
val data_stream = inputdatastream
.map ( data => {
val arr = data.split(",")
sensorReading(arr(0), arr(1).toLong, arr(2).toDouble)
}
)
val result_stream = data_stream
.map(data =>(data.id,data.timestamp,data.temperature))
.keyBy(_._1)
//window Assigner
// Tumbling Time Window 第一个参数代表:size 第二个参数代表:offset 延迟
// .window(TumblingEventTimeWindows.of(Time.seconds(10)))
// .window(TumblingEventTimeWindows.of(Time.seconds(10),Time.seconds(5)))
// .window(TumblingProcessingTimeWindows.of(Time.seconds(10)))
// .window(TumblingProcessingTimeWindows.of(Time.seconds(10),Time.seconds(5)))
// Sliding Time Window 第一个参数代表:size 第二个参数代表:slide 第三个参数代表:offset
// .window(SlidingEventTimeWindows.of(Time.seconds(10),Time.seconds(5)))
// .window(SlidingEventTimeWindows.of(Time.seconds(10),Time.seconds(5),Time.seconds(2)))
.window(SlidingProcessingTimeWindows.of(Time.seconds(10),Time.seconds(5)))
// .window(SlidingProcessingTimeWindows.of(Time.seconds(10),Time.seconds(5),Time.seconds(2)))
// 经过windowFunction变成DataStream类型才可以print 现在是一个windowedStream
//windowFunction
.reduce((data1,data2)=>(data1._1,data2._2,data1._3.min(data2._3)))
// result_stream.print()
env.execute("window test")
}
}
参数、方法使用可以参考底层源码,例如
SlidingProcessingTimeWindows类的静态方法可以返回一个SlidingProcessingTimeWindows类型的对象,两个参数均是Time类型,第一个参数是窗口大小,第二个参数是窗口滑动时间,和另一个方法相比,第三个参数默认offset:0
简单方法创建窗口的代码实现:
import org.apache.flink.streaming.api.scala.{StreamExecutionEnvironment, createTypeInformation}
import org.apache.flink.streaming.api.windowing.time.Time
object window_test2 {
def main(args: Array[String]): Unit = {
val env = StreamExecutionEnvironment.getExecutionEnvironment
// env.setParallelism(1)
val inputdatastream = env.socketTextStream("localhost",7777)
val data_stream = inputdatastream
.map ( data => {
val arr = data.split(",")
sensorReading(arr(0), arr(1).toLong, arr(2).toDouble)
}
)
val result_stream = data_stream
.map(data =>(data.id,data.timestamp,data.temperature))
.keyBy(_._1)
// 这两个方法 一个参数代表滚动窗口Tumbling,两个参数代表滑动窗口sliding
.timeWindow(Time.seconds(10))
// .timeWindow(Time.seconds(10),Time.seconds(5))
// .countWindow(10)
// .countWindow(10,5)
.reduce((data1,data2)=>(data1._1,data2._2,data1._3.min(data2._3)))
result_stream.print()
env.execute("window test2")
}
}
window operate = window assigner + window function
窗口函数Window Function --定义了对窗口中收集的数据做的计算操作
- 增量聚合函数(incremental aggregation functions)
来一个就计算一个
ReduceFunction, AggregateFunction, FoldFunction
主要基于中间状态的计算结果,窗口中只维护中间结果状态值,不缓存原始数据。
-
全窗口函数(full window functions)
先把窗口内所有数据收集起来,等到计算的时候遍历所有数据
ProcessWindowFunction
全量窗口函数使用的代价相对较高,性能比较弱,主要因为此时算子需要对所有属于该窗口的介入数据进行缓存,然后等到窗口触发的时候,对所有的原始数据进行汇总计算。如果接入数据量比较大或窗口时间比较长,就比较有可能导致计算性能的下降。
其它可选窗口API
- .allowedLateness() —— 允许处理迟到的数据
- .sideOutputLateData() —— 将迟到的数据放入侧输出流
- .getSideOutput() —— 获取侧输出流