Flink學習筆記-Window簡單示例

Window
Flink中Window可以將無限流切分成有限流,是處理有限流的核心組件,現在Flink中 Window可以是時間驅動的Time Window,也可以是數據驅動的Count Window。
基於時間的窗口操作:在每個相同的時間間隔對Stream中的記錄進行處理,通常各個時間間隔內的窗口操作處理的記錄數不固定。
基於數據驅動的窗口操作:可以在Stream中選擇固定數量的記錄作爲一個窗口,對該窗口中的記錄進行處理。

窗口類型
Tumbling Window(滾動窗口):窗口間的元素無重複
一個翻滾窗口分配器的每個數據元分配給指定的窗口的窗口大小。翻滾窗具有固定的尺寸,不重疊。
Sliding Window(滑動窗口):窗口間的元素可能重複
該滑動窗口分配器分配元件以固定長度的窗口。與翻滾窗口分配器類似,窗口大小由窗口大小參數配置。附加的窗口滑動參數控制滑動窗口的啓動頻率。因此,如果幻燈片小於窗口大小,則滑動窗口可以重疊。在這種情況下,數據元被分配給多個窗口。
Session Window(會話窗口)
在會話窗口中按活動會話分配器組中的數據元。與翻滾窗口和滑動窗口相比,會話窗口不重疊並且沒有固定的開始和結束時間。相反,當會話窗口在一段時間內沒有接收到數據元時,即當發生不活動的間隙時,會關閉會話窗口。會話窗口分配器可以配置靜態會話間隙或會話間隙提取器函數,該函數定義不活動時間段的長度。當此期限到期時,當前會話將關閉,後續數據元將分配給新的會話窗口。
Global Window(全局窗口)
一個全局性的窗口分配器分配使用相同的Keys相同的單個的所有數據元全局窗口。此窗口方案僅在您還指定自定義觸發器時纔有用。否則,將不執行任何計算,因爲全局窗口沒有我們可以處理聚合數據元的自然結束。

Window Function在窗口觸發後,負責對窗口內的元素進行計算。
Window Function分爲兩類: 增量聚合和全量聚合。
增量聚合: 窗口不維護原始數據,只維護中間結果,每次基於中間結果和增量數據進行聚合。如: ReduceFunction、AggregateFunction等。
全量聚合: 窗口需要維護全部原始數據,窗口觸發進行全量聚合。如:ProcessWindowFunction。

Time的分類
Event-Time :事件時間是每個事件在其生產設備上發生的時間。
Ingestion-Time :攝取時間是事件進入Flink的時間。
Processing-Time : 處理時間是指執行相應算子操作的機器的系統時間。
不設置Time 類型,默認是processingTime。可以如下方式修改時間特性:
env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime);

Watermark
主要解決延遲數據、亂序問題
Watermark是Apache Flink爲了處理EventTime窗口計算提出的一種機制,本質上也是一種時間戳,由Apache Flink Source或者自定義的Watermark生成器按照需求Punctuated或者Periodic兩種方式生成的一種系統Event,與普通數據流Event一樣流轉到對應的下游算子,接收到Watermark Event的算子以此不斷調整自己管理的EventTime Clock。

Watermark兩種生成方式:
Periodic - 週期性(一定時間間隔或者達到一定的記錄條數)產生一個Watermark,默認週期爲200毫秒。在實際的生產中Periodic的方式必須結合時間和積累條數兩個維度繼續週期性產生Watermark,否則在極端情況下會有很大的延時。
接口 AssignerWithPeriodicWatermarks

Punctuated:數據流中每一個遞增的EventTime都會產生一個Watermark。沒有時間週期規律,可打斷的生成Watermark。在實際的生產中Punctuated方式在TPS很高的場景下會產生大量的Watermark在一定程度上對下游算子造成壓力,所以只有在實時性要求非常高的場景纔會選擇Punctuated的方式進行Watermark的生成。
接口 AssignerWithPunctuatedWatermarks

在基於Event-Time的流處理應用中,每個數據有兩個必需的信息:
時間戳:事件發生的時間
Watermark:算子通過Watermark推斷當前的事件時間。Watermark用於通知算子沒有比水位更小的時間戳的事件會發生了。
基於時間的窗口會根據事件時間將一個數據分配給某個窗口。每個時間窗口都有一個開始時間戳和結束時間戳。所有內置的窗口分配器都會提供一個默認的觸發器,一旦時間超過某個窗口的結束時間,觸發器就會觸發對這個窗口的計算。

API簡單示例

import java.sql.Timestamp
import java.text.SimpleDateFormat
import java.util.Properties

import org.apache.flink.api.common.functions.AggregateFunction
import org.apache.flink.api.common.serialization.SimpleStringSchema
import org.apache.flink.api.common.state.{ListState, ListStateDescriptor}
import org.apache.flink.configuration.Configuration
import org.apache.flink.streaming.api.TimeCharacteristic
import org.apache.flink.streaming.api.functions.timestamps.BoundedOutOfOrdernessTimestampExtractor
import org.apache.flink.streaming.api.functions.{AssignerWithPeriodicWatermarks, AssignerWithPunctuatedWatermarks, KeyedProcessFunction}
import org.apache.flink.streaming.api.scala._
import org.apache.flink.streaming.api.scala.function.WindowFunction
import org.apache.flink.streaming.api.watermark.Watermark
import org.apache.flink.streaming.api.windowing.time.Time
import org.apache.flink.streaming.api.windowing.windows.TimeWindow
import org.apache.flink.streaming.connectors.kafka.FlinkKafkaConsumer
import org.apache.flink.util.Collector

import scala.collection.mutable.ListBuffer

case class UserBehavior(userId:Long, itemId:Long, behavior:String, timestamp:Long)
case class ItemCount(itemId:Long, count:Long, timestamp:Long)

