每隔5秒,输出最近10分钟内访问量最多的前N个URL(考虑迟到数据,迟到数据的三重保障,重点是分析!!!)

代码

package com.zjc.flow_analysis


import org.apache.flink.api.common.functions.AggregateFunction
import org.apache.flink.api.common.state.{ListStateDescriptor, MapState, MapStateDescriptor}
import org.apache.flink.api.java.tuple.Tuple
import org.apache.flink.streaming.api.TimeCharacteristic
import org.apache.flink.streaming.api.functions.KeyedProcessFunction
import org.apache.flink.streaming.api.functions.timestamps.BoundedOutOfOrdernessTimestampExtractor
import org.apache.flink.streaming.api.scala.function.WindowFunction
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 java.lang
import scala.collection.JavaConversions.bufferAsJavaList
import scala.collection.mutable.ListBuffer
import org.apache.flink.streaming.api.scala._

import java.sql.Timestamp
import java.text.SimpleDateFormat

case class LogEvent(ip: String, userId: String, eventTime: Long, method: String, url: String)
case class UrlViewCount(url: String, windowEnd: Long, count: Long)

/**
 * 每隔5秒,输出最近10分钟内访问量最多的前N个URL
 */
object FlowTopNPage {
  def main(args: Array[String]): Unit = {
    val env = StreamExecutionEnvironment.getExecutionEnvironment
    env.setParallelism(1)
    env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)
    // nc -lk 7777
    val streamData = env.socketTextStream("hadoop103", 7777)

    // 将数据封装成样例类,并设置watermark,延迟时间最大1分钟
    val dataStream = streamData.map(data => {
      val dataArray = data.split(" ")
      val sf = new SimpleDateFormat("dd/MM/yyyy:HH:mm:ss")
      val timeStamp = sf.parse(dataArray(3)).getTime

      LogEvent(dataArray(0), dataArray(1), timeStamp, dataArray(5), dataArray(6))
      // 保证1,延迟时间1s
    }).assignTimestampsAndWatermarks(new BoundedOutOfOrdernessTimestampExtractor[LogEvent](Time.seconds(1)) {
      override def extractTimestamp(t: LogEvent): Long = t.eventTime
    })
    val lateOutputTag = new OutputTag[LogEvent]("late data")
    //开窗聚合
    val aggData = dataStream.filter( data => {
      val pattern = "^((?!\\.(ico|png|css|js)$).)*$".r
      (pattern findFirstIn data.url).nonEmpty
    } )
      .keyBy(_.url).timeWindow(Time.minutes(10), Time.seconds(5))
      .allowedLateness(Time.minutes(1)) // 保证2,延迟1分钟
      .sideOutputLateData(new OutputTag[LogEvent]("late data"))// 保证3,侧输出流
      .aggregate(new PageCountAgg(), new PageCountWindowResult())

    val lateDataStream = aggData.getSideOutput(lateOutputTag)

    //每个窗口的统计值排序输出
    val resultStream = aggData.keyBy("windowEnd").process(new TopNPage(3))

    dataStream.print("data")
    aggData.print("agg")
    lateDataStream.print("late")
    resultStream.print("result")
    env.execute("top N page job.")
  }
}

class PageCountAgg() extends AggregateFunction[LogEvent, Long, Long] {
  override def createAccumulator(): Long = 0L

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

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

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

/**
 * 自定义windowFunction包装成样例类输出,如果是 .keyBy("url")则第三参数是Tuple
如果是.keyBy(data=>data.url)或 .keyBy(_.url)则第三个参数是String
 */
class PageCountWindowResult() extends WindowFunction[Long, UrlViewCount, String, TimeWindow] {
  override def apply(key: String, window: TimeWindow, input: Iterable[Long], out: Collector[UrlViewCount]): Unit = {

    out.collect(UrlViewCount(key, window.getEnd, input.head))
  }
}

class TopNPage(n: Int) extends KeyedProcessFunction[Tuple, UrlViewCount, String] {
  // 定义ListState保存所以的聚合结果
  lazy val PageCountMapState: MapState[String, Long] = getRuntimeContext.getMapState(
    new MapStateDescriptor[String, Long]("PageCountMapState", classOf[String], classOf[Long]))

  override def processElement(i: UrlViewCount, context: KeyedProcessFunction[Tuple, UrlViewCount, String]#Context,
                              collector: Collector[String]): Unit = {
    PageCountMapState.put(i.url, i.count)
    context.timerService().registerEventTimeTimer(i.windowEnd + 100)
    context.timerService().registerEventTimeTimer(i.windowEnd + 60 * 1000L)
  }

