f基础知识点3----window窗口函数与时间语义watermark

Window窗口

streaming 流式计算是一种被设计用于处理无限数据集的数据处理引擎,而无限数据集是指一种不断增长的本质上无限的数据集, 而 window 是一种切割无限数据为有限块进行处理的手段。

无限切割为有限流的方式,将流数据分发到有限大小的桶中进行分析。

在这里插入图片描述

窗口的类型

CountWindow

按照指定的数据条数生成一个 Window, 与时间无关。

TimeWindow

按照时间生成 Window。
对于 TimeWindow,可以根据(窗口实现原理的不同)数据分发到桶的规则不同分成三类:滚动窗口( Tumbling Window)、滑动窗口( Sliding Window) 和会话窗口( Session Window)

滚动窗口(Tumbling Windows)

数据只属于一个窗口。

滑动窗口(Sliding Windows)

数据不只是属于一个窗口,属于多个窗口。

会话窗口(Session Windows)

Window API

窗口分配器 – window() 方法,需要注意的是,KeyedStream里面才有window()方法,因此window()方法的使用,必须在keyby之后,才能使用。

需要注意的是Time这个包,要引用对,streaming api 下面的。注意里面有windowing。

import org.apache.flink.streaming.api.windowing.time.Time

在这里插入图片描述

TimeWindow

滚动窗口(Tumbling Windows)
   val resultStream = dataStream
      .map(data =>(data.id,data.tempreture)) //转换成二元组,求id分组的最小温度,显然时间戳不需要了。
      .keyBy(_._1) // 以id进行分组
      .timeWindow(Time.seconds(15))  //15s的窗口大小,不滑动,也就是滚动。
      //后面可以继续跟着聚合的方法。
滑动窗口(Sliding Windows)

   val resultStream = dataStream
      .map(data =>(data.id,data.tempreture)) //转换成二元组,求id分组的最小温度,显然时间戳不需要了。
      .keyBy(_._1) // 以id进行分组
      .timeWindow(Time.seconds(15), Time.seconds(5)) //15s的窗口大小,滚动5s。
    //后面可以继续跟着聚合的方法。
会话窗口(Session Windows)

会话窗口的话,只能这么写。没有timewindow(),这种简写的方式。

在这里插入图片描述

CountWindow

滚动窗口(Tumbling Windows)
 val resultStream = dataStream
      .map(data =>(data.id,data.tempreture)) //转换成二元组,求id分组的最小温度,显然时间戳不需要了。
      .keyBy(_._1) // 以id进行分组
      .countWindow(10)
      //后面可以继续跟着聚合的方法。
滑动窗口(Sliding Windows)
  val resultStream = dataStream
      .map(data =>(data.id,data.tempreture)) //转换成二元组,求id分组的最小温度,显然时间戳不需要了。
      .keyBy(_._1) // 以id进行分组
     .countWindow(10,2)
     //后面可以继续跟着聚合的方法。

window function 窗口函数

window function 定义了要对窗口中收集的数据做的计算操作,(计算过程中,用哪种方式进行计算,来进行分类)主要可以分为两类

在这里插入图片描述

增量聚合函数

每条数据到来就进行计算, 保持一个简单的状态。典型的增量聚合函数有 ReduceFunction, AggregateFunction。

在当前的有界流中,还是流处理,也就是来一个聚合一次。等到窗口关闭的时候,直接拿出累加好的结果。

也就是在等数据收集的时候,已经在做叠加,已经计算完了。只是等到时间到的时候,把结果输出一下而已。

全窗口函数

先把窗口所有数据收集起来, 等到计算的时候会遍历所有数据。 ProcessWindowFunction 就是一个全窗口函数。

这种就是攒成一批,有界流当做一批,攒齐了。然后做一个批处理。

等到时间到了,最后要输出结果的时候,去遍历所有的数据,依次叠加,然后算完,输出结果。显然这种效率是比增量聚合的效率是低的。

使用场景

排序、当前所有数据的中位数、百分之多少的数据。 这些场景的话,如果来一个数,重新整合一遍,显然是效率太低了,所以直接全量聚合。

全量窗口函数比增量窗口函数获取的信息要多,能够在上下文拿到当前的窗口信息。拿到当前运行时上下文的状态的信息。所以能做的操作更多一些。比较更底层,更加的灵活。

其它可选API

trigger() —— 触发器

定义 window 什么时候关闭, 触发计算并输出结果。

evitor() —— 移除器

定义移除某些数据的逻辑

allowedLateness() ——分布式架构,有可能出现数据的乱序,窗口要关闭的时候,数据还没有到,那么窗口等一会再关闭,解决数据的迟到问题。允许处理迟到的数据。

在开启allowedLateness()之后,可以允许处理迟到数据,当前分布式架构有可能出现数据的乱序,本来应该来的数据,之后才来。窗口关闭的时候,数据还没到,可以等一会,allowedLateness()里面可以传一个时间。

sideOutputLateData() —— 上面allowedLateness()之后,发现还有没到的,放在侧输出流。将迟到的数据放入侧输出流。

