flink windows 窗口函数的使用

本文详细介绍了Flink中的窗口函数,包括ReduceFunction、AggregateFunction和ProcessWindowFunction等,阐述了这些函数如何帮助实现数据流的窗口聚合操作,并提供了具体的应用案例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Windows Function

ReduceFunction

ReduceFunction指定如何组合输入中的两个元素以生成相同类型的输出元素。Flink使用ReduceFunction递增聚合窗口的元素。
e.g

输入类型要和输出类型一致

val input: DataStream[(String, Long)] = ...

input
    .keyBy(<key selector>)
    .window(<window assigner>)
    .reduce { (v1, v2) => (v1._1, v1._2 + v2._2) }

AggregateFunction

AggregateFunction是ReduceFunction的通用版本,它有三种类型:输入类型(IN)、累加器类型(ACC)和输出类型(OUT)。输入类型是输入流中元素的类型,AggregateFunction有一个将一个输入元素添加到累加器的方法。该接口还有用于创建初始累加器、将两个累加器合并为一个累加器以及从累加器提取输出(OUT类型)的方法。我们将在下面的例子中看到它是如何工作的。
与ReduceFunction一样,Flink将在窗口的输入元素到达时递增地聚合它们。

/**
 * The accumulator is used to keep a running sum and a count. The [getResult] method
 * computes the average.
 */
class AverageAggregate extends AggregateFunction[(String, Long), (Long, Long), Double] {
  override def createAccumulator() = (0L, 0L)

  override def add(value: (String, Long), accumulator: (Long, Long)) =
    (accumulator._1 + value._2, accumulator._2 + 1L)

  override def getResult(accumulator: (Long, Long)) = accumulator._1 / accumulator._2

  override def merge(a: (Long, Long), b: (Long, Long)) =
    (a._1 + b._1, a._2 + b._2)
}

val input: DataStream[(String, Long)] = ...

input
    .keyBy(<key selector>)
    .window(<window assigner>)
    .aggregate(new AverageAggregate)

ProcessWindowFunction

ProcessWindowFunction获得一个包含所有窗口元素的Iterable,以及一个能够访问时间和状态信息的Context 上下文对象的接口,这使它能够比其他窗口函数提供更多的灵活性。这是以性能和资源消耗为代价的,因为元素不能增量地聚合,而是需要在内部缓冲,直到窗口被认为可以处理为止。
注意:key参数是通过为keyBy()调用指定的KeySelector提取的键。在元索引键或字符串字段引用的情况下,此键类型总是Tuple,您必须手动将其转换为正确大小的元组来提取键字段。

abstract class ProcessWindowFunction[IN, OUT, KEY, W <: Window] extends Function {

  /**
    * Evaluates the window and outputs none or several elements.
    *
    * @param key      The key for which this window is evaluated.
    * @param context  The context in which the window is being evaluated.
    * @param elements The elements in the window being evaluated.
    * @param out      A collector for emitting elements.
    * @throws Exception The function may throw exceptions to fail the program and trigger recovery.
    */
  def process(
      key: KEY,
      context: Context,
      elements: Iterable[IN],
      out: Collector[OUT])

  /**
    * The context holding window metadata
    */
  abstract class Context {
    /**
      * Returns the window that is being evaluated.
      * 返回正在计算的窗口。
      */
    def window: W

    /**
      * Returns the current processing time.
      * 返回当前处理时间。
      */
    def currentProcessingTime: Long

    /**
      * Returns the current event-time watermark.
      * 返回当前事件时间水印
      */
    def currentWatermark: Long

    /**
      * State accessor for per-key and per-window state.
      * 每个键全局状态的状态访问器。
      */
    def windowState: KeyedStateStore

    /**
      * State accessor for per-key global state.
      */
    def globalState: KeyedStateStore
  }

}
val input: DataStream[(String, Long)] = ...

input
  .keyBy(_._1)
  .window(TumblingEventTimeWindows.of(Time.minutes(5)))
  .process(new MyProcessWindowFunction())

/* ... */

class MyProcessWindowFunction extends ProcessWindowFunction[(String, Long), String, String, TimeWindow] {

  def process(key: String, context: Context, input: Iterable[(String, Long)], out: Collector[String]) = {
    var count = 0L
    for (in <- input) {
      count = count + 1
    }
    out.collect(s"Window ${context.window} count: $count")
  }
}

