Flink 窗口计算

Flink 窗口计算

一、概述

窗⼝计算是流计算的核⼼,窗⼝将流数据切分成有限⼤⼩的“buckets”,我们可以对这个“buckets”中的有限数据做运算。

在Flink中整体将窗⼝计算按分为两⼤类:keyedstream窗⼝、datastream窗⼝,以下是代码结构:

keyedStream

stream
.keyBy(…) <--------------- keyed versus non-keyed windows
.window(…) <---------------必须指定: “window assigner”
[.trigger(…)] <--------------- 可选: “trigger” (else default trigger) 决定了窗⼝何时触发计算
[.evictor(…)] <--------------- 可选: “evictor” (else no evictor) 剔除器,剔除窗⼝内的元素
[.allowedLateness(…)] <---------------可选: “lateness” (else zero) 是否允许有迟到
[.sideOutputLateData(…)] <--------------- 可选: “output tag” (else no side output for latedata)
.reduce/aggregate/fold/apply() <--------------- 必须: “Window Function” 对窗⼝的数据做运算
[.getSideOutput(…)] <---------------可选: “output tag” 获取迟到的数据

Non-Keyed Windows

stream

.windowAll(…) <--------------- 必须指定: “window assigner”

☆.Window Lifecycle

当有第⼀个元素落⼊到窗⼝中的时候窗⼝就被 创建,当时间(⽔位线)越过窗⼝的EndTime的时候,该窗⼝认定为是就绪状态,可以应⽤WindowFunction对窗⼝中的元素进⾏运算。当前的时间(⽔位线)越过了窗⼝的EndTime+allowed lateness时间,该窗⼝会被删除

  • 只有time-based windows 才有⽣命周期的概念,因为Flink还有⼀种类型的窗⼝global window不是基于时间的,因此没有⽣命周期的概念。
  • 每⼀种窗⼝都有⼀个Trigger和function与之绑定,function的作⽤是⽤于对窗⼝中的内容实现运算。⽽Trigger决定了窗⼝什么时候是就绪的,因为只有就绪的窗⼝才会运⽤function做运算。除了指定以上的策略以外,我们还可以指定 Evictor ,该 Evictor 可以在窗⼝就绪以后且在function运⾏之前或者之后删除窗⼝中的元素。

Keyed vs Non-Keyed Windows:

  • Keyed Windows:在某⼀个时刻,会触发多个window任务,取决于Key的种类。
  • Non-Keyed Windows:因为没有key概念,所以任意时刻只有⼀个window任务执⾏。

二、Window Assigners

窗口分配器

Window Assigner定义了如何将元素分配给窗⼝,这是通过在 window(...) / windowAll() 指定⼀个Window Assigner实现。

Window Assigner负责将接收的数据分配给1~N窗⼝,Flink中预定义了⼀些Window Assigner分如下:

  • tumbling windows , sliding windows , session windows 和 global windows.
  • ⽤户可以同过实现WindowAssigner类⾃定义窗⼝。除了global windows 以外其它窗⼝都是基于时间TimeWindow.Timebased窗⼝都有 start timestamp (包含)和end timestamp (排除)属性描述⼀个窗⼝的⼤⼩。
①.Tumbling Windows

滚动窗口

package com.baizhi.window

import org.apache.flink.streaming.api.scala._
import org.apache.flink.streaming.api.windowing.assigners.TumblingProcessingTimeWindows
import org.apache.flink.streaming.api.windowing.time.Time

object Tumbling {
   
  def main(args: Array[String]): Unit = {
   
    //获取执行环境
    val env = StreamExecutionEnvironment.getExecutionEnvironment

    //获取输入源
    val stream: DataStream[String] = env.socketTextStream("hbase",9999)

    //处理
    stream.flatMap(_.split("\\s+"))
      .map(x=>(x,1))
      .keyBy(0)
      //设置一个滚动窗口 周期长度为 5秒
      .window(TumblingProcessingTimeWindows.of(Time.seconds(5)))
      .reduce((y,z)=>(y._1,y._2+z._2))
      .print()
    env.execute("Tumbling Processing Time Window Word Count")

  }
}
②.Sliding Windows

滑动窗口

package com.baizhi.window

import org.apache.flink.api.common.functions.AggregateFunction
import org.apache.flink.streaming.api.scala._
import org.apache.flink.streaming.api.windowing.assigners.{
   SlidingProcessingTimeWindows, TumblingProcessingTimeWindows}
import org.apache.flink.streaming.api.windowing.time.Time

object Sliding {
   
  def main(args: Array[String]): Unit = {
   
    //获取执行环境
    val env = StreamExecutionEnvironment.getExecutionEnvironment

    //获取输入源
    val stream: DataStream[String] = env.socketTextStream("hbase",9999)

    //处理
    stream.flatMap(_.split("\\s+"))
      .map(x=>(x,1))
      .keyBy(0)
      //设置一个滑动窗口
      .window(SlidingProcessingTimeWindows.of(Time.seconds(4),Time.seconds(2)))
      //自定义一个聚合规则
      .aggregate(new MydefinedSlidingAggregateFunction)
      .print()

    env.execute("Sliding Processing Time Window Word Count")

  }

}
//自定义一个聚合函数
class MydefinedSlidingAggregateFunction extends AggregateFunction[(String,Int),(String,Int),(String,Int)]{
   
