Flink的Window

Flink的Window操作

Flink认为Batch是Streaming的一个特例,所以Flink底层引擎是一个流式处理引擎,而Window就是Streaming和Batch链接的桥梁。
Window的概念:在流式处理应用中,数据是连续不断的,我们不可能等到所有数据都到了之后再去处理,但是有时根据业务的需求我们需要做一些聚合类的处理。例如:在过去的一分钟内有多少用户点击了我们的网页,在这种情况下,我们必须定义一个窗口,用来收集最近一分钟的数据,并对这个窗口内的数据进行计算。
Window的类型:
时间窗口(time-window):根据时间进行截取,比如1分钟统计一次或者10分钟统计一次
1.tumbling-time-window(滚动时间窗口-无重叠数据):按照时间进行窗口划分,每次窗口的滑动距离等于窗口的长度,数据就不会重复计算。
原理:滚动窗口分配器将每个元素分配到一个指定窗口大小的窗口中
使用场景:适合做BI统计等
在这里插入图片描述

/**
  * Flink默认的时间窗口根据ProcessTime进行窗口的划分,
  * 将Flink获取到的数据根据进入的Flink的时间划分不同的窗口中,
  * 时间间隔可以通过Time.millseconds(),Time.seconds(),Time.minutes()等其中的一个指定。
  *
**/
object StreamingTumblingTimeWindow{
	def main(args:Array[String]):Unit = {
		//1.创建运行环境
		val env:StreamExecutionEnvironment = StreamExceptionEnvironment.getExecutionEnvironment
		//2.定义数据源
		val textStream = env.socketTextStream("node01",9999)
		//3.转换数据格式
		val data = textStream.map(line => {
			val array = line.split(",")
			WordCountCart(array(0).toInt,array(1).toInt)
		})
		//4.执行统计操作,每个sensorId一个tumling窗口,窗口大小为5S
		val keyByData:KeyedStream[WordCountCart,Int] = data.keyBy(line => line.sen)
		//无重叠数据,所以只需给一个参数即可
		val result = KeyByData.timeWindow(Time.seconds(5)).sum(1)
		//5.显示统计结果
		result.print()
		//6.触发流计算
		env.excute()
	}
}

case class WordCountCart(sen:Int,carNum:Int)

2.sliding-time-window(滑动时间窗口-有重叠数据):窗口大小固定,可以有重叠。
原理:
(1)滑动窗口分配器将数据分配到固定的长度的窗口中,与滚动窗口类似。
(2)窗口的大小由窗口大小参数来配置
(3)窗口滑动的频率由窗口滑动的参数来配置
(4)如果滑动窗口的大小小于窗口的大小,数据会发生重叠,这种情况下元素会分配到多个窗口中。
适用场景:对最近一个时间段内的统计(求某接口最近5分钟的失败率决定是否报警)
在这里插入图片描述

/**
  * 滑动窗口和滚动窗口的函数名是完全一致的
  * 在传参时需要传入两个参数,一个是window_size,一个是sliding_size
  * 时间间隔可以通过Time.millseconds(),Time.seconds(),Time.minutes()等其中一个来指定
**/
Object StreamingTimeSlidingWindow{
	def main(args:Array[String]):Unit = {
		//1.创建运行环境
		val env:StreamExecutionEnviroment = StreamExecutionEnviroment.getExecutionEnvironment
		//2.定义数据源
		val textStream = env.socketTextStream("node01",9999)
		//3.转换数据格式
		val data = textStream.map(line => {
			val array = line.split(",")
			WordCountCart(arr(0).toInt,arr(1).toInt)
		}) 
		//4.执行计算操作,每个sensorId一个Sliding窗口,窗口大小为5秒
		//也就是说没2S统计一次,在这过去的10S内各个路口通过红绿灯汽车的数量
		val keyByData:KeyedStream[WordcountCart,Int] = data.keyBy(line => line.sen)
		val result = keyByData.timeWindow(Time.seconds(10),Time.seconds(2)).sum(1)
		//5.显示统计结果
		result.print()
		//6.触发流计算
		env.execute
	}
}

