Flink窗口计算

1,窗口(window) 概念

窗口, 就是把无界的数据流, 依据一定规则划分成一段一段的有界数据流来计算;
既然划分成有界数据段, 通常都是为了"聚合";

Keyedwindow 重要特性: 任何一个窗口, 都绑定在自己所属的 key 上; 不同 key 的数据肯定不会划分到相同窗口中去!
 

2、窗口细分类型

2.1 滑动创建

window size: 表示窗口的长度,比如1小时

window slide: 表示窗口滑动的时间间隔,或者就叫滑动长度,也就是每隔多长时间(或者多少条数据)滑动一次

 上面的图,可以理解为:窗口大小为1小时,滑动步长为30分钟。

上面的例子中同一条数据,最多同时属于2个窗口(窗口大小除以窗口滑动步长)

2.2 滚动窗口

window size : 表示窗口的大小,比如1小时。

滚动窗口的应用场景:比如每隔1小时,统计上一个小时的订单总量。

 滚动窗口实际上就是滑动窗口的一个特例:也就是滑动步长等于窗口大小

2.3 会话窗口

  没有固定的窗口长度, 也没有固定的滑动步长, 而是根据数据流中前后两个事件的时间
间隔是否超出阈值(session gap) 来划分;

比如上图中,对于user1,window1这个窗口中收到2条数据(假设窗口时间间隔是5秒),在收到第2条数据后,超过5秒没有收到数据,这时候前面的2条数据就会被划分为一个窗口。比如过了8秒,又收到了4条数据,又过了5秒没收到数据,那么这4条数据就被划分为一个 窗口。 以此类推。

3、窗口计算基本API代码示例

keyed window(带key的窗口),具体的API是 ds.window(....)

NonKeyed Window(不带key的窗口),具体的API是 ds.windowAll(....)

Flink有两类窗口聚合算子:

窗口聚合算子, 整体上分为两类
 增量聚合算子, 如 min、 max、 minBy、 maxBy、 sum、 reduce、 aggregate
 全量聚合算子, 如 apply、 process
两类聚合算子的底层区别
 增量聚合: 一次取一条数据, 用聚合函数对中间累加器更新; 窗口触发时, 取累加器输出结果;
 全量聚合: 数据“攒” 在状态容器中, 窗口触发时, 把整个窗口的数据交给聚合函数;
 

import java.util.Properties

import com.sunzm.flink.demo.datastream.window.agg.CountAggregateFunction
import com.sunzm.flink.demo.datastream.window.process.CountProcessWindowFunction
import org.apache.flink.api.common.eventtime.WatermarkStrategy
import org.apache.flink.api.common.serialization.SimpleStringSchema
import org.apache.flink.api.common.typeinfo.BasicTypeInfo
import org.apache.flink.connector.kafka.source.KafkaSource
import org.apache.flink.connector.kafka.source.enumerator.initializer.OffsetsInitializer
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
import org.apache.kafka.clients.consumer.{ConsumerConfig, OffsetResetStrategy}

object WindowDemo {

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

    //创建一个flink执行环境
    val env = StreamExecutionEnvironment.getExecutionEnvironment

    // 为了方便测试,设置一个并行度
    env.setParallelism(1)

    val properties = new Properties()

    properties.setProperty(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, "true")

    val kafkaSource = KafkaSource.builder[String]()
      //设置连接的broker地址
      .setBootstrapServers("192.168.1.184:9092,192.168.1.185:9092,192.168.1.186:9092")
      //设置反序列化器 (字符串)
      .setValueOnlyDeserializer(new SimpleStringSchema)
      //设置消费的组Id
      .setGroupId("flink-demo-group")
      // 设置消费的主题
      .setTopics("test")
      //设置从哪里开始消费
      .setStartingOffsets(OffsetsInitializer.committedOffsets(OffsetResetStrategy.EARLIEST))
      .build()

    val kafkaDataStream: DataStream[String] = env.fromSource(kafkaSource, WatermarkStrategy.noWatermarks(),
      "kafkaSource")(BasicTypeInfo.STRING_TYPE_INFO)

