ubuntu14、flink1.7.2、scala2.11、kafka2.3.0、jdk1.8、idea2019
创建maven项目,命名UserBehaviorAnalysis,其pom内容如下:
4.0.0
com.ustc
UserBehaviorAnalysis
pom
1.0-SNAPSHOT
1.7.2
2.11
2.3.0
HotItemsAnalysis
org.apache.flink
flink-scala_${scala.binary.version}
${flink.version}
org.apache.flink
flink-streaming-scala_${scala.binary.version}
${flink.version}
org.apache.kafka
kafka_${scala.binary.version}
${kafka.version}
org.apache.flink
flink-connector-kafka_${scala.binary.version}
${flink.version}
net.alchim31.maven
scala-maven-plugin
3.4.6
testCompile
org.apache.maven.plugins
maven-assembly-plugin
3.0.0
jar-with-dependencies
make-assembly
package
single
在该项目中创建子模块(右击->module),取名 HotItemsAnalysis,其pom内容(保持默认内容即可):
UserBehaviorAnalysis
com.ustc
1.0-SNAPSHOT
4.0.0
HotItemsAnalysis
在 HotItemsAnalysis子模块中,将Java包名改为scala,并添加Object命名HotItems(右击scala文件夹->new->Object),HotItems.scala内容如下:
import java.sql.Timestamp
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.api.java.tuple.{Tuple, Tuple1}
import org.apache.flink.configuration.Configuration
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.streaming.connectors.kafka.FlinkKafkaConsumer
import org.apache.flink.util.Collector
import scala.collection.mutable.ListBuffer
//输入样例类
case class UserBehavior(userId:Long,itemId:Long,categoryId:Int,behavior:String,timestamp:Long)
case class ItemViewCount(itemId:Long,windowEnd:Long,count:Long)
object HotItems {
def main(args: Array[String]): Unit = {
//1. 创建执行环境
val env=StreamExecutionEnvironment.getExecutionEnvironment
//指定Time类型为EventTime
env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)
//并发度(slot)
env.setParallelism(1)
//2. 读取数据
//2.1 kafka数据源
val properties=new Properties()
properties.setProperty("bootstrap.servers","localhost:9092") //访问地址及端口号
properties.setProperty("group.id","consumer-group")
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]("hotitems",new SimpleStringSchema(),properties))
//2.2 文件数据源
//val dataStream=env.readTextFile("")
.map(line=>{
val linearray=line.split(",")
UserBehavior(linearray(0).trim.toLong,linearray(1).trim.toLong,linearray(2).trim.toInt,linearray(3).trim,linearray(4).trim.toLong)
})
//抽取时间戳和生成watermark(由于数据源的每条数据的时间戳是单调递增的,所有将每条数据的业务时间当做watermark,若是乱序数据,需要使用BoundedOutOfOrdernessTimestampExtractor)
.assignAscendingTimestamps(_.timestamp*1000L) //时间戳转为ms单位
// 3. 转换处理数据
val processedStream=dataStream
.filter(_.behavior=="pv") //过滤点击事件
.keyBy(_.itemId) //对商品分组,分成多个并行流
.timeWindow( Time.minutes(60),Time.minutes(5) ) //5分钟一个窗口(若后面没有延迟,则会5分钟输出一个窗口)
.aggregate(new CountAgg(),new WindowResult())
.keyBy(_.windowEnd) //按窗口结束时间分组
.process(new TopNHotItems(3))
//4. sink,控制台输出
processedStream.print()
env.execute("Hot Items Job")
}
}
//自定义预聚合函数(累加器),适合简单聚合
class CountAgg() extends AggregateFunction[UserBehavior,Long,Long]{
override def add(in: UserBehavior, acc: Long): Long = acc+1
//累加器初始值为0
override def createAccumulator(): Long = 0L
override def getResult(acc: Long): Long = acc
override def merge(acc: Long, acc1: Long): Long = acc+acc1
}
//自定义预聚合函数计算平均数
/**
* 输入类型:UserBehavior
* 中间聚合状态类型:(Long,Int) ->Long:时间戳和,Int:个数
* 输出类型:Double
*/
class AverageAgg() extends AggregateFunction[UserBehavior,(Long,Int),Double]{
//当前输入值:in ,中间状态:acc
override def add(in: UserBehavior, acc: (Long, Int)): (Long, Int) = (acc._1+in.timestamp,acc._2+1)
override def createAccumulator(): (Long, Int) = (0L,0)
override def getResult(acc: (Long, Int)): Double = acc._1/acc._2
//合并两个累加器,仍是中间状态的改变
override def merge(acc: (Long, Int), acc1: (Long, Int)): (Long, Int) = (acc._1+acc1._1,acc._2+acc1._2)
}
//自定义窗口函数,输出ItemViewCount(商品ID、)
/**
* 输入类型:预聚合函数的输出 Long
* 输出类型: ItemViewCount
* key类型: Tuple类型
* Window类型:TimeWindow
*/
class WindowResult() extends WindowFunction[Long,ItemViewCount,Long,TimeWindow]{
override def apply(key: Long, window: TimeWindow, input: Iterable[Long], out: Collector[ItemViewCount]): Unit = {
out.collect(ItemViewCount(key,window.getEnd,input.iterator.next()))
}
}
//自定义的处理函数,
/**
* key类型:Long
* 输入类型:ItemViewCount
* 输出类型:String
* @param topSize
*/
class TopNHotItems(topSize: Int) extends KeyedProcessFunction[Long,ItemViewCount,String]{
private var itemState:ListState[ItemViewCount]= _ //默认空值
override def open(parameters: Configuration): Unit = {
itemState=getRuntimeContext.getListState(new ListStateDescriptor[ItemViewCount]("item-state",classOf[ItemViewCount]))
}
override def processElement(value: ItemViewCount, context: KeyedProcessFunction[Long, ItemViewCount, String]#Context, collector: Collector[String]): Unit = {
//将每条到来的数据存入状态列表
itemState.add(value)
//注册一个定时器
context.timerService().registerEventTimeTimer(value.windowEnd+1) //延迟1s触发
}
//定时器触发时,对所有数据排序,并输出结果
override def onTimer(timestamp: Long, ctx: KeyedProcessFunction[Long, ItemViewCount, String]#OnTimerContext, out: Collector[String]): Unit = {
//将所有state中数据取出放入List Buffer中
val allItems:ListBuffer[ItemViewCount]=new ListBuffer()
//遍历引入转换包
import scala.collection.JavaConversions._
for(item<-itemState.get()){
allItems+=item
}
//按照点击量(count)降序,并取前N
val sortedItems=allItems.sortBy(_.count)(Ordering.Long.reverse).take(topSize)
//清空状态
itemState.clear()
//将排名记过格式化输出
val result:StringBuilder=new StringBuilder()
result.append("时间: ").append(new Timestamp(timestamp-1)) //窗口关闭时间timestamp为定时器触发时间
.append("\n")
//输出每个商品信息
for (i<-0 to sortedItems.length-1){
val currentItem=sortedItems(i)
result.append("No").append(i+1).append(":")
.append(" 商品ID=").append(currentItem.itemId)
.append( "浏览量=").append(currentItem.count)
.append("\n")
}
result.append("=============================")
//控制输出频率
Thread.sleep(1000)
out.collect(result.toString())
}
}
kafka数据源:滑动窗口->5分钟一个窗口结果输出(若后面没有延迟,则会每5分钟输出一次往前一个小时内的统计结果)
cd /usr/local/kafka/
./bin/zookeeper-server-start.sh config/zookeeper.properties
./bin/kafka-server-start.sh config/server.properties
./bin/kafka-console-producer.sh --broker-list localhost:9092 --topic hotitems
测试数据:
543462,1715,1464116,pv,1511658000 ->2017/11/26 9:00:00
662867,2244074,1575622,pv,1511658060 ->2017/11/26 9:01:00
561558,3611281,965809,pv,1511658120 ->2017/11/26 9:02:00
625915,3611281,570735,pv,1511658300 ->2017/11/26 9:05:00
578814,1715,982926,pv,1511658301 ->2017/11/26 9:05:01
578814,1715,982926,pv,1511658330 ->2017/11/26 9:05:30
87335,1256540,1451783,pv,1511658540 ->2017/11/26 9:09:00
429984,2244074,2355072,pv,1511658600 ->2017/11/26 9:10:00
937166,1715,2355072,pv,1511661600 ->2017/11/26 10:00:00
937170,1715,2355072,pv,1511665200 ->2017/11/26 11:00:00
其中时间为时间窗口结束时间,由于时间窗口长度=60mins,滑动距离=5mins,而数据的EventTime作为Watermark,会每隔5mins统计一次,统计事件由到来的数据的EventTime值进行触发。
第一条数据EventTime的时间戳 1511658000 ->2017/11/26 9:00:00 ,若第一条数据的EventTime的时间戳 1511658060 ->2017/11/26 9:01:00,第一个窗口的结束时间仍然为 9:05:00(不包括该时刻的数据),当时间戳为9:05:00的数据到来时,引发窗口[8:05:00,9:05:00)关闭,并对EventTime在该时间段窗口内的数据进行计数统计。
时间: 2017-11-26 09:05:00.0 [08:05:00.0,09:05:00.0) #时间戳为2017/11/26 9:05:00或更晚的数据到来引发该窗口关闭并统计,但由于代码中定时器延迟1s触发,当时间戳为2017/11/26 9:05:01的数据到来时才会在控制台输出统计结果
No1: 商品ID=1715浏览量=1
No2: 商品ID=3611281浏览量=1
No3: 商品ID=2244074浏览量=1
=============================
时间: 2017-11-26 09:10:00.0 [08:10:00.0,09:10:00.0) #时间戳为2017/11/26 9:10:00或更晚的数据到来引发该窗口关闭并统计,但由于代码中定时器延迟1s触发,当时间戳为2017/11/26 9:10:01的数据到来时才会在控制台输出统计结果
No1: 商品ID=1715浏览量=3
No2: 商品ID=3611281浏览量=2
No3: 商品ID=2244074浏览量=1
=============================
时间: 2017-11-26 09:15:00.0 [08:15:00.0,09:15:00.0) #时间戳为2017/11/26 9:15:00或更晚的数据到来引发该窗口关闭并统计,但由于代码中定时器延迟1s触发,当时间戳为2017/11/26 9:15:01的数据到来时才会在控制台输出统计结果
No1: 商品ID=1715浏览量=3
No2: 商品ID=3611281浏览量=2
No3: 商品ID=2244074浏览量=2
=============================
时间: 2017-11-26 09:20:00.0 [08:20:00.0,09:20:00.0) #时间戳为2017/11/26 9:20:00或更晚的数据到来引发该窗口关闭并统计,但由于代码中定时器延迟1s触发,当时间戳为2017/11/26 9:20:01的数据到来时才会在控制台输出统计结果
No1: 商品ID=1715浏览量=3
No2: 商品ID=3611281浏览量=2
No3: 商品ID=2244074浏览量=2
=============================
时间: 2017-11-26 09:25:00.0 [08:25:00.0,09:25:00.0) #时间戳为2017/11/26 9:25:00或更晚的数据到来引发该窗口关闭并统计,但由于代码中定时器延迟1s触发,当时间戳为2017/11/26 9:25:01的数据到来时才会在控制台输出统计结果
No1: 商品ID=1715浏览量=3
No2: 商品ID=3611281浏览量=2
No3: 商品ID=2244074浏览量=2
=============================
时间: 2017-11-26 09:30:00.0 [08:30:00.0,09:30:00.0) #时间戳为2017/11/26 9:30:00或更晚的数据到来引发该窗口关闭并统计,但由于代码中定时器延迟1s触发,当时间戳为2017/11/26 9:30:01的数据到来时才会在控制台输出统计结果
No1: 商品ID=1715浏览量=3
No2: 商品ID=2244074浏览量=2
No3: 商品ID=3611281浏览量=2
=============================
时间: 2017-11-26 09:35:00.0 [08:35:00.0,09:35:00.0) #时间戳为2017/11/26 9:35:00或更晚的数据到来引发该窗口关闭并统计,但由于代码中定时器延迟1s触发,当时间戳为2017/11/26 9:35:01的数据到来时才会在控制台输出统计结果
No1: 商品ID=1715浏览量=3
No2: 商品ID=2244074浏览量=2
No3: 商品ID=3611281浏览量=2
=============================
时间: 2017-11-26 09:40:00.0 [08:40:00.0,09:40:00.0) #时间戳为2017/11/26 9:40:00或更晚的数据到来引发该窗口关闭并统计,但由于代码中定时器延迟1s触发,当时间戳为2017/11/26 9:40:01的数据到来时才会在控制台输出统计结果
No1: 商品ID=1715浏览量=3
No2: 商品ID=2244074浏览量=2
No3: 商品ID=3611281浏览量=2
=============================
时间: 2017-11-26 09:45:00.0 [08:45:00.0,09:45:00.0) #时间戳为2017/11/26 9:45:00或更晚的数据到来引发该窗口关闭并统计,但由于代码中定时器延迟1s触发,当时间戳为2017/11/26 9:45:01的数据到来时才会在控制台输出统计结果
No1: 商品ID=1715浏览量=3
No2: 商品ID=2244074浏览量=2
No3: 商品ID=3611281浏览量=2
=============================
时间: 2017-11-26 09:50:00.0 [08:50:00.0,09:50:00.0) #时间戳为2017/11/26 9:50:00或更晚的数据到来引发该窗口关闭并统计,但由于代码中定时器延迟1s触发,当时间戳为2017/11/26 9:50:01的数据到来时才会在控制台输出统计结果
No1: 商品ID=1715浏览量=3
No2: 商品ID=3611281浏览量=2
No3: 商品ID=2244074浏览量=2
=============================
时间: 2017-11-26 09:55:00.0 [08:55:00.0,09:55:00.0) #时间戳为2017/11/26 9:55:00或更晚的数据到来引发该窗口关闭并统计,但由于代码中定时器延迟1s触发,当时间戳为2017/11/26 9:55:01的数据到来时才会在控制台输出统计结果
No1: 商品ID=1715浏览量=3
No2: 商品ID=3611281浏览量=2
No3: 商品ID=2244074浏览量=2
=============================
时间: 2017-11-26 10:00:00.0 [09:00:00.0,10:00:00.0) #时间戳为2017/11/26 10:00:00或更晚的数据到来引发该窗口关闭并统计,但由于代码中定时器延迟1s触发,当时间戳为2017/11/26 10:00:01的数据到来时才会在控制台输出统计结果
No1: 商品ID=1715浏览量=3
No2: 商品ID=3611281浏览量=2
No3: 商品ID=2244074浏览量=2
=============================
时间: 2017-11-26 10:05:00.0 [09:05:00.0,10:05:00.0) #时间戳为2017/11/26 10:05:00或更晚的数据到来引发该窗口关闭并统计,但由于代码中定时器延迟1s触发,当时间戳为2017/11/26 10:05:01的数据到来时才会在控制台输出统计结果
No1: 商品ID=1715浏览量=3
No2: 商品ID=2244074浏览量=1
No3: 商品ID=1256540浏览量=1
=============================
时间: 2017-11-26 10:10:00.0 [09:10:00.0,10:10:00.0) #时间戳为2017/11/26 10:00:00或更晚的数据到来引发该窗口关闭并统计,但由于代码中定时器延迟1s触发,当时间戳为2017/11/26 10:00:01的数据到来时才会在控制台输出统计结果
No1: 商品ID=1715浏览量=1
No2: 商品ID=2244074浏览量=1
=============================
时间: 2017-11-26 10:15:00.0 [09:15:00.0,10:15:00.0)
No1: 商品ID=1715浏览量=1
=============================
时间: 2017-11-26 10:20:00.0 [09:20:00.0,10:20:00.0)
No1: 商品ID=1715浏览量=1
=============================
时间: 2017-11-26 10:25:00.0 [09:25:00.0,10:25:00.0)
No1: 商品ID=1715浏览量=1
=============================
时间: 2017-11-26 10:30:00.0 [09:30:00.0,10:30:00.0)
No1: 商品ID=1715浏览量=1
=============================
时间: 2017-11-26 10:35:00.0 [09:35:00.0,10:35:00.0)
No1: 商品ID=1715浏览量=1
=============================
时间: 2017-11-26 10:40:00.0 [09:40:00.0,10:40:00.0)
No1: 商品ID=1715浏览量=1
=============================
时间: 2017-11-26 10:45:00.0 [09:45:00.0,10:45:00.0)
No1: 商品ID=1715浏览量=1
=============================
时间: 2017-11-26 10:50:00.0 [09:50:00.0,10:50:00.0)
No1: 商品ID=1715浏览量=1
=============================
时间: 2017-11-26 10:55:00.0 [09:55:00.0,10:55:00.0)
No1: 商品ID=1715浏览量=1
=============================
Caused by: org.apache.kafka.common.errors.InvalidTopicException: Topic 'hotitems' is invalid
将kafka、flink集群、应用应用程序重新启动,但不知道到原因。