这个例子显示了一个ProcessWindowFunction,它对窗口中的元素进行计数。此外,window函数将窗口的信息添加到输出中。

注意:使用ProcessWindowFunction进行简单的聚合(如计数)是非常低效的。下一节将展示如何将ReduceFunction或AggregateFunction与ProcessWindowFunction结合以获得增量聚合和添加ProcessWindowFunction的信息。

ProcessWindowFunction with Incremental Aggregation(处理窗口函数和增加聚合函数结合)

ProcessWindowFunction可以与ReduceFunction或AggregateFunction组合在一起,以在元素到达窗口时递增地聚合元素。当窗口关闭时,将向ProcessWindowFunction提供聚合的结果。这允许它增量地计算窗口,同时访问ProcessWindowFunction的附加窗口元信息。
注意, 您也可以使用以前的WindowFunction代替ProcessWindowFunction来进行增量窗口聚合。 但是windowFunction 没有Context 不能截取上下文

Incremental Window Aggregation with ReduceFunction(ReduceFuntione 结合 增长聚合窗口)

val input: DataStream[SensorReading] = ...

input
  .keyBy(<key selector>)
  .window(<window assigner>)
  .reduce(
    (r1: SensorReading, r2: SensorReading) => { if (r1.value > r2.value) r2 else r1 },
    ( key: String,
      context: ProcessWindowFunction[_, _, _, TimeWindow]#Context,
      minReadings: Iterable[SensorReading],
      out: Collector[(Long, SensorReading)] ) =>
      {
        val min = minReadings.iterator.next()
        out.collect((context.window.getStart, min))
      }
  )

Incremental Window Aggregation with AggregateFunction(AggregateFunction 结合增长聚合窗口)

这里的AggregateFunction 使用org.apache.flink.api.common.functions.AggregateFunction 下的如果没有此方法可以在pom 文件中添加

       <dependency>
            <groupId>org.apache.flink</groupId>
            <artifactId>flink-table-common</artifactId>
            <version>${flink.version}</version>
        </dependency>
val input: DataStream[(String, Long)] = ...

input
  .keyBy(<key selector>)
  .window(<window assigner>)
  .aggregate(new AverageAggregate(), new MyProcessWindowFunction())

// Function definitions

/**
 * The accumulator is used to keep a running sum and a count. The [getResult] method
 * computes the average.
 */
class AverageAggregate extends AggregateFunction[(String, Long), (Long, Long), Double] {
  override def createAccumulator() = (0L, 0L)

  override def add(value: (String, Long), accumulator: (Long, Long)) =
    (accumulator._1 + value._2, accumulator._2 + 1L)

  override def getResult(accumulator: (Long, Long)) = accumulator._1 / accumulator._2

  override def merge(a: (Long, Long), b: (Long, Long)) =
    (a._1 + b._1, a._2 + b._2)
}

class MyProcessWindowFunction extends ProcessWindowFunction[Double, (String, Double), String, TimeWindow] {

  def process(key: String, context: Context, averages: Iterable[Double], out: Collector[(String, Double)]) = {
    val average = averages.iterator.next()
    out.collect((key, average))
  }
}

Using per-window state in ProcessWindowFunction

除了访问键控状态(任何rich function都可以)之外,ProcessWindowFunction还可以使用作用域为函数当前正在处理的窗口的键控状态。在这种情况下,理解每个窗口状态所指的是什么窗口是很重要的。这里有不同的“窗口”:

  • 当指定窗口操作时定义的窗口:这可能是滚动1小时的窗口或滑动2小时的窗口,滑动1小时。
  • 给定键的定义窗口的实际实例:这可能是用户id xyz从12:00到13:00的时间窗口。这是基于窗口定义的,并且会有许多窗口是基于作业当前正在处理的键的数量,以及基于事件所处的时间段。

每个窗口的状态与后者相关联。这意味着如果我们为1000个不同的键处理事件,并且所有的事件当前都在[12:00,13:00)时间窗口内,那么将会有1000个窗口实例,每个实例都有自己的键控窗口状态。
process()调用接收到的Context对象上有两种方法允许访问这两种状态:

  • globalState(),它允许访问不在窗口作用域内的键控状态
  • windowState(),它允许访问同样作用域为窗口的键控状态