    //滚动窗口 10秒滚动一次 计算收到 的数据条数
     //如果直接调用 .aggregate(new CountProcessWindowFunction) 方法,输入数据的类型就是 kafkaDataStream 的数据类型
    //如果调用 .aggregate(new CountAggregateFunction, new CountProcessWindowFunction) 方法, 那么 CountProcessWindowFunction 输入数据的类型是 CountAggregateFunction 输出数据的的类型
    //如果调用 .aggregate(new CountAggregateFunction) 方法, 那么最终输出数据的类型就是 CountAggregateFunction 的输出类型
    //如果调用 .aggregate(new CountAggregateFunction, new CountProcessWindowFunction) 方法, 那么最终输出数据的类型就是 CountProcessWindowFunction 的输出类型
    val result = kafkaDataStream.windowAll(TumblingProcessingTimeWindows.of(Time.seconds(10)))
      .aggregate(new CountAggregateFunction, new CountProcessWindowFunction)

    
     val keyedDS = kafkaDataStream.keyBy(line => {
      val jSONObject = JSON.parseObject(line)

      jSONObject.getString("userId")
    })

    //基于事件时间语义的滚动窗口 (窗口长度为10秒)
    keyedDS.window(TumblingEventTimeWindows.of(Time.seconds(10)))
      //.aggregate()

    //基于处理时间语义的滚动窗口 (窗口长度为10秒)
    keyedDS.window(TumblingProcessingTimeWindows.of(Time.seconds(10)))

    //基于事件时间语义的滑动窗口 (窗口长度为10秒, 滑动长度为5秒)
    keyedDS.window(SlidingEventTimeWindows.of(Time.seconds(10), Time.seconds(5)))

    //基于处理时间语义的滑动窗口 (窗口长度为10秒, 滑动长度为5秒)
    keyedDS.window(SlidingProcessingTimeWindows.of(Time.seconds(10), Time.seconds(5)))

    //基于事件时间语义的会话窗口 (时间间隔为10分钟)
    keyedDS.window(EventTimeSessionWindows.withGap(Time.minutes(10)))

    //基于处理时间语义的会话窗口 (时间间隔为10分钟)
    keyedDS.window(ProcessingTimeSessionWindows.withGap(Time.minutes(10)))

    result.print()

    //5.执行  ProcessFunctionDemo$
    env.execute(this.getClass.getSimpleName.stripSuffix("$"))

  }
}
import org.apache.flink.api.common.functions.AggregateFunction

/**
 * 第一个泛型:收到的数据类型,也就是调用这个算子的数据类型
 * 第二个泛型:中间聚合结果的类型,这个例子中计算窗口收到的数据条数,所以是long类型
 * 第三个泛型:输出结果的类型
 */
class CountAggregateFunction extends AggregateFunction[String, Long, Long]{

  override def createAccumulator(): Long = {
    //初始化一个中间结果累加器对象, 这个方法只会在每隔个窗口创建的时候调用一次
    0L
  }

  override def add(value: String, accumulator: Long): Long = {
    //每收到一条数据调用一次, 更新中间累加结果
    //value是新收到的数据, accumulator是上一次的累加结果

    accumulator + 1
  }

  override def getResult(accumulator: Long): Long = {
    //窗口触发计算的时候执行一次,如果窗口10秒触发一次,那么这个方法10秒就会调用一次
    accumulator
  }

  override def merge(a: Long, b: Long): Long = {
    //合并中间结果
    a + b
  }
}
import org.apache.commons.lang.time.DateFormatUtils
import org.apache.flink.streaming.api.scala.function.ProcessAllWindowFunction
import org.apache.flink.streaming.api.windowing.windows.TimeWindow
import org.apache.flink.util.Collector

/**
 * 第一个泛型: 输入数据的类型
 * 第二个泛型: 输出数据的类型
 * 第三个泛型: 窗口的类型
 */
class CountProcessWindowFunction extends ProcessAllWindowFunction[Long, String, TimeWindow]{

  override def process(context: Context, elements: Iterable[Long],
                       out: Collector[String]): Unit = {

    val window = context.window

    //窗口开始时间
    val start = window.getStart

    //窗口结束时间
    val end = window.getEnd

    val startTimeStr = DateFormatUtils.format(start, "yyyy-MM-dd HH:mm:ss")
    val endTimeStr = DateFormatUtils.format(end, "yyyy-MM-dd HH:mm:ss")

    var sum = 0L
    elements.foreach(count => {
      sum += count
    })

    val res = s"窗口开始时间: ${startTimeStr}, 窗口结束时间: ${endTimeStr}, 本窗口收到的数据条数: ${sum}"

    out.collect(res)

  }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值