case class WordCountCart(sen:Int,carNum:Int)

计数窗口(count window):按照指定的数据条数生成一个Window,与时间无关。
1.tumbling-count-window(无重叠数据):按照事件的个数进行统计

/**
  * CountWindow根据窗口中相同key元素的数量触发执行,执行时只计算事件个数达到窗口大小的key对应的结果
**/
object StreamingCountTumblingWindow {
  def main(args: Array[String]): Unit = {
    //1.创建运行环境
    val env: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment
    //2.定义数据流来源
    val textStream = env.socketTextStream("node01", 9999)
    //3.转换数据格式,text->CountCart
    val data = textStream.map(line => {
      val array = line.split(",")
      CountCart(array(0).toInt, array(1).toInt)
    })
    //4.执行统计操作
    //按照key进行收集,对应的key出现的次数达到5次作为一个结果
    val keyByData: KeyedStream[CountCart, Int] = data.keyBy(line => line.sen)
    //相同的key出现三次才做一次sum聚合
    val result = keyByData.countWindow(3).sum(1)
    //5、显示统计结果
    result.print()
    //6、触发流计算
    env.execute()
  }
}
case class CountCart(sen:Int, cardNum:Int)

2.sliding-count-window(有重叠数据)

object StreamingCountSlidingWindow {
  def main(args: Array[String]): Unit = {
    //1.创建运行环境
    val env: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment
    //2.定义数据流来源
    val textStream = env.socketTextStream("node01", 9000)
    //3.转换数据格式,text->CarWc
    val data = textStream.map(line => {
      val array = line.split(",")
      CountCart(array(0).toInt, array(1).toInt)
    })
    //4.执行统计操作,每个sensorId一个sliding窗口,窗口大小5条数据,窗口滑动为3条数据
    //也就是说,每个路口分别统计,收到关于它的3条消息时统计在最近5条消息中,各自路口通过的汽车数量
    val keyByData: KeyedStream[CountCart, Int] = data.keyBy(line => line.sen)
    val result = keyByData.countWindow(5, 3).sum(1)
    //5、显示统计结果
    result.print()
    //6、触发流计算
    env.execute()
  }
}

会话窗口(session window):由一系列事件组合一个指定时间长度的timeout间隙组成,类似于web的session,也就是一段时间没有接收到新数据就会生成新的窗口。
特点:时间不对齐
原理:
(1)session窗口分配器通过session活动来元素进行分组。
(2)session窗口跟滚动窗口和滑动窗口相比,不会有重叠和固定的开始时间和结束时间情况。
(3)当它在一个固定的时间内没有接收到数据,即非活动间隔产生,那么这个窗口就会关闭。
(4)一个session窗口通过一个session间隔来配置,这个session间隔定义了非活跃周期的长度。
(5)当这个非活跃周期产生,那么当前的session将关闭并且后续的数据会发送到新的session窗口中。
计数窗口(Count-window):根据消息的数量进行截取,按照指定的数据条数生成一个window,与时间无关。
Window的API
窗口的分配器-window()方法
KeyedStream实例.window()来定义一个窗口,然后基于这个窗口做一些聚合或者其他处理。
注意:window()方法必须在KeyBy方法之后使用
Flink提供了更加简单的KeyedStream实例.timeWindow()和KeyedStream。实例.countWindow()方法,用于定义时间窗口和计数窗口。
窗口分配器(window assigner)
1.KeyedStream实例.window()接收输入的参数是一个WindowAssigner。
2.WindowAssigner负责指定窗口的类型,将数据流分发到正确的window中。
3.窗口所表现出的类型特性取决于WindowAssigner的定义。
4.Flink提供了通用的WindowAssigner
滚动窗口:tumbling window
滑动窗口:sliding window
会话窗口:session window
全局窗口:global window
自定义窗口:custom window
window fuction
window function定义了要对窗口中收集的数据做的计算操作,主要分为两类:
1.增量聚合函数(incerment alaggregation functions)
窗口不维护原始数据,只维护中间结果,每次基于中间结果和增量的数据进行聚合。
每条数据到来就进行计算,保持一个简单的状态。
典型的增量聚合函数有ReduceFunction、AggregateFunction。
注意:FoldFunction也是增量聚合函数,但在Flink1.9.0中标记过时,可用AggreateFunction函数代替。
案例:获取一段时间内(Window size)每个红外测温仪(KeyBy)监测到的旅客的平均体温(aggregateFunction)。

