代码
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分钟了,放到了侧输出流中。