sideOutputLateData() 和 allowedLateness()是配合起来用的。允许处理迟到数据的话,那么这个迟到数据要等多久?到底等多久才能保证结果的正确呢?这个就没准了。如果一直等待的话,当前窗口里面的数据,状态,上下文等。一直保持在这里不能释放。这都会占据内存。allowedLateness()给的时间太长的话,那么内存的压力比较大,不能无限等下去。所以,allowedLateness() 一般给一个大概差不多的时间,还有少量的这个时间段没来的,那么把它扔到侧输出流中。

getSideOutput() —— 基于最后结果的datastream,获取侧输出流

官网不推荐直接用WindowAll,因为直接用的话,flink底层会把所有的数据都发送到同一个分区里面,然后再做窗口的分桶。相当于并行度变成了1,整个的任务没有并行了,性能是不好的。所以,非必要情况,还是要先进行keyby的。

在这里插入图片描述

解释

1、进行keyby操作,形成多个分区。
2、进行开窗操作,具体开什么样的窗。
3、在窗口里面,对数据的加工计算。
上面3项呢,是必选的。
还有一些可以选的呢。就是定义 分布式架构下数据的迟到如何处理,以及如何触发窗口关闭等,都是关于在窗口中计算时的数据问题。

在这里插入图片描述

时间语义与 Wartermark

Flink 中的时间语义

在这里插入图片描述

Event  Time: 是事件创建的时间。它通常由事件中的时间戳描述, 例如采集的日志数据中,每一条日志都会记录自己的生成时间,Flink 通过时间戳分配器访问事件时间戳。
Ingestion Time: 是数据进入 Flink 的时间。
Processing  Time: 是每一个执行基于时间操作的算子的本地系统时间, 与机器相关, 默认的时间属性就是 Processing Time

一个例子—— 电影《星球大战》:

在这里插入图片描述
在这里插入图片描述

上面的例子
事件时间,显然就是,了解事件情节的先后顺序,而不是拍摄的早晚,也就是1234567,这样的。
处理时间,就是按拍摄的先后顺序。也就是按照的拍摄时间的先后。

EventTime 的引入

在 Flink 的流式处理中, 绝大部分的业务都会使用 eventTime, 一般只在 eventTime 无法使用时, 才会被迫使用 ProcessingTime 或者 IngestionTime。比较靠前的数据没有到,那么我们就等待数据的到来,但是因为无法判断数据等待多久,窗口何时关闭无法确定,时效性很差了就。那么就不要想着用processing time,用事件时间,至于现在系统本身是多少时间,并不重要。只考虑数据本身的时间戳进展到什么程度了。

如果要使用 EventTime,那么需要引入 EventTime 的时间属性。引入方式也是特别的简单,就是紧跟环境的后面,进行设置。

val env = StreamExecutionEnvironment.getExecutionEnvironment

// 从调用时刻开始给env 创建的每一个stream 追加时间特征,时间特性。
env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)

需要注意的是,使用事件时间语义的时候,因为我们是要从数据的字段中提取一个时间类型的值,来作为时间特性。所以在获取输入流的时候,就要进行设置。

在这里插入图片描述

时间戳的提取和后面的watermark是结合在一起的,所以在后面的时候进行写相关的代码。

Watermark

数据中的时间戳进展到什么程度了。以事件时间,从数据里面提取的发生的时间戳,作为当前时间推进的考量。那么现在的时间就是用时间戳来控制,就和系统时间没有关系了。

理想的情况就是,数据来的顺序和它产生的数据一个一个的来。现实的情况,并不是这个样子,因为分布式系统的存在,在不停的传输的过程中,先发生的数据到后面处理的时候,未必排在前面。

由于乱序的影响,本该小的数据的时间戳没有来,那么相对较大的数据时间戳来了,那么时间也就推进了,如果数据的时间戳到了窗口关闭的时间,那么该关闭?显然是不行的,关了的话,数据就是丢了。因为后面本应该在窗口的数据还没有进入窗口中。

在这里插入图片描述

如果说让着整个窗口多等一段时间的话,一方面窗口的状态不能释放,另一方面,实际的场景,就是数据相差几毫秒,几十毫秒,也就是很短的乱序程度,如果说这时候窗口多等一分钟话,这种情况就是没有必要的。正确的做法就是将整体的时间进行推移,就可以把乱序数据处理了。之前是来了多大的时间戳的数据,我们就以为时间就进行到哪里了。实施整体的时间延迟机制,让时间滞后一点。比如说5秒的时间戳来了,我们延迟2秒的话,我们就认为-- 窗口的时间 --现在只进行到3秒钟了,那么现在认为3秒钟之前的数据该到的都到齐了。

那么引入的这个延迟机制,就是watermark。也就是不用数据中的时间戳来触发窗口关闭,而是用watermark来触发窗口操作。

1Watermark 是一种衡量 Event Time 进展的机制。
2Watermark  是用于处理乱序事件的, 而正确的处理乱序事件, 通常用watermark 机制结合 window 来实现。
3、数据流中的 Watermark 用于表示 timestamp 小于 Watermark 的数据,都已经到达了, 因此, window 的执行也是由 Watermark 触发的。
4Watermark 可以理解成一个延迟触发机制,我们可以设置 Watermark 的延时时长 t,每次系统会校验已经到达的数据中最大的 maxEventTime,然后认定 eventTime小于 maxEventTime - t 的所有数据都已经到达, 如果有窗口的停止时间等于maxEventTime – t, 那么这个窗口被触发执行。