import org.apache.flink.api.scala._
env.socketTextStream("node01",8888)
	.filter(_.trim.nonEmpty)
	.map(line => {
		val arr = line.split(",")
		val id = arr(0).trim
		val temperature = arr(1).trim.Double
		val name = arr(2).trim
		val timeStamp = arr(3).trim.toLong
		val location = arr(4).trim
		Raytek(id,temperature,name,timestamp,location)
	}).keyBy(0)
		.timeWindow(Time.seconds(5))
		.aggregate(new AggregateFunction[Raytek,(String,Int,Double),(String,Double)]{
		override def createAccumulator():(String,Int,Double)=("",0.0.0)
		
		override def add(value:Raytek,accumulator:(String,Int,Double)):(String,Int,Double) = {
			val cnt = accumulator._2 + 1
			val temp = accumulator._3 + value.temperature
			(value.id,cnt,temp)
		}
		
		override def merge(a:(String,Int,Double),b:(String,Int,Double)):(String,Int,Double) = {
			val mergeCnt = a._2 + b._2
			val mergeTemp = a._3 + b._3
			(a._1,mergeCnt,mergeTemp)
		}

		override def getResult(accumulator:(String,Int,Double)):(String,Double)=(accumulator._1,accumulator._3/accumulator._2)
	})
	.print()

2.全窗口函数(fulll window functions)
窗口需要维护全部原始数据,窗口触发进行全量聚合。
先把窗口所有数据全部收集起来,等到遍历的时候会遍历所有数据。
可以实现对窗口内的数据进行排序等需求。
ProcessWindowFunction就是一个全窗口函数。
注意:WindowFunction也是全量聚合函数,已被更高级的ProcessWindowFunction逐渐代替。
案例:获取一段时间内(Window Size)每个红外测温仪(KeyBy)监测到的旅客平均体温(ProcessWindowFunction)

env.socketTextStream("node01",8888)
	.filter(_.trim.nonEmpty)
	.map(line => {
		val arr = linr.split(",")
		val id = arr(0).trim
		val temperature = arr(1).trim.toDouble
		val name = arr(2).trim
		val timeStamp = arr(3).trim.toLong
		val location = arr(4).trim
		Raytek(id,temperature,name,timestamp,location)
	})
	.KeyBy("id")
	.timeWindow(Time.seconds(5))
	.process[(String,Double)](new ProcessWindowFunction[Raytek,(String,Double),Tuple,TimeWindow]{
		override def process(key:Tuple,context:Context,elements:Iterable[Raytek],out:Collector[(String,Double)]):Unit = {
			val cnt = 0
			val totalTemp = 0.0
			elements.foreach(line => {
				cnt = cnt + 1
				totalTemp = totalTemp + line.temperature
			})
			out.collect((key.getField(0),TotalTemp/cnt))
		}
	}).print()

案例:增量聚合(AggrateFunction)+全窗口函数(ProcessWindowFunction),获取一段时间内(Window Size)每个红外测温仪(KeyBy)监测到的旅客的平均体温,并求出窗口信息。

