Flink模拟项目: 实时流量统计
3.1 模块创建和数据准备
在UserBehaviorAnalysis下新建一个 maven module作为子项目,命名为NetworkTrafficAnalysis。在这个子模块中,我们同样并没有引入更多的依赖,所以也不需要改动pom文件。
在src/main/目录下,将默认源文件目录java改名为scala。将apache服务器的日志文件apache.log复制到资源文件目录src/main/resources下,我们将从这里读取数据
3.2 代码实现
我们现在要实现的模块是 “实时流量统计”。对于一个电商平台而言,用户登录的入口流量、不同页面的访问流量都是值得分析的重要数据,而这些数据,可以简单地从web服务器的日志中提取出来。我们在这里实现最基本的“页面浏览数”的统计,也就是读取服务器日志中的每一行log,统计在一段时间内用户访问url的次数。
具体做法为:每隔5秒,输出最近10分钟内访问量最多的前N个URL。可以看出,这个需求与之前“实时热门商品统计”非常类似,所以我们完全可以借鉴此前的代码。
在src/main/scala下创建TrafficAnalysis.scala文件,新建一个单例对象。定义样例类ApacheLogEvent,这是输入的日志数据流;另外还有UrlViewCount,这是窗口操作统计的输出数据类型。在main函数中创建StreamExecutionEnvironment 并做配置,然后从apache.log文件中读取数据,并包装成ApacheLogEvent类型。
需要注意的是,原始日志中的时间是“dd/MM/yyyy:HH:mm:ss”的形式,需要定义一个DateTimeFormat将其转换为我们需要的时间戳格式:
.map(line => { val linearray = line.split(" ") val sdf = new SimpleDateFormat("dd/MM/yyyy:HH:mm:ss") val timestamp = sdf.parse(linearray(3)).getTime ApacheLogEvent(linearray(0), linearray(2), timestamp, linearray(5), linearray(6)) })
完整代码如下:
case class ApacheLogEvent(ip: String, userId: String, eventTime: Long, method: String, url: String) case class UrlViewCount(url: String, windowEnd: Long, count: Long) object TrafficAnalysis { def main(args: Array[String]): Unit = { val env = StreamExecutionEnvironment.getExecutionEnvironment env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime) env.setParallelism(1) val stream = env // 以window下为例,需替换成自己的路径 .readTextFile("YOUR_PATH\\resources\\apache.log") .map(line => { val linearray = line.split(" ") val simpleDateFormat = new SimpleDateFormat("dd/MM/yyyy:HH:mm:ss") val timestamp = simpleDateFormat.parse(linearray(3)).getTime ApacheLogEvent(linearray(0), linearray(2), timestamp, linearray(5), linearray(6)) }) .assignTimestampsAndWatermarks(new BoundedOutOfOrdernessTimestampExtractor[ApacheLogEvent] (Time.milliseconds(1000)) { override def extractTimestamp(t: ApacheLogEvent): Long = { t.eventTime } }) .keyBy("url") .timeWindow(Time.minutes(10), Time.seconds(5)) .aggregate(new CountAgg(), new WindowResultFunction()) .keyBy(1) .process(new TopNHotUrls(5)) .print() env.execute("Traffic Analysis Job") } class CountAgg extends AggregateFunction[ApacheLogEvent, Long, Long] { override def createAccumulator(): Long = 0L override def add(apacheLogEvent: ApacheLogEvent, acc: Long): Long = acc + 1 override def getResult(acc: Long): Long = acc override def merge(acc1: Long, acc2: Long): Long = acc1 + acc2 } class WindowResultFunction extends WindowFunction[Long, UrlViewCount, Tuple, TimeWindow] { override def apply(key: Tuple, window: TimeWindow, aggregateResult: Iterable[Long], collector: Collector[UrlViewCount]) : Unit = { val url: String = key.asInstanceOf[Tuple1[String]].f0 val count = aggregateResult.iterator.next collector.collect(UrlViewCount(url, window.getEnd, count)) } } class TopNHotUrls(topsize: Int) extends KeyedProcessFunction[Tuple, UrlViewCount, String] { private var urlState : ListState[UrlViewCount] = _ override def open(parameters: Configuration): Unit = { super.open(parameters) val urlStateDesc = new ListStateDescriptor[UrlViewCount]("urlState-state", classOf[UrlViewCount]) urlState = getRuntimeContext.getListState(urlStateDesc) } override def processElement(input: UrlViewCount, context: KeyedProcessFunction[Tuple, UrlViewCount, String]#Context, collector: Collector[String]): Unit = { // 每条数据都保存到状态中 urlState.add(input) context.timerService.registerEventTimeTimer(input.windowEnd + 1) } override def onTimer(timestamp: Long, ctx: KeyedProcessFunction[Tuple, UrlViewCount, String]#OnTimerContext, out: Collector[String]): Unit = { // 获取收到的所有URL访问量 val allUrlViews: ListBuffer[UrlViewCount] = ListBuffer() import scala.collection.JavaConversions._ for (urlView <- urlState.get) { allUrlViews += urlView } // 提前清除状态中的数据,释放空间 urlState.clear() // 按照访问量从大到小排序 val sortedUrlViews = allUrlViews.sortBy(_.count)(Ordering.Long.reverse) .take(topSize) // 将排名信息格式化成 String, 便于打印 var result: StringBuilder = new StringBuilder result.append("====================================\n") result.append("时间: ").append(new Timestamp(timestamp - 1)).append("\n") for (i <- sortedUrlViews.indices) { val currentUrlView: UrlViewCount = sortedUrlViews(i) // e.g. No1: URL=/blog/tags/firefox?flav=rss20 流量=55 result.append("No").append(i+1).append(":") .append(" URL=").append(currentUrlView.url) .append(" 流量=").append(currentUrlView.count).append("\n") } result.append("====================================\n\n") // 控制输出频率,模拟实时滚动结果 Thread.sleep(1000) out.collect(result.toString) } } }