Flink窗口计算全解析:从基础到实战

目录

代码各部分详细解释

1. 包导入与数据模型定义

2. 执行环境设置

3. 数据源与时间戳分配

4. 增量聚合函数 Reduce

5. 增量聚合函数 Aggregate

6. 全窗口函数 WindowFunction

7. 全窗口函数 ProcessWindowFunction

8. 增量聚合函数与全窗口函数的结合使用

代码原理知识背景拓展

1. 窗口计算基础

2. 增量聚合函数

3. 全窗口函数

4. 时间语义

5. 水位线(Watermark)

6. 窗口分配器

7. 状态管理

8. 容错机制


package window

import java.text.SimpleDateFormat

import org.apache.flink.api.common.eventtime.{SerializableTimestampAssigner, WatermarkStrategy}
import org.apache.flink.api.common.functions.{AggregateFunction, ReduceFunction}
import org.apache.flink.streaming.api.scala._
import org.apache.flink.streaming.api.scala.function.{ProcessWindowFunction, WindowFunction}
import org.apache.flink.streaming.api.windowing.assigners.{EventTimeSessionWindows, SlidingEventTimeWindows, TumblingEventTimeWindows, 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
import source.ClickSource

case class UrlViewCount(url:String,count:Long,widowStart:String,windowEnd:String)
/**
 *
 * @PROJECT_NAME: flink1.13
 * @PACKAGE_NAME: window
 * @author: 赵嘉盟-HONOR
 * @data: 2023-05-23 0:21
 * @DESCRIPTION
 *
 */
object windowFunction {
  def main(args: Array[String]): Unit = {
    val env = StreamExecutionEnvironment.getExecutionEnvironment
    env.setParallelism(1)

    val data = env.addSource(new ClickSource)
      .assignTimestampsAndWatermarks(WatermarkStrategy.forMonotonousTimestamps()
        .withTimestampAssigner(new SerializableTimestampAssigner[source.Event] {
          override def extractTimestamp(t: source.Event, l: Long): Long = t.timestamp
        }))
    //TODO 增量聚合函数 Reduce(流处理)
    data.map(data => (data.user,1)).keyBy(_._1)
      .window(TumblingEventTimeWindows.of(Time.seconds(5)))
      .reduce((state,data)=>(data._1,data._2+state._2)).print("reduceLmd")

    data.map(data => (data.user, 1)).keyBy(_._1)
      .window(TumblingEventTimeWindows.of(Time.seconds(5)))
      .reduce(new ReduceFunction[(String, Int)] {
        override def reduce(t: (String, Int), t1: (String, Int)): (String, Int) = (t._1,t._2+t1._2)
      }).print("reduceFunction")

    val data2 = env.addSource(new ClickSource)
      .assignAscendingTimestamps(_.timestamp) //升序数据时间戳提取
      .keyBy(data => "kry").window(TumblingEventTimeWindows.of(Time.seconds(10)))
    //TODO 增量聚合函数 Aggregate(统计PV,UV,输出PV/UV)
      data2
      .aggregate(new AggregateFunction[source.Event,(Long,Set[String]),Double] {
        override def createAccumulator(): (Long, Set[String]) = (0L,Set[String]())
        override def add(in: source.Event, acc: (Long, Set[String])): (Long, Set[String]) = (acc._1+1,acc._2+in.user)
        override def getResult(acc: (Long, Set[String])): Double = acc._1.toDouble/acc._2.size
        override def merge(acc: (Long, Set[String]), acc1: (Long, Set[String])): (Long, Set[String]) = ???
      }).print("aggregate")

    //TODO 全窗口函数 windowFunction(apply)(批处理)
    data2.apply(new WindowFunction[source.Event,String,String,TimeWindow] {
      override def apply(key: String, window: TimeWindow, input: Iterable[source.Event], out: Collector[String]): Unit = {
        var userSet=Set[String]()
        input.foreach(userSet+=_.user)
        val uv=userSet.size
        val windowStart=window.getStart
        val windowEnd=window.getEnd
        out.collect(s"窗口 $windowStart ~ $windowEnd 的UV值为 $uv")
      }
    }).print("windowFunction")

    data2.apply((key,window,input,out:Collector[String])=>{
      var userSet = Set[String]()
      input.foreach(userSet += _.user)
      val uv = userSet.size
      val windowStart = window.getStart
      val windowEnd = window.getEnd
      out.collect(s"窗口 $windowStart ~ $windowEnd 的UV值为 $uv")
    })

    //TODO 全窗口函数 processWindowFunction(process)
    data2.process(new ProcessWindowFunction[source.Event,String,String,TimeWindow] {
      override def process(key: String, context: Context, elements: Iterable[source.Event], out: Collector[String]): Unit = {
        var userSet = Set[String]()
        elements.foreach(userSet += _.user)
        val uv = userSet.size
        val windowStart = context.window.getStart
        val windowEnd = context.window.getEnd
        out.collect(s"窗口 $windowStart ~ $windowEnd 的UV值为 $uv")
      }
    }).print("processWindowFunction")

    //TODO 增量聚合函数与券窗口函数的结合使用:分组计算PV
    data2.aggregate(new AggregateFunction[source.Event,Long,Long] {
      override def createAccumulator(): Long = 0L
      override def add(in: source.Event, acc: Long): Long = acc+1
      override def getResult(acc: Long): Long = acc
      override def merge(acc: Long, acc1: Long): Long = ???
    },new ProcessWindowFunction[Long,UrlViewCount,String,TimeWindow] {
      override def process(key: String, context: Context, elements: Iterable[Long], out: Collector[UrlViewCount]): Unit = {
        out.collect(UrlViewCount(key,
          elements.iterator.next(),
          new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(context.window.getStart),
          new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(context.window.getEnd)
        ))
      }
    }).print("windowFunctionPlusMax")
    env.execute("windowFunction")
  }

}

这段代码是一个使用 Apache Flink 进行窗口计算的示例,展示了如何使用不同的窗口函数和聚合函数来处理流数据。以下是代码各部分的详细解释以及相关原理知识的拓展。


代码各部分详细解释

1. 包导入与数据模型定义
package window

import java.text.SimpleDateFormat

import org.apache.flink.api.common.eventtime.{SerializableTimestampAssigner, WatermarkStrategy}
import org.apache.flink.api.common.functions.{AggregateFunction, ReduceFunction}
import org.apache.flink.streaming.api.scala._
import org.apache.flink.streaming.api.scala.function.{ProcessWindowFunction, WindowFunction}
import org.apache.flink.streaming.api.windowing.assigners.{EventTimeSessionWindows, SlidingEventTimeWindows, TumblingEventTimeWindows, 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
import source.ClickSource

case class UrlViewCount(url: String, count: Long, widowStart: String, windowEnd: String)
  • 包导入:引入了 Flink 相关的各种类和接口,包括窗口分配器、时间语义、聚合函数、窗口函数等。
  • UrlViewCount 样例类:定义了一个数据模型,用于存储 URL 的访问次数、窗口开始时间和结束时间。
2. 执行环境设置
val env = StreamExecutionEnvironment.getExecutionEnvironment
env.setParallelism(1)
  • 创建执行环境:获取 Flink 的流执行环境。
  • 设置并行度:将任务的并行度设置为 1,方便调试和观察结果。
3. 数据源与时间戳分配
val data = env.addSource(new ClickSource)
  .assignTimestampsAndWatermarks(WatermarkStrategy.forMonotonousTimestamps()
    .withTimestampAssigner(new SerializableTimestampAssigner[source.Event] {
      override def extractTimestamp(t: source.Event, l: Long): Long = t.timestamp
    }))
  • 添加数据源:从自定义的 ClickSource 中获取数据。
  • 分配时间戳和水位线:使用 WatermarkStrategy.forMonotonousTimestamps() 为单调递增的时间戳生成水位线,并通过 withTimestampAssigner 从事件中提取时间戳。
4. 增量聚合函数 Reduce
data.map(data => (data.user, 1)).keyBy(_._1)
  .window(TumblingEventTimeWindows.of(Time.seconds(5)))
  .reduce((state, data) => (data._1, data._2 + state._2)).print("reduceLmd")

data.map(data => (data.user, 1)).keyBy(_._1)
  .window(TumblingEventTimeWindows.of(Time.seconds(5)))
  .reduce(new ReduceFunction[(String, Int)] {
    override def reduce(t: (String, Int), t1: (String, Int)): (String, Int) = (t._1, t._2 + t1._2)
  }).print("reduceFunction")
  • 第一种方式:使用匿名函数进行增量聚合,计算每个用户在 5 秒滚动窗口内的点击次数。
  • 第二种方式:实现 ReduceFunction 接口进行增量聚合,功能与第一种方式相同。
5. 增量聚合函数 Aggregate
val data2 = env.addSource(new ClickSource)
  .assignAscendingTimestamps(_.timestamp)
  .keyBy(data => "kry").window(TumblingEventTimeWindows.of(Time.seconds(10)))

data2
  .aggregate(new AggregateFunction[source.Event, (Long, Set[String]), Double] {
    override def createAccumulator(): (Long, Set[String]) = (0L, Set[String]())
    override def add(in: source.Event, acc: (Long, Set[String])): (Long, Set[String]) = (acc._1 + 1, acc._2 + in.user)
    override def getResult(acc: (Long, Set[String])): Double = acc._1.toDouble / acc._2.size
    override def merge(acc: (Long, Set[String]), acc1: (Long, Set[String])): (Long, Set[String]) =???
  }).print("aggregate")
  • 数据源与窗口设置:从 ClickSource 获取数据,设置升序时间戳,按 "kry" 分组,使用 10 秒滚动窗口。
  • 聚合函数:计算每个窗口内的平均 UV(Unique Visitor)。
6. 全窗口函数 WindowFunction
data2.apply(new WindowFunction[source.Event, String, String, TimeWindow] {
  override def apply(key: String, window: TimeWindow, input: Iterable[source.Event], out: Collector[String]): Unit = {
    var userSet = Set[String]()
    input.foreach(userSet += _.user)
    val uv = userSet.size
    val windowStart = window.getStart
    val windowEnd = window.getEnd
    out.collect(s"窗口 $windowStart ~ $windowEnd 的 UV 值为 $uv")
  }
}).print("windowFunction")

data2.apply((key, window, input, out: Collector[String]) => {
  var userSet = Set[String]()
  input.foreach(userSet += _.user)
  val uv = userSet.size
  val windowStart = window.getStart
  val windowEnd = window.getEnd
  out.collect(s"窗口 $windowStart ~ $windowEnd 的 UV 值为 $uv")
})
  • 第一种方式:实现 WindowFunction 接口,计算每个窗口内的 UV 值并输出。
  • 第二种方式:使用匿名函数实现相同的功能。
7. 全窗口函数 ProcessWindowFunction
data2.process(new ProcessWindowFunction[source.Event, String, String, TimeWindow] {
  override def process(key: String, context: Context, elements: Iterable[source.Event], out: Collector[String]): Unit = {
    var userSet = Set[String]()
    elements.foreach(userSet += _.user)
    val uv = userSet.size
    val windowStart = context.window.getStart
    val windowEnd = context.window.getEnd
    out.collect(s"窗口 $windowStart ~ $windowEnd 的 UV 值为 $uv")
  }
}).print("processWindowFunction")
  • 实现 ProcessWindowFunction:计算每个窗口内的 UV 值并输出,与 WindowFunction 类似,但提供了更多的上下文信息。
8. 增量聚合函数与全窗口函数的结合使用
data2.aggregate(new AggregateFunction[source.Event, Long, Long] {
  override def createAccumulator(): Long = 0L
  override def add(in: source.Event, acc: Long): Long = acc + 1
  override def getResult(acc: Long): Long = acc
  override def merge(acc: Long, acc1: Long): Long =???
}, new ProcessWindowFunction[Long, UrlViewCount, String, TimeWindow] {
  override def process(key: String, context: Context, elements: Iterable[Long], out: Collector[UrlViewCount]): Unit = {
    out.collect(UrlViewCount(key,
      elements.iterator.next(),
      new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(context.window.getStart),
      new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(context.window.getEnd)
    ))
  }
}).print("windowFunctionPlusMax")
  • 增量聚合:计算每个窗口内的点击次数。
  • 全窗口函数:将点击次数、窗口开始时间和结束时间封装成 UrlViewCount 对象并输出。

代码原理知识背景拓展

1. 窗口计算基础

在流处理中,窗口计算是一种常用的技术,用于对一段时间内的数据进行聚合操作。Flink 提供了多种窗口类型,包括滚动窗口(Tumbling Windows)、滑动窗口(Sliding Windows)和会话窗口(Session Windows)。

2. 增量聚合函数
  • ReduceFunction:对窗口内的数据进行逐步聚合,每次处理一个新元素时更新聚合结果。
  • AggregateFunction:更灵活的增量聚合函数,允许定义初始值、累加器和合并逻辑。
3. 全窗口函数
  • WindowFunction:在窗口关闭时对整个窗口的数据进行处理,适用于需要访问整个窗口数据的场景。
  • ProcessWindowFunction:与 WindowFunction 类似,但提供了更多的上下文信息,如当前窗口的开始时间、结束时间和水位线等。
4. 时间语义

Flink 支持三种时间语义:事件时间(Event Time)、摄入时间(Ingestion Time)和处理时间(Processing Time)。事件时间是指事件实际发生的时间,通过水位线(Watermark)来处理乱序数据。

5. 水位线(Watermark)

水位线是 Flink 中处理乱序数据的关键机制,它表示一个时间戳,在这个时间戳之前的数据都已经到达。水位线的生成可以是单调递增的,也可以是有界乱序的。

6. 窗口分配器

Flink 提供了多种窗口分配器,用于将数据流划分为不同的窗口。滚动窗口是固定大小的窗口,滑动窗口可以重叠,会话窗口根据事件之间的时间间隔来划分。

7. 状态管理

在窗口计算中,Flink 需要管理窗口的状态,包括聚合结果、时间戳等。状态管理对于保证计算的准确性和容错性非常重要。

8. 容错机制

Flink 提供了强大的容错机制,通过检查点(Checkpoint)和重启策略来保证在故障发生时能够恢复计算。

通过以上解释和拓展,你应该对 Flink 的窗口计算有了更深入的理解。希望这段代码示例和原理知识对你有所帮助。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

渣渣盟

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值