目录
任务描述
本关任务:编写一个能计算排行榜并实时输出的小程序。
相关知识
为了完成本关任务,你需要掌握:如何计算排行榜并实时输出。
计算排行榜并实时输出
对于实时数据,我们之前进行了基本的计算,但是我们还要用 Flink 的方式来实时获得数据和实时输出数据,以及用 Flink 的方式来计算实时数据。
代码及详细解释:
//输入数据
case class UserBehaviorL1( userId: Long, itemId: Long, categoryId: Int, behavior: String, timestamp: Long )
//输出数据
case class ItemViewCountL1( itemId: Long, windowEnd: Long, count: Long )
object LiLun1 {
def main(args: Array[String]): Unit = {
val properties = new Properties()
//flink的流执行环境
val env = StreamExecutionEnvironment.getExecutionEnvironment
//设定Time类型为EventTime
env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)
//并发为1
env.setParallelism(1)
//获得数据源的文件
val stream = env
.readTextFile("D:\\MyProjects\\UserBehaviorL1Analysis\\HotItemsAnalysis\\src\\main\\resources\\UserBehavior1.csv")
//获得文件中的每一行记录
.map(line => {
val linearray = line.split(",")
UserBehaviorL1( linearray(0).toLong, linearray(1).toLong, linearray(2).toInt, linearray(3), linearray(4).toLong )
})
//指定时间戳
.assignAscendingTimestamps(_.timestamp * 1000)
//进行过滤,只获得pv行为的数据
.filter(_.behavior == "pv")
//按照商品id进行分流
.keyBy("itemId")
//滑动窗口,每隔5分钟,获得之前1小时窗口中的数据
.timeWindow(Time.hours(1), Time.minutes(5))
//预聚合,第一个参数:求和,第二个参数:每个商品在每个窗口的点击量
.aggregate( new CountAgg(), new WindowResultFunction() )
// 因为之前已经按商品id分流,这里再按窗口分流,
// 所以会获得同一窗口的不同商品的数据,
// 便于后面将同一窗口的不同商品的数据进行浏览次数统计,来获得最热门的商品
.keyBy("windowEnd")
//在自定义函数中进行业务逻辑处理
.process( new TopNHotItems(2))
.print()
env.execute("Hot Items Job")
}
//统计出总数
class CountAgg extends AggregateFunction[UserBehaviorL1, Long, Long]{
override def add(value: UserBehaviorL1, accumulator: Long): Long = accumulator + 1
override def createAccumulator(): Long = 0L
override def getResult(accumulator: Long): Long = accumulator
override def merge(a: Long, b: Long): Long = a + b
}
//获得某个商品在一个窗口中的总数
class WindowResultFunction extends WindowFunction[Long, ItemViewCountL1, Tuple, TimeWindow]{
override def apply(key: Tuple, window: TimeWindow, input: Iterable[Long], out: Collector[ItemViewCountL1]): Unit = {
val itemId: Long = key.asInstanceOf[Tuple1[Long]].f0
val count = input.iterator.next()
out.collect(ItemViewCountL1(itemId, window.getEnd, count))
}
}
//求某个窗口中前N名的热门点击商品
class TopNHotItems(topSize: Int) extends KeyedProcessFunction[Tuple, ItemViewCountL1, String]{
//ListState用于保存状态
lazy val itemState: ListState[ItemViewCountL1] = getRuntimeContext
.getListState( new ListStateDescriptor[ItemViewCountL1]( "itemState", classOf[ItemViewCountL1] ) )
//注册定时器
override def processElement(i: ItemViewCountL1, context: KeyedProcessFunction[Tuple, ItemViewCountL1, String]#Context, collector: Collector[String]): Unit = {
//添加本窗口中的所有数据
itemState.add(i)
//windowEnd+1ms作为关闭窗口的触发时间
context.timerService.registerEventTimeTimer( i.windowEnd + 1 )
}
//到了windowEnd+1时间后,执行onTimer方法
override def onTimer(timestamp: Long, ctx: KeyedProcessFunction[Tuple, ItemViewCountL1, String]#OnTimerContext, out: Collector[String]): Unit = {
val allItems: ListBuffer[ItemViewCountL1] = ListBuffer()
import scala.collection.JavaConversions._
//将ListStae对象的数据存入到ListBuffer对象
for(item <- itemState.get){
allItems += item
}
//清除状态中的数据
itemState.clear()
//按count进行降序排序,获得前3名的数据
val sortedItems = allItems.sortBy(_.count)(Ordering.Long.reverse).take(topSize)
//格式化,打印
val result: StringBuilder = new StringBuilder
result.append("====================================\n")
result.append("时间:").append(new Timestamp(timestamp - 1)).append("\n")
for( i <- sortedItems.indices ){
val currentItem: ItemViewCountL1 = sortedItems(i)
result.append("No").append(i+1).append(":")
.append(" 商品ID=").append(currentItem.itemId)
.append(" 浏览量=").append(currentItem.count).append("\n")
}
result.append("====================================\n\n")
out.collect(result.toString)
// 终止程序,只输出一次
sys.exit()
//控制输出频率,模拟实时滚动
// Thread.sleep(1000)
}
}
}
编程要求
根据提示,在右侧编辑器补充代码,计算排行榜并实时输出,
要求每 30 秒计算之前 2 分钟的时间窗口数据, 对其中的浏览商品行为(即行为类型为 pv)进行浏览次数的计算,并输出浏览次数排名前 3 的商品结果。
数据格式如下表:
用户id | 商品id | 商品类目id | 行为类型 | 时间戳 |
---|---|---|---|---|
543462 | 1715 | 1464116 | pv | 1511658000 |
以逗号进行分隔。
测试说明
平台会对你编写的代码进行测试:
测试输入:电商数据日志文件; 预期输出:
====================================
时间:2017-11-26 01:00:30.0
No1: 商品ID=1715 浏览量=3
No2: 商品ID=2244074 浏览量=2
No3: 商品ID=5140156 浏览量=1
====================================
====================================
时间:2017-11-26 01:01:00.0
No1: 商品ID=1715 浏览量=3
No2: 商品ID=2244074 浏览量=2
No3: 商品ID=5140156 浏览量=1
====================================
====================================
时间:2017-11-26 01:01:30.0
No1: 商品ID=1715 浏览量=3
No2: 商品ID=2244074 浏览量=2
No3: 商品ID=5140156 浏览量=1
====================================
====================================
时间:2017-11-26 01:02:00.0
No1: 商品ID=1715 浏览量=3
No2: 商品ID=2244074 浏览量=2
No3: 商品ID=5140156 浏览量=1
====================================
参考答案
import java.sql.Timestamp
import java.util.Properties
import org.apache.flink.api.common.functions.AggregateFunction
import org.apache.flink.api.common.state.{ListState, ListStateDescriptor}
import org.apache.flink.api.java.io.TextInputFormat
import org.apache.flink.api.java.tuple.{Tuple, Tuple1}
import org.apache.flink.streaming.api.TimeCharacteristic
import org.apache.flink.streaming.api.functions.KeyedProcessFunction
import org.apache.flink.streaming.api.scala._
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 scala.collection.mutable.ListBuffer
//输入数据
case class UserBehaviorA1( userId: Long, itemId: Long, categoryId: Int, behavior: String, timestamp: Long )
//输出数据
case class ItemViewCountA1( itemId: Long, windowEnd: Long, count: Long )
object Test1 {
def main(args: Array[String]): Unit = {
val properties = new Properties()
//flink的流执行环境
val env = StreamExecutionEnvironment.getExecutionEnvironment
//设定Time类型为EventTime
env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)
//并发为1
env.setParallelism(1)
//获得数据源的文件
val stream = env.readFile[String](
new TextInputFormat(null),
"/data/workspace/myshixun/pflinkhotitems4_1/src/UserBehavior1.csv")
//获得文件中的每一行记录
val stream1 = stream.map(line => {
val linearray = line.split(",")
UserBehaviorA1( linearray(0).toLong, linearray(1).toLong, linearray(2).toInt, linearray(3), linearray(4).toLong )
})
//指定时间戳
.assignAscendingTimestamps(_.timestamp * 1000)
//进行过滤,只获得pv行为的数据
.filter(_.behavior == "pv")
//按照商品id进行分流
.keyBy("itemId")
//滑动窗口,每隔30秒,获得之前2分钟时间窗口中的数据
// 可以根据具体需求来设置此时间,
// 比如:每隔5分钟获得之前1小时时间窗口中的数据的代码如下
// .timeWindow(Time.hours(1), Time.minutes(5))
.timeWindow(Time.minutes(2), Time.seconds(30))
//预聚合,第一个参数:求和,第二个参数:每个商品在每个窗口的点击量
.aggregate( new CountAgg(), new WindowResultFunction() )
// 因为之前已经按商品id分流,这里再按窗口分流,
// 所以会获得同一窗口的不同商品的数据,
// 便于后面将同一窗口的不同商品的数据进行浏览次数统计,来获得最热门的商品
.keyBy("windowEnd")
//在自定义函数中进行业务逻辑处理
.process( new TopNHotItems(3))
stream1.writeAsText("/root/files/result.txt").setParallelism(1)
env.execute("计算排行榜并实时输出")
}
//统计出总数
class CountAgg extends AggregateFunction[UserBehaviorA1, Long, Long]{
override def add(value: UserBehaviorA1, accumulator: Long): Long = accumulator + 1
override def createAccumulator(): Long = 0L
override def getResult(accumulator: Long): Long = accumulator
override def merge(a: Long, b: Long): Long = a + b
}
//获得某个商品在一个窗口中的总数
class WindowResultFunction extends WindowFunction[Long, ItemViewCountA1, Tuple, TimeWindow]{
override def apply(key: Tuple, window: TimeWindow, input: Iterable[Long], out: Collector[ItemViewCountA1]): Unit = {
val itemId: Long = key.asInstanceOf[Tuple1[Long]].f0
val count = input.iterator.next()
out.collect(ItemViewCountA1(itemId, window.getEnd, count))
}
}
//求某个窗口中前N名的热门点击商品
class TopNHotItems(topSize: Int) extends KeyedProcessFunction[Tuple, ItemViewCountA1, String]{
//ListState用于保存状态
lazy val itemState: ListState[ItemViewCountA1] = getRuntimeContext
.getListState( new ListStateDescriptor[ItemViewCountA1]( "itemState", classOf[ItemViewCountA1] ) )
//注册定时器
override def processElement(i: ItemViewCountA1, context: KeyedProcessFunction[Tuple, ItemViewCountA1, String]#Context, collector: Collector[String]): Unit = {
//添加本窗口中的所有数据
itemState.add(i)
//windowEnd+1ms作为关闭窗口的触发时间
context.timerService.registerEventTimeTimer( i.windowEnd + 1 )
}
//到了windowEnd+1时间后,执行onTimer方法
override def onTimer(timestamp: Long, ctx: KeyedProcessFunction[Tuple, ItemViewCountA1, String]#OnTimerContext, out: Collector[String]): Unit = {
val allItems: ListBuffer[ItemViewCountA1] = ListBuffer()
import scala.collection.JavaConversions._
//将ListStae对象的数据存入到ListBuffer对象
for(item <- itemState.get){
allItems += item
}
//清除状态中的数据
itemState.clear()
//********** Begin **********
//按count进行降序排序,获得前3名的数据
val sortedItems = allItems.sortBy(_.count)(Ordering.Long.reverse).take(topSize)
//********** End **********
//格式化,打印
val result: StringBuilder = new StringBuilder
result.append("====================================\n")
result.append("时间:").append(new Timestamp(timestamp - 1)).append("\n")
for( i <- sortedItems.indices ){
val currentItem: ItemViewCountA1 = sortedItems(i)
result.append("No").append(i+1).append(":")
.append(" 商品ID=").append(currentItem.itemId)
.append(" 浏览量=").append(currentItem.count).append("\n")
}
result.append("====================================\n\n")
out.collect(result.toString)
// 终止程序,只输出一次
//sys.exit()
//控制输出频率,实时滚动,会每隔1秒就输出一个结果
Thread.sleep(1000)
}
}
}