watermark的特点

在这里插入图片描述

在这里插入图片描述

watermark传递原理

选最小的表明,最小的数之前的数都到齐了。
在这里插入图片描述

在这里插入图片描述

watermark的引入

在这里插入图片描述

assignTimestampsAndWatermarks – 水印

关于 assignTimestampsAndWatermarks 这个方法。
点进去,第一眼看,啥也没有。
在这里插入图片描述
仔细看上面的英文注释。

在这里插入图片描述

在这里插入图片描述

按住Ctrl,点进去。
在这里插入图片描述

这个对象实现的类,刚好是 assignTimestampsAndWatermarks 中所需要的类。
而且构造器是 public的,也就是公共的,所以是可以直接new 这个类的。
在这里插入图片描述

输入一个乱序程度,也就是我们的watermark的值。

在这里插入图片描述

sideOutputLateData – 侧输出流

需要注意的是,里面的T,是数据的类型,不要忘了外边的[ ],括号。
在这里插入图片描述
点进去这个类。

在这里插入图片描述
注意类型
在这里插入图片描述
看上面的解释。
在这里插入图片描述

可以看出,是伴生类对象,所以不用new,直接这个类,就相当于是创建了对象。
在这里插入图片描述

代码体现
   // watermark触发窗口之前,小于窗口时间的数据,都是增量聚合,不输出。
  // 到了watermark的时候,就输出了一次。
 //延迟时间范围内的数据,每来一次就输出一次。窗口真正关闭的时候,是watermark的基础上加上延迟时间。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

import org.apache.flink.api.java.utils.ParameterTool
import org.apache.flink.api.scala.createTypeInformation
import org.apache.flink.streaming.api.functions.timestamps.BoundedOutOfOrdernessTimestampExtractor
import org.apache.flink.streaming.api.scala._
import org.apache.flink.streaming.api.windowing.time.Time

case class SensorReading2 (id :String , timestamp: Long ,tempreture:Double)

object WindowTest {

  def main(args: Array[String]): Unit = {

    // 定义流式处理环境。
    val env = StreamExecutionEnvironment.getExecutionEnvironment
    env.setParallelism(1)


    val parameter = ParameterTool.fromArgs(args)
    val host = parameter.get("host")
    val port = parameter.getInt("port")

    //创建一个socket文本流
    val inputDataSet   =  env.socketTextStream(host,port)


    val dataStream = inputDataSet.map(data => {
      val strings = data.split(",")
      SensorReading2(strings(0), strings(1).toLong, strings(2).toDouble) //方法的返回值
    })
       // 在数据源有了具体的类型之后, 然后设置时间戳和水印。
      .assignTimestampsAndWatermarks(  //在数据源后设置水印,这个方法翻译过来: 分配时间戳和水印

           //当水印需要落后于流元素中迄今为止看到的最大时间戳固定的时间量时,
          //并且该量是预先知道的,请使用 BoundedOutOfOrdernessTimestampExtractor。
           new BoundedOutOfOrdernessTimestampExtractor[SensorReading2](Time.seconds(3)) {
        override def extractTimestamp(element: SensorReading2): Long =  element.timestamp * 1000L
      }

      ) //快速得到几乎一个正确的结果,能抓住大多数的数据,近似正确。


    // new OutputTag[T] (STRING) ,其中 T是数据的类型, STRING 是一个标记,也就是标记这个流,对这个流进行一个标记。
    val latetag =   OutputTag[(String,Double)] ("late")

    val resultStream = dataStream
      .map(data =>(data.id,data.tempreture)) //转换成二元组,求id分组的最小温度,显然时间戳不需要了。
      .keyBy(_._1) // 以id进行分组
      .timeWindow(Time.seconds(15))
      .allowedLateness(Time.seconds(15))  // 要求更精确,那么允许处理迟到数据,时间不能太久,否则的话,资源不释放。

       // new OutputTag[T] (STRING) ,其中 T是数据的类型, STRING 是一个标记,也就是标记这个流,对这个流进行一个标记。
      .sideOutputLateData(latetag)  //侧输出流,允许迟到数据之后,还没到那么就到侧输出流中。
     .reduce((cur,newstate)=>(cur._1,cur._2.min(newstate._2)))


    resultStream.print("result")
    resultStream.getSideOutput(latetag).print("late")


    env.execute()

    // watermark触发窗口之前,小于窗口时间的数据,都是增量聚合,不输出。
    // 到了watermark的时候,就输出了一次。
    //延迟时间范围内的数据,每来一次就输出一次。窗口真正关闭的时候,是watermark的基础上加上延迟时间。

  }

}
窗口起点的确定

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

来到了一个方法,就是得到窗口欧的起始偏移量。offset默认是0,timestamp是当前数据的时间戳,windowsize是窗口的大小。
最后得到的结果就是窗口的整数倍。

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值