如果您预期同一个窗口会有多个触发,那么这个特性是有用的,因为当您对延迟到达的数据有延迟触发时,或者当您有一个自定义触发器执行推测性的早期触发时,就会发生这种情况。在这种情况下,您需要在每个窗口状态中存储关于以前的触发或触发次数的信息。
当使用窗口状态时,在窗口被清除时清除该状态也很重要。这应该在clear()方法中发生。

WindowFunction (Legacy)

在一些可以使用ProcessWindowFunction的地方,你也可以使用WindowFunction。这是ProcessWindowFunction的一个旧版本,它提供较少的上下文信息,并且没有一些高级特性,比如每个窗口的键控状态。这个接口将在某个时候被弃用。

trait WindowFunction[IN, OUT, KEY, W <: Window] extends Function with Serializable {

  /**
    * Evaluates the window and outputs none or several elements.
    *
    * @param key    The key for which this window is evaluated.
    * @param window The window that is being evaluated.
    * @param input  The elements in the window being evaluated.
    * @param out    A collector for emitting elements.
    * @throws Exception The function may throw exceptions to fail the program and trigger recovery.
    */
  def apply(key: KEY, window: W, input: Iterable[IN], out: Collector[OUT])
}
val input: DataStream[(String, Long)] = ...

input
    .keyBy(<key selector>)
    .window(<window assigner>)
    .apply(new MyWindowFunction())

Keyed Windows

stream
.keyBy(…) <- keyed versus non-keyed windows
.window(…) <- required: “assigner”
[.trigger(…)] <- optional: “trigger” (else default trigger)
[.evictor(…)] <- optional: “evictor” (else no evictor)
[.allowedLateness(…)] <- optional: “lateness” (else zero)
[.sideOutputLateData(…)] <- optional: “output tag” (else no side output for late data)
.reduce/aggregate/fold/apply() <- required: “function”
[.getSideOutput(…)] <- optional: “output tag”

### Apache Flink 窗口操作详解 #### 定义与功能 Flink 提供了丰富的窗口操作来处理流数据中的时间性和聚合需求。通过定义不同类型的窗口,可以在指定的时间范围内对事件进行分组并执行计算。这使得能够实现诸如每分钟统计点击量、滑动窗口内的平均值等复杂业务逻辑[^1]。 #### 类型划分 主要分为两类:基于时间和基于数量的窗口。具体来说有以下几种常见形式: - **滚动窗口 (Tumbling Window)** 数据被划分为互不重叠的时间区间,在每个固定长度周期结束时触发计算。 - **滑动窗口 (Sliding Window)** 允许设定两个参数——窗口大小和滑动间隔;即使部分重合也会分别独立计算结果。 - **会话窗口 (Session Windows)** 根据活动间隙自动调整边界,适合用于检测用户行为模式或长时间无交互后的重新激活情况。 - **全局窗口 (Global Windows)** 将所有记录收集到一起再做一次性汇总处理,通常配合自定义触发器使用以控制何时输出最终结果。 #### 实现方式 对于上述提到的各种窗口类型,可以通过调用`window()`方法来进行配置,并结合内置或者用户自定义函数完成特定任务。下面是一个简单的例子展示了如何创建一个五分钟滚动窗口并对其中的数据求和: ```java DataStream<Tuple2<String, Integer>> input = ...; input .keyBy(value -> value.f0) // 按照第一个字段作为键分区 .window(TumblingProcessingTimeWindows.of(Time.minutes(5))) // 创建五分钟后关闭的窗口 .sum(1); // 对第二个字段数值相加 ``` 为了更精确地管理状态以及优化性能表现,还可以引入额外的功能组件比如允许迟到数据进入已关闭窗口(`allowedLateness`)或是设置侧输出通道(side output),以便将不符合条件的数据分流出去单独处理。 #### 性能考量 当设计涉及大量并发连接的应用程序时,合理规划资源分配至关重要。考虑到这一点,建议开发者关注以下几个方面: - 调整并行度(parallelism); - 启用增量式检查点(incremental checkpointing)减少恢复成本; - 利用水位线(watermark)机制提高吞吐率的同时保持低延迟特性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值