  override def createAccumulator(): (String, Int) = {
   
    //创建一个累加器
    ("",0)
  }
  override def add(in: (String, Int), acc: (String, Int)): (String, Int) = {
   
    //求和
    (in._1,in._2+acc._2)
  }
  override def getResult(acc: (String, Int)): (String, Int) = {
   
    acc
  }
  override def merge(acc: (String, Int), acc1: (String, Int)): (String, Int) = {
   
    (acc._1,acc._2+acc1._2)
  }
}
③.Session Windows

会话窗⼝分配器按活动会话对元素进⾏分组。与滚动窗⼝和滑动窗⼝相⽐,会话窗⼝不重叠且没有固定的开始和结束时间。相反,当会话窗⼝在⼀定时间段内未接收到元素时(即,发⽣不活动间隙时),它将关闭。

package com.baizhi.window

import java.text.SimpleDateFormat

import org.apache.flink.api.common.functions.AggregateFunction
import org.apache.flink.streaming.api.scala._
import org.apache.flink.streaming.api.scala.function.WindowFunction
import org.apache.flink.streaming.api.windowing.assigners.{
   ProcessingTimeSessionWindows, SlidingProcessingTimeWindows, TumblingProcessingTimeWindows}
import org.apache.flink.streaming.api.windowing.time.Time
import org.apache.flink.streaming.api.windowing.windows.TimeWindow
import org.apache.flink.util.Collector

object Session {
   
  def main(args: Array[String]): Unit = {
   
    //获取执行环境
    val env = StreamExecutionEnvironment.getExecutionEnvironment

    //获取输入源
    val stream: DataStream[String] = env.socketTextStream("hbase",9999)

    //处理
    stream.flatMap(_.split("\\s+"))
      .map(x=>(x,1))
      .keyBy(_._1)
      //设置一个session,间隔时间为5S
      .window(ProcessingTimeSessionWindows.withGap(Time.seconds(5)))
      //自定义一个聚合规则 ,此算子不支持 位置的聚合
      .apply(new MydefinedWindowsFunction)
      .print()
    env.execute("Session Processing Time Window Word Count")
  }
}


//自定义一个聚合函数
class MydefinedWindowsFunction extends WindowFunction[(String,Int),(String,Int),String,TimeWindow]{
   
  override def apply(key: String, window: TimeWindow, input: Iterable[(String, Int)], out: Collector[(String, Int)]): Unit = {
   
    //定义一个时间格式
    val sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
    //解析时间格式
    val stTime = sdf.format(window.getStart)
    val edTime = sdf.format(window.getEnd)
    //获取总值
    val count = input.map(_._2).sum
    //输出
    out.collect(s"$key \t $stTime \t $edTime \t",count)
  }
}
④.Global Windows

global window不是基于时间的

全局窗⼝分配器将具有相同键的所有元素分配给同⼀单个全局窗⼝。仅当您指定了触发器时,此窗⼝⽅案才有⽤。否则,将不会执⾏任何计算,因为全局窗⼝没有可以处理聚合元素的⾃然终点。

package com.baizhi.window

import java.text.SimpleDateFormat

import org.apache.flink.api.common.functions.AggregateFunction
import org.apache.flink.streaming.api.scala._
import org.apache.flink.streaming.api.scala.function.WindowFunction
import org.apache.flink.streaming.api.windowing.assigners.{
   GlobalWindows, SlidingProcessingTimeWindows, TumblingProcessingTimeWindows, WindowAssigner}
import org.apache.flink.streaming.api.windowing.time.Time
import org.apache.flink.streaming.api.windowing.triggers.CountTrigger
import org.apache.flink.streaming.api.windowing.windows.GlobalWindow
import org.apache.flink.util.Collector

object Global {
   
  def main(args: Array[String]): Unit = {
   
    //获取执行环境
    val env = StreamExecutionEnvironment.getExecutionEnvironment

    //获取输入源
    val stream: DataStream[String] = env.socketTextStream("hbase",9999)

    //处理
    stream.flatMap(_.split("\\s+"))
      .map(x=>(x,1))
      .keyBy(_._1)
      //设置一个Global窗口
      .window(GlobalWindows.create())
    //定义一个触发器 触发数量为3
      .trigger(CountTrigger.of(3))
      //自定义一个聚合规则
      .apply(new MydefinedGlobalFunction)
      .print()
    env.execute("Global Processing Time Window Word Count")
  }
}

//自定义一个聚合函数
class MydefinedGlobalFunction extends WindowFunction[(String,Int),(String,Int),String,GlobalWindow]{
   
  override def apply(key: String, window: GlobalWindow, input: Iterable[(String, Int)], out: Collector[(String, Int)]): Unit = {
   

    out.collect(key,input.map(_._2).sum)
  }
}

三、Window Functions