object TopNHotItemsGenerator {

  def main(args: Array[String]): Unit = {
    val env = StreamExecutionEnvironment.getExecutionEnvironment
    env.setParallelism(1)
    // 設置水位間隔,默認200毫秒
    env.getConfig.setAutoWatermarkInterval(100)
    // 設置時間特性
    env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)

    val dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss")

    val properties = new Properties()
    properties.setProperty("bootstrap.servers", "192.168.0.1:9092")
    properties.setProperty("group.id", "tmp-1")
    properties.setProperty("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer")
    properties.setProperty("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer")
    properties.setProperty("auto.offset.reset", "latest")
    val dataStream = env.addSource(new FlinkKafkaConsumer[String]("tmp1", new SimpleStringSchema(), properties))
      .map(data => {
        val dataArray = data.split(",")
        UserBehavior(dataArray(0).trim().toLong, dataArray(1).trim().toLong,
          dataArray(2).trim(), dateFormat.parse(dataArray(3).trim()).getTime)
      })
      .assignAscendingTimestamps(_.timestamp)  // 數據中提取時間戳
      /**  定義水位生成方式
      .assignTimestampsAndWatermarks(new BoundedOutOfOrdernessTimestampExtractor[UserBehavior](Time.seconds(1)) {
        override def extractTimestamp(t: UserBehavior): Long = {
          t.timestamp
        }
      })
      .assignAscendingTimestamps(new PeriodicWatermarksAssigner()) 
      .assignAscendingTimestamps(new PunctuatedWatermarksAssigner()) 
      */

    dataStream.print()

    val aggregateStream = dataStream.filter(_.behavior == "click").keyBy(_.itemId)
      .timeWindow(Time.hours(1), Time.minutes(5))
      .aggregate(new CountAggregate(), new WindowAggregateResult()) // 窗口聚合

    aggregateStream.print()

    val processStream = aggregateStream.keyBy(_.timestamp)
      .process(new TopNHotItemsProcessFunction(2))

    processStream.print()

    env.execute("topn hot items")

  }

}

class CountAggregate() extends AggregateFunction[UserBehavior, Long, Long] {

  override def createAccumulator(): Long = 0L

  override def add(in: UserBehavior, acc: Long): Long = acc + 1

  override def getResult(acc: Long): Long = acc

  override def merge(acc: Long, acc1: Long): Long = acc + acc1

}

class AvgAggregate() extends AggregateFunction[UserBehavior, (Long, Long), Double] {

  override def createAccumulator(): (Long, Long) = (0L, 0L)

  override def add(in: UserBehavior, acc: (Long, Long)): (Long, Long) = (acc._1 + in.timestamp, acc._2 + 1)

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

  override def merge(acc: (Long, Long), acc1: (Long, Long)): (Long, Long) = (acc._1 + acc1._1, acc._2 + acc1._2)

}

class WindowAggregateResult() extends WindowFunction[Long, ItemCount, Long, TimeWindow] {

  override def apply(key: Long, window: TimeWindow, input: Iterable[Long], out: Collector[ItemCount]): Unit = {
    out.collect(ItemCount(key, input.iterator.next(), window.getEnd))
  }

}

class TopNHotItemsProcessFunction(topN: Int) extends KeyedProcessFunction[Long, ItemCount, String] {

  var itemsState:ListState[ItemCount] = _

  override def open(parameters: Configuration): Unit = {
    super.open(parameters)
    itemsState = getRuntimeContext.getListState(new ListStateDescriptor[ItemCount]("hot-items-state", classOf[ItemCount]))
  }

  override def processElement(value: ItemCount, context: KeyedProcessFunction[Long, ItemCount, String]#Context, collector: Collector[String]): Unit = {
    itemsState.add(value)
    // 註冊事件時間定時器
    context.timerService().registerEventTimeTimer(value.timestamp + 1)
  }

  override def onTimer(timestamp: Long, ctx: KeyedProcessFunction[Long, ItemCount, String]#OnTimerContext, out: Collector[String]): Unit = {
    val items:ListBuffer[ItemCount] = new ListBuffer[ItemCount]()
    import scala.collection.JavaConversions._
    for (item <- itemsState.get()) {
      items += item
    }
    val topNHotItems = items.sortBy(_.count)(Ordering.Long.reverse).take(topN)

    val result: StringBuilder = new StringBuilder()
    result.append("time: ").append(new Timestamp(timestamp - 1)).append("\n")
    for (i <- topNHotItems.indices) {
      val hotItem = topNHotItems(i)
      result.append("top%d: itemId %d count %d".format(i + 1, hotItem.itemId, hotItem.count)).append("\n")
    }
    result.append("====================")

    out.collect(result.toString())

    itemsState.clear()
  }

}


class PeriodicWatermarksAssigner extends AssignerWithPeriodicWatermarks[UserBehavior] {

  val bound: Long = 60000; //延時一分鐘

  var maxTs: Long = Long.MinValue //最大時間戳

  override def getCurrentWatermark: Watermark = {
    new Watermark(maxTs - bound)
  }

  override def extractTimestamp(element: UserBehavior, l: Long): Long = {
    maxTs = maxTs.max(element.timestamp)
    element.timestamp
  }

}

class PunctuatedWatermarksAssigner extends AssignerWithPunctuatedWatermarks[UserBehavior] {

  val bound: Long = 60000; //延時一分鐘

  override def checkAndGetNextWatermark(element: UserBehavior, l: Long): Watermark = {
    if (element.behavior == "") {
      new Watermark(l - bound)
    } else {
      null
    }
  }

  override def extractTimestamp(element: UserBehavior, l: Long): Long = element.timestamp

}

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章