Flink 认为 Batch 是 Streaming 的一个特例,所以 Flink 底层引擎是一个流式引擎,在上面实现了流处理和批处理。而窗口(window)就是从 Streaming 到 Batch 的一个桥梁。Flink 提供了非常完善的窗口机制。

什么是window

在流式数据中,数据是连续的。有时我们需要根据业务做一些聚合类的操作,例如过去五分钟内用户浏览量的计算。这五分钟就是一个窗口。
窗口可以由时间或者数量来做区分
1.根据时间进行截取,比如每10分钟统计一次
2.根据消息数量进行统计,比如每100个数据统计一次

时间窗口

时间窗口又分为滚动窗口,滑动窗口,和会话窗口

滚动窗口:

时间对齐,窗口长度固定,没有重叠

flink 远程调试 flink keyby原理_流处理


如图:以固定的长度进行分割,比如一个网站统计每分钟的浏览量,BI的pv,uv统计

滑动窗口

时间对齐,窗口长度固定,有重叠,展现的是数据的变化趋势

flink 远程调试 flink keyby原理_流处理_02


如图:窗口大小为4,步长为2,每隔两秒统计仅4s的数据

适用于 股票的实时波动情况等

会话窗口

flink 远程调试 flink keyby原理_flink_03

当流中达到多长时间没有新的数据到来,上一个会话窗口就是截至到新数据到来前接收到的最后一条数据,当新数据到来后,上一个窗口将会关闭,开启一个新的窗口。
常见于,web程序session的统计

代码实现

常见分类:keyed window和Non-keyed window
二者区分是否跟于keyBy之后

CountWindow实现
object CountWindow {
  def main(args: Array[String]): Unit = {
    val environment: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment
    val dataStream: DataStream[String] = environment.socketTextStream("192.168.23.171", 8989, '\n')
    //None_keyed Stream
//    dataStream.flatMap(_.split(" "))
//      .map((_,1))
//      .countWindowAll(10)
//      .sum(1)
//      .print()
    //keyed Stream
    dataStream.flatMap(_.split(" "))
      .map((_,1))
      .keyBy(0)
      .countWindow(10)
      .sum(1)
      .print()
    environment.execute("CountWindowStream")
  }
}

滚动窗口实现

object TubmlingWindow {
  def main(args: Array[String]): Unit = {
    val environment: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment
    val dataStream: DataStream[String] = environment.socketTextStream("node01", 8989, '\n')
    // Non-keyed 分组
//    dataStream.flatMap(_.split(" "))
//      .map((_, 1))
//      .timeWindowAll(Time.seconds(5))
//        .windowAll(TumblingProcessingTimeWindows.of(Time.seconds(5)))
//      .sum(1)
//      .print()
// keyed Stream
    dataStream.flatMap(_.split(" "))
      .map((_,1))
      .keyBy(0)
//      .timeWindow(Time.seconds(5))
      .window(TumblingProcessingTimeWindows.of(Time.seconds(5)))
      .sum(1)
      .print()
    environment.execute("StreamTubmlingWindow")
  }
}

滑动窗口实现

object SlindingWindow {
  def main(args: Array[String]): Unit = {
    val environment: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment
    val dataStream: DataStream[String] = environment.socketTextStream("node01", 8989, '\n')
    // Non-keyed 分组
//        dataStream.flatMap(_.split(" "))
//          .map((_, 1))
          .timeWindowAll(Time.seconds(10),Time.seconds(5))
//          .windowAll(SlidingProcessingTimeWindows.of(Time.seconds(10),Time.seconds(5)))
//          .sum(1)
//          .print()
    dataStream.flatMap(_.split(" "))
      .map((_,1))
      .keyBy(0)
//      .timeWindow(Time.seconds(10),Time.seconds(5))
      .window(SlidingProcessingTimeWindows.of(Time.seconds(10),Time.seconds(5)))
      .sum(1)
      .print()
    environment.execute("StreamSlidingWindow")
  }
}

会话窗口实现

object SessionWindow {
  def main(args: Array[String]): Unit = {
    val environment: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment
    val dataStream: DataStream[String] = environment.socketTextStream("node01", 8989, '\n')
    // Non-keyed 分组
    //              dataStream.flatMap(_.split(" "))
    //                .map((_, 1))
    //                .windowAll(ProcessingTimeSessionWindows.withGap(Time.seconds(5)))
    //                .sum(1)
    //                .print()ronment.socketTextStream("node01", 8989, '\n')
    dataStream.flatMap(_.split(" "))
      .map((_,1))
      .keyBy(0)
      .window(EventTimeSessionWindows.withGap(Time.seconds(5)))
      .sum(1)
      .print()
    environment.execute("StreamSessionWindow")
  }
}

Flink的水印机制

Flink流处理时间方式

  • EventTime[事件时间]
    事件发生的时间,例如:点击网站上的某个链接的时间
  • IngestionTime[进入时间]
    某个Flink节点的source operator接收到数据的时间,例如:某个source消费到kafka中的数据
  • ProcessingTime[处理时间]
    某个Flink节点执行某个operation的时间,例如:timeWindow接收到数据的时间
    设置Flink流处理的时间类型
// 设置为按照事件时间来进行计算
env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)
// 设置为按照处理时间来进行计算
env.setStreamTimeCharacteristic(TimeCharacteristic.ProcessingTime)

比如一个10分钟一个滚动窗口,统计每个时间段访问人数

10:00:00到10:00:00 为一个窗口

假设10:09:59产生了一条数据,但是因为网络延迟,进入flink已经10:10:03那么该数据该如何归类,所以此时应该使用Eventtime

使用水印可以解决这类网络延迟问题,可以理解水印就是一个时间戳,每接收处理一个消息都会添加水印

水印影响原有事件时间,当数据流添加水印后,会按照水印时间来触发窗口统计计算,当接收到的水印时间大于等于窗口的endTime,触发计算。

flink 远程调试 flink keyby原理_flink_04