定义窗⼝分配器后,我们需要指定要在每个窗⼝上执⾏的计算。这是Window Function的职责,⼀旦系统确定窗⼝已准备好进⾏处理,就可以处理每个窗⼝的元素。

窗⼝函数可以是ReduceFunction,AggregateFunction,FoldFunction、ProcessWindowFunction或WindowFunction(古董)之⼀。

  • 其中ReduceFunction和AggregateFunction在运⾏效率上⽐ProcessWindowFunction要⾼,因为前俩个⽅法执⾏的是增量计算,只要有数据抵达窗⼝,系统就会调⽤ReduceFunction,AggregateFunction实现增量计算;
  • ProcessWindowFunction在窗⼝触发之前会⼀直缓存接收数据,只有当窗⼝就绪的时候才会对窗⼝中的元素做批量计算,但是该⽅法可以获取窗⼝的元数据信息。但是可以通过将ProcessWindowFunction与ReduceFunction,AggregateFunction或FoldFunction结合使⽤来获得窗⼝元素的增量聚合以及ProcessWindowFunction接收的其他窗⼝元数据,从⽽减轻这种情况。
①.ReduceFunction
package com.baizhi.windowfunction

import org.apache.flink.api.common.functions.{
   AggregateFunction, ReduceFunction}
import org.apache.flink.streaming.api.scala._
import org.apache.flink.streaming.api.windowing.assigners.{
   SlidingProcessingTimeWindows, TumblingProcessingTimeWindows}
import org.apache.flink.streaming.api.windowing.time.Time

object ReduceFunction {
   
  def main(args: Array[String]): Unit = {
   
    //获取执行环境
    val env = StreamExecutionEnvironment.getExecutionEnvironment

    //获取输入源
    val stream: DataStream[String] = env.socketTextStream("hbase",9999)
    //测试使用
    println("====STREAM START====")
    //处理
    stream.flatMap(_.split("\\s+"))
      .map(x=>(x,1))
      .keyBy(0)
      //设置一个滚动窗口
      .window(TumblingProcessingTimeWindows.of(Time.seconds(5)))
      //自定义一个聚合规则
      .reduce(new UserDefinedReduceFunction)
      .print()

    env.execute("Sliding Processing Time Window Word Count")

  }

}
//自定义一个聚合函数
class UserDefinedReduceFunction extends ReduceFunction[(String,Int)]{
   
  override def reduce(t: (String, Int), t1: (String, Int)): (String, Int) = {
   
    println("----执行一次reduce算子----")
    (t._1,t._2+t1._2)
  }
}
②.AggregateFunction
package com.baizhi.windowfunction

import org.apache.flink.api.common.functions.AggregateFunction
import org.apache.flink.streaming.api.scala._
import org.apache.flink.streaming.api.windowing.assigners.TumblingProcessingTimeWindows
import org.apache.flink.streaming.api.windowing.time.Time

object AggregateFunction {
   
  def main(args: Array[String]): Unit = {
   
    //获取执行环境
    val env = StreamExecutionEnvironment.getExecutionEnvironment

    //获取输入源
    val stream: DataStream[String] = env.socketTextStream("hbase",9999)
    //测试使用
    println("====STREAM START====")
    //处理
    stream.flatMap(_.split("\\s+"))
      .map(x=>(x,1))
      .keyBy(0)
      //设置一个滚动窗口
      .window(TumblingProcessingTimeWindows.of(Time.seconds(5)))
      .aggregate(new UserDefinedAggregateFunction)
      .print()

    env.execute("Tumbling Processing Time Window Word Count")

  }
}


//自定义一个类
class UserDefinedAggregateFunction extends AggregateFunction[(String,Int),(String,Int),(String,Int)]{
   
  override def createAccumulator(): (String, Int) = {
   
    ("",0)
  }
  override def add(in: (String, Int), acc: (String, Int)): (String, Int) = {
   
    println("合并一次")
    (in._1,in._2+acc._2)
  }
  override def getResult(acc: (String, Int)): (String, Int) = {
   
    //返回
    acc
  }
  override def merge(acc: (String, Int), acc1: (String, Int)): (String, Int) = {
   
    (acc._1,acc._2+acc1._2)
  }
}

③.FoldFunction

FoldFunction不可以⽤在Session Window中

package com.baizhi.windowfunction

import org.apache.flink.api.common.functions.{
   AggregateFunction, FoldFunction}
import org.apache.flink.streaming.api.scala._
import org.apache.flink.streaming.api.windowing.assigners.TumblingProcessingTimeWindows
import org.apache.flink.streaming.api.windowing.time.Time

object FoldFunction {
   
  def main(args: Array[String]): Unit = {
   
    //获取执行环境
    val env = StreamExecutionEnvironment.getExecutionEnvironment

    //获取输入源
    val stream: DataStream[String] = env.socketTextStream("hbase",9999)
    //测试使用
    println("====STREAM START====")
    //处理
    stream.flatMap(_.split("\\s+"))
      .map(x=>(x,1))
      .keyBy(0)
      //设置一个滚动窗口
      .window(TumblingProcessingTimeWindows.of(Time.seconds(5)))
      .fold((
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值