  override def onTimer(timestamp: Long, ctx: KeyedProcessFunction[Tuple, UrlViewCount, String]#OnTimerContext, out: Collector[String]): Unit = {
    if (timestamp == ctx.timestamp() + 60 * 1000L) {
      PageCountMapState.clear()
      return
    }
    val pcListState :ListBuffer[(String, Long)] = ListBuffer()
    val iter = PageCountMapState.entries().iterator()
    while (iter.hasNext) {
      val entry = iter.next()
      pcListState += ((entry.getKey, entry.getValue))
    }

//    PageCountListState.clear()
    val sortedPCList = pcListState.sortWith(_._2 > _._2).take(n)
    // 或
//    val sortedPCList1 = pcListState.sortBy(_.count)(Ordering.Long.reverse).take(n)
    // 将排名输出显示
    val result = new StringBuilder()
    result.append("时间:").append(new Timestamp(timestamp - 100)).append("\n")
    //遍历输出
    for (i <- sortedPCList.indices) {
      val curPCCount = sortedPCList.get(i)
      result.append("Top").append(i + 1).append(":")
      result.append("当前url:").append(curPCCount._1)
      result.append(",当前访问量:").append(curPCCount._2)
      result.append("\n")
    }
    result.append("--------------------------------------------\n")
    //控制输出频率
    Thread.sleep(1000L)
    out.collect(result.toString())
  }
}

说明:

数据延迟的三重保证,保证一,设置watermark,延迟1s;保证二,设置允许迟到1分钟的数据;保证三,将迟到数据超过1分钟的放入侧输出流中。

启动:

启动上面的程序,然后在hadoop103输入:nc -lk 7777。启动后,控制台等待hadoop103这边的输入的数据。

数据的准备:

83.149.9.216 - - 17/05/2015:10:25:49 +0000 GET /presentations/
83.149.9.216 - - 17/05/2015:10:25:50 +0000 GET /presentations/
83.149.9.216 - - 17/05/2015:10:25:51 +0000 GET /presentations/
83.149.9.216 - - 17/05/2015:10:25:52 +0000 GET /presentations/
83.149.9.216 - - 17/05/2015:10:25:46 +0000 GET /presentations/
83.149.9.216 - - 17/05/2015:10:25:53 +0000 GET /presentations/
83.149.9.216 - - 17/05/2015:10:25:46 +0000 GET /present
83.149.9.216 - - 17/05/2015:10:25:31 +0000 GET /presentations/
83.149.9.216 - - 17/05/2015:10:25:23 +0000 GET /presentations/
83.149.9.216 - - 17/05/2015:10:25:23 +0000 GET /present
83.149.9.216 - - 17/05/2015:10:25:54 +0000 GET /presentations/
83.149.9.216 - - 17/05/2015:10:26:52 +0000 GET /presentations/
83.149.9.216 - - 17/05/2015:10:25:49 +0000 GET /presentations/
83.149.9.216 - - 17/05/2015:10:15:51 +0000 GET /presentations/

分析

分别输入:
83.149.9.216 - - 17/05/2015:10:25:49 +0000 GET /presentations/
83.149.9.216 - - 17/05/2015:10:25:50 +0000 GET /presentations/
则,控制台分别显示:
在这里插入图片描述
原因:因为我们的窗口长是1分钟,每5秒滑动一次,所以在10:25:50时候关闭窗口,但这里没有关闭是因为我们的第一重保证设置的1s的延迟,所以要在10:25:51秒时候会关闭窗口。
再次输入:
83.149.9.216 - - 17/05/2015:10:25:51 +0000 GET /presentations/
则,控制台分别显示:
在这里插入图片描述
原因:因为来的数据是10:25:51,到达了1s的延迟,所以窗口会被关闭,因此看见了上面agg的输出,但这里没有见到排序后的结果,是因为我们注册的事件是在原来窗口的右边界(上界)时间戳的基础上增加了100毫秒的延迟,因此看不见排序的结果。
再次输入:
83.149.9.216 - - 17/05/2015:10:25:52 +0000 GET /presentations/
则,控制台分别显示:
在这里插入图片描述
原因:因为这条数据的时间是10:25:52,所以到了触发定时器的时间,因此可以看见排序的结果,访问量1次,因为统计的时间范围是[10:15:50,10:25:50),在这个时间范围确实只要10:25:49秒这条数据。
再次输入:
83.149.9.216 - - 17/05/2015:10:25:46 +0000 GET /presentations/
则,控制台分别显示:
在这里插入图片描述
原因:10:25:46数据迟到了,属于它的窗口是[10:15:50,10:25:50),但已经过了,所以它会在原来窗口数据上count值增加1,没有输出result是因为,10:25:46数据输入后注册了一个10:25:50+100毫秒的定时器,但定时器没有更新,是因为定时器的触发是根据watermark更新来触发的。
再次输入:
83.149.9.216 - - 17/05/2015:10:25:53 +0000 GET /presentations/
则,控制台分别显示:
在这里插入图片描述
原因:10:25:53数据来,使得wartermark更新,触发10:25:50+100毫秒的定时器,因此输出10:25:50的result数据。
再次输入:
83.149.9.216 - - 17/05/2015:10:25:46 +0000 GET /present
则,控制台分别显示:
在这里插入图片描述
原因:10:25:46数据是与之前的key不同,但这时候属于它的窗口是[10:15:50,10:25:50),但已经过了,所以它直接输出agg。
再次输入:
83.149.9.216 - - 17/05/2015:10:25:31 +0000 GET /presentations/
则,控制台在原来基础上多显示:
在这里插入图片描述
原因:多输出的四条UrlViewCount是因为10:25:31的数据,对于它们来讲是迟到数据,直接在原来基础上输出。没有看到比10:25:55窗口大的输出,是因为当前输入的数据最大时间是10:25:53的,还没到下一个窗口。
result没输出还是因为wartermark没更新。
再次输入:
83.149.9.216 - - 17/05/2015:10:25:23 +0000 GET /presentations/
则,控制台在原来基础上多显示:
在这里插入图片描述
原因:与上面的思路完全相同。
再次输入:
83.149.9.216 - - 17/05/2015:10:25:23 +0000 GET /present
则,控制台在原来基础上多显示:
在这里插入图片描述
原因:与上面的思路完全相同,只是key不同而已。
再次输入:
83.149.9.216 - - 17/05/2015:10:25:54 +0000 GET /presentations/
则,控制台在原来基础上多显示:

data> LogEvent(83.149.9.216,-,1431829554000,GET,/presentations/)
result> 时间:2015-05-17 10:25:25.0
Top1:当前url:/present,当前访问量:1
Top2:当前url:/presentations/,当前访问量:1
--------------------------------------------

result> 时间:2015-05-17 10:25:30.0
Top1:当前url:/present,当前访问量:1
Top2:当前url:/presentations/,当前访问量:1
--------------------------------------------

result> 时间:2015-05-17 10:25:35.0
Top1:当前url:/presentations/,当前访问量:2
Top2:当前url:/present,当前访问量:1
--------------------------------------------

result> 时间:2015-05-17 10:25:40.0
Top1:当前url:/presentations/,当前访问量:2
Top2:当前url:/present,当前访问量:1
--------------------------------------------

result> 时间:2015-05-17 10:25:45.0
Top1:当前url:/presentations/,当前访问量:2
Top2:当前url:/present,当前访问量:1
--------------------------------------------

result> 时间:2015-05-17 10:25:50.0
Top1:当前url:/presentations/,当前访问量:4
Top2:当前url:/present,当前访问量:2
--------------------------------------------

原因:10:25:54的数据是最新的数据,因此水位线更新,触发了大量的定时间,因此可以看到上面大量的result输出,从10:25:25到10:25:50的各种窗口。
再次输入:
83.149.9.216 - - 17/05/2015:10:26:52 +0000 GET /presentations/
则,控制台在原来基础上多显示:

data> LogEvent(83.149.9.216,-,1431829612000,GET,/presentations/)
agg> UrlViewCount(/presentations/,1431829555000,9)
agg> UrlViewCount(/present,1431829555000,2)
agg> UrlViewCount(/presentations/,1431829560000,9)
agg> UrlViewCount(/present,1431829560000,2)
agg> UrlViewCount(/presentations/,1431829565000,9)
agg> UrlViewCount(/present,1431829565000,2)
agg> UrlViewCount(/presentations/,1431829570000,9)
agg> UrlViewCount(/present,1431829570000,2)
agg> UrlViewCount(/present,1431829575000,2)
agg> UrlViewCount(/presentations/,1431829575000,9)
agg> UrlViewCount(/present,1431829580000,2)
agg> UrlViewCount(/presentations/,1431829580000,9)
agg> UrlViewCount(/present,1431829585000,2)
agg> UrlViewCount(/presentations/,1431829585000,9)
agg> UrlViewCount(/presentations/,1431829590000,9)
agg> UrlViewCount(/present,1431829590000,2)
agg> UrlViewCount(/presentations/,1431829595000,9)
agg> UrlViewCount(/present,1431829595000,2)
agg> UrlViewCount(/presentations/,1431829600000,9)
agg> UrlViewCount(/present,1431829600000,2)
agg> UrlViewCount(/presentations/,1431829605000,9)
agg> UrlViewCount(/present,1431829605000,2)
agg> UrlViewCount(/presentations/,1431829610000,9)
agg> UrlViewCount(/present,1431829610000,2)
result> 时间:2015-05-17 10:25:55.0
Top1:当前url:/presentations/,当前访问量:9
Top2:当前url:/present,当前访问量:2
--------------------------------------------

result> 时间:2015-05-17 10:26:00.0
Top1:当前url:/presentations/,当前访问量:9
Top2:当前url:/present,当前访问量:2
--------------------------------------------

result> 时间:2015-05-17 10:26:05.0
Top1:当前url:/presentations/,当前访问量:9
Top2:当前url:/present,当前访问量:2
--------------------------------------------

result> 时间:2015-05-17 10:26:10.0
Top1:当前url:/presentations/,当前访问量:9
Top2:当前url:/present,当前访问量:2
--------------------------------------------

result> 时间:2015-05-17 10:26:15.0
Top1:当前url:/presentations/,当前访问量:9
Top2:当前url:/present,当前访问量:2
--------------------------------------------

result> 时间:2015-05-17 10:26:20.0
Top1:当前url:/presentations/,当前访问量:9
Top2:当前url:/present,当前访问量:2
--------------------------------------------

result> 时间:2015-05-17 10:26:24.9
Top1:当前url:/present,当前访问量:1
Top2:当前url:/presentations/,当前访问量:1
--------------------------------------------

result> 时间:2015-05-17 10:26:25.0
Top1:当前url:/presentations/,当前访问量:9
Top2:当前url:/present,当前访问量:2
--------------------------------------------

result> 时间:2015-05-17 10:26:29.9
Top1:当前url:/present,当前访问量:1
Top2:当前url:/presentations/,当前访问量:1
--------------------------------------------

result> 时间:2015-05-17 10:26:30.0
Top1:当前url:/presentations/,当前访问量:9
Top2:当前url:/present,当前访问量:2
--------------------------------------------

result> 时间:2015-05-17 10:26:34.9
Top1:当前url:/presentations/,当前访问量:2
Top2:当前url:/present,当前访问量:1
--------------------------------------------

result> 时间:2015-05-17 10:26:35.0
Top1:当前url:/presentations/,当前访问量:9
Top2:当前url:/present,当前访问量:2
--------------------------------------------

result> 时间:2015-05-17 10:26:39.9
Top1:当前url:/presentations/,当前访问量:2
Top2:当前url:/present,当前访问量:1
--------------------------------------------

result> 时间:2015-05-17 10:26:40.0
Top1:当前url:/presentations/,当前访问量:9
Top2:当前url:/present,当前访问量:2
--------------------------------------------

result> 时间:2015-05-17 10:26:44.9
Top1:当前url:/presentations/,当前访问量:2
Top2:当前url:/present,当前访问量:1
--------------------------------------------

result> 时间:2015-05-17 10:26:45.0
Top1:当前url:/presentations/,当前访问量:9
Top2:当前url:/present,当前访问量:2
--------------------------------------------

result> 时间:2015-05-17 10:26:49.9
Top1:当前url:/presentations/,当前访问量:4
Top2:当前url:/present,当前访问量:2
--------------------------------------------

result> 时间:2015-05-17 10:26:50.0
Top1:当前url:/presentations/,当前访问量:9
Top2:当前url:/present,当前访问量:2
--------------------------------------------

原因:10:26:52的数据到了,之前的数据窗口都关闭了(第一重保证,设的1s延迟),第二重保证,设1分钟延迟,所以10:25:55到10:26:50之间的窗口,输出agg,又因为10:26:52是最新的时间,会触发大量的定时器,因此输出大量的result。(有疑问的地方,后几条result输出有疑惑?为什么差0.1s)
再次输入:
83.149.9.216 - - 17/05/2015:10:25:49 +0000 GET /presentations/
则,控制台在原来基础上多显示:
在这里插入图片描述
原因:属于的窗口,但已经关闭的,直接输出agg。
再次输入:
83.149.9.216 - - 17/05/2015:10:15:51 +0000 GET /presentations/
则,控制台在原来基础上多显示:
在这里插入图片描述
原因:迟到超过10分钟了,放到了侧输出流中。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值