env.socketTextStream("node01",8888)
	.filter(_.trim.nonEmpty)
	.map(line => {
		val arr = linr.split(",")
		val id = arr(0).trim
		val temperature = arr(1).trim.toDouble
		val name = arr(2).trim
		val timeStamp = arr(3).trim.toLong
		val location = arr(4).trim
		Raytek(id,temperature,name,timestamp,location)
	})
	.KeyBy(0)
	.timeWindow(Time.seconds(5))
	.aggrate(new AggregateFunction[Raytek,(String,Int,Double),(String,Double)]{
		override def createAccumulator() = ("",0,0.0)
		override def add(value:Raytek,accumulator:(String,Int,Double)):(String,Int,Double) = {
			val cnt = accumulator._2 + 1
			val temp = accumulator._3 + value.temperature
			(value,id,cnt,temp)
		}
		
		override def getResult(accumulator:(String,Int,Double)):(String,Double) = (accumulator._1,accumulator._3/accumulator._2)
	},
	new ProcessWindowFunction[(String,Double),String,Tuple,TimeWindow]
	override def process(key:Tuple,context:Context,elements:Iterator[(String,Double)],out:Colletors[String]):Unit = {
		val(id,avgTemp) = elements.itearator.next()

		val fmt = new SimpleDateFormat("yyyy年MM月dd日 HH时mm分ss秒")
		val date = new Data()
		date.setTime(context,window.getStart)
		val windowStart = fmt.format(date)
		date.setTime(context.window.getEnd)
		val windowEnd = fmt.format(date)
		
		val record = f"红外测温仪id->${key.getField(0)},窗口开始时间->$windowStart,窗口结束时间->$windowEnd,旅客的平均体温->$avgTemp%.2f"
		out.collect(record);
		}
	}).print()

Window的apply()方法
apply方法可以进行一系列的自定义处理,通过匿名内部类的方法实现,有复杂的计算时使用
用法:
1.实现WindowFunction类
2.指定该类的泛型【输入数据类型,输出数据类型,KeyBy中使用分组字段的类型,窗口类型】
示例:
使用apply方法来实现单词统计,每一行数据按照空格切分单词。
步骤:
1.获取流处理运行环境。
2.构建socket流数据源,并指定IP地址和端口号
3.对接收到的数据转换成单词元组。
4.使用keyBy进行分流(分组)
5.使用timeWindow指定窗口的长度(每3S计算一次)
6.实现一个WindowFunction匿名内部类
在apply方法中实现聚合计算
使用Collector.collect收集数据
7.打印输出
8.启动执行
9.在Linux中,使用nc -lk 端口号监听端口,并发送单词

object WindowApply {
	
	def main(args:Array[String]):Unit = {
		//1.获取流处理运行环境
		val env = StreamExecutionEnvironment.getExecutionEnvironment
		//2.构建socket流数据源,并指定IP地址和端口号
		val socketDataStream = env.socketTextStream("node01",9999)
		//3.对接收到的数据转换成单词元组
		val wordcountDataStream:DataStream[(String,Int)] = socketDataStream.flatMap{
			text => text.split(",").map(_ -> 1)
		}
		
		//4.使用keyBy进行分流(分组)
		val groupedDataStream = wordCountDataStream.KeyBy(_._1)

		//5.使用timewindow指定窗口的长度(每3S计算一次)
		val windowDataStream = groupDataStream.timeWindow(Time.seconds(3))

		//6.实现一个WindowFunction匿名内部类
		val resultDataStream:DataStream[(String,Int)] = windowDataStream.apply(new WindowFunction[(String,Int),(String,Int),(String,Int),String,TimeWindow]{
			// 在apply方法中实现聚合计算
			override def apply(key:String,window:TimeWindow,Input:Iterable[(String,Int)],out:Collector[(String,Int)]):Unit = {
				val resultWordCount:(String,Int) = input.reduce{
					(wc1,wc2) => (wc1._1,wc1._2+wc2._2)
				}
				//使用Collector.collect收集数据
				out.collect(resultWordCount)
			}
		}) 

		//7.打印输出
		resultDataStream.print()

		//8.启动执行
		env.execute("App")
	}
}

触发器(.trigger())

1.定义window什么时候关闭,出发计算并输出结果
2.每一个window分会有一个默认的Trigger(触发器),如果默认的触发器不能满足需要,可以自定义触发器
3.常见的trigger
EventTimeTrigger:基于EventTime Window的默认触发器
ProcessTimeTrigger:基于ProcessingTime Window的默认触发器
CountTrigger:基于Count Window的默认触发器
PurgingTrigger:内部使用,用于清楚窗口内容
NeverTrigger:永不触发的触发器

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值