Scala_Spark-电商平台离线分析项目-需求八各城市各广告的实时点击流量统计
第四模块:广告流量实时统计统计
技术点:SparkStreaming、kafka集群
知识点:updateStateByKey 全局的累积操作
kafka.broker.list=node01:9092,node02:9092,node03:9092
kafka.topics=AdRealTimeLog0308、
(一)执行步骤
1)本地生产数据 发送到kafka
-
开启zookeeper集群
【三台服务器启动zookeeper】,三台机器都执行以下命令启动zookeeper
cd /export/servers/zookeeper-3.4.5-cdh5.14.0
bin/zkServer.sh start
进程QuorumPeerMain -
开启kafka集群
【启动kafka集群】默认端口9092
三台机器启动kafka服务
[root@node01 servers]# cd /export/servers/kafka_2.11-1.0.0/
前台启动 ./kafka-server-start.sh …/config/server.properties
后台启动命令 nohup bin/kafka-server-start.sh config/server.properties > /dev/null 2>&1 & -
node01开启一个消费者
所有配置完成的情况下
[root@node01 ~]# kafka-console-consumer.sh --zookeeper node01:2181 --topic AdRealTimeLog0308 Using the ConsoleConsumer with old consumer is deprecated and will be removed in a future major release. Consider using the new consumer by passing [bootstrap-server] instead of [zookeeper]. // 等待消费
-
本地运行模拟生产实时数据文件MockRealTimeData.scala
该需求运行前可以把id数和秒数改小一点 不然要等很久才能过100次
// node01上出现 集群上消费成功 代表生产数据发送到kafka没有问题 1573145438221 3 3 93 5 1573145438221 6 6 87 17 1573145438221 0 0 10 16 1573145438221 7 7 11 15 1573145438221 0 0 8 18 1573145438221 0 0 97 1
-
提前在本地建立的commerce数据库创建好5张ad广告表
// 以上部分和上个笔记一样
2)IDEA运行AdverStat.scala
-
在以上试验成功状态下,IDEA运行AdverStat.scala文件
运行成功后不断去数据库中刷新ad_stat表 数字在不断变化
其中主要方法部分如下:
def provinceCityClickStat
/** * 锚点2的方法 * 需求八 各城市一天中的广告点击(去除了黑名单里的id点击的) * * 知识点 * updateStateByKey 全局的累积操作 * reduceByKey 统计每个rdd的值的累积量 * * @param adRealTimeFilterDStream 所有不在黑名单里的数据 * @return */ def provinceCityClickStat(adRealTimeFilterDStream: DStream[String])={ // adReadTimeValueDStream:DStream[RDD RDD RDD ...] RDD[String] // String: timestamp province city userid adid // todo map val key2ProvinceCityDStream = adRealTimeFilterDStream.map{ case log => val logSplit: Array[String] = log.split(" ") val timeStamp = logSplit(0).toLong // dateKey : yy-mm-dd val dateKey = DateUtils.formatDateKey(new Date(timeStamp)) val province = logSplit(1) val city = logSplit(2) val adid = logSplit(4) val key = dateKey+"_"+province+"_"+city+"_"+adid // 要加广告id 每个城市 每个广告的点击量 这意思 唯独不要用户id (key,1L) } // todo updateStateByKey // 聚合 关键步骤 sparkStreaming常用的updateStateByKey,按key进行遍历 记住是遍历 // 所有的rdd都会被遍历 全局的累积操作 val key2StateDStream = key2ProvinceCityDStream.updateStateByKey[Long]{ // updateStateByKey 必须有了Some(类实例)才会不爆红(长的像没导包的爆红 还以为不能用 南) // values:Seq[Long] 就是当期执行的key 对应到的所有数据 以Seq形式传进来 所有的(当前key,1L)里面的1 // state:Option[Long] 就是当前执行的key 对应的之前checkpoint出去的值 (values:Seq[Long],state:Option[Long]) => var newValue = 0L if(state.isDefined) // 判断Option里面有没有值 None/可get newValue = state.get // 如果之前有checkpoint出去就get出来 如果这个key是第一次执行 没有就是上面的0 for(value <- values){ // 增强for循环 其实就是遍历的一个一个的1 newValue += value // } // 返回最新的累积值 它会自动的帮我checkpoint到当前key Some(newValue) } // 至此 获得全局的累加结果 // key2StateDStream.foreachRDD{ rdd => rdd.foreachPartition{ items => val adStatArray = new ArrayBuffer[AdStat]() // key: date_province_city_adid for((key,count) <- items){ val keySplit = key.split("_") val date = keySplit(0) val province = keySplit(1) val city = keySplit(2) val adid = keySplit(3).toLong adStatArray += AdStat(date,province,city,adid,count) } // 调用DAO类 更新到数据库 AdStatDAO.updateBatch(adStatArray.toArray) } } } // 需求八结束 运行后刷新看ad_stat表
主方法
def main(args: Array[String]): Unit = { val sparkConf = new SparkConf().setAppName("adverstat").setMaster("local[*]") val sparkSession = SparkSession.builder().config(sparkConf).enableHiveSupport().getOrCreate() // 标准应当是 val streamingContext = StreamingContext.getActiveOrCreate(checkpointDir,func) val streamingContext = new StreamingContext(sparkSession.sparkContext,Seconds(5)) // 配置kafka相关信息 val kafka_brokers = ConfigurationManager.config.getString(Constants.KAFKA_BROKERS) //node01:9092,node02:9092,node03:9092 val kafka_topics = ConfigurationManager.config.getString(Constants.KAFKA_TOPICS) //kafka.topics=AdRealTimeLog0308 // kafka配置信息 val kafkaParam = Map( "bootstrap.servers" -> kafka_brokers, "key.deserializer" -> classOf[StringDeserializer], "value.deserializer" -> classOf[StringDeserializer], "group.id" -> "group1", // auto.offset.reset // latest: 先去Zookeeper获取offset,如果有,直接使用,如果没有,从最新的数据开始消费 // earlist: 先去zookeeper获取offset,如果有直接使用,如果没有,从最开始的数据开始消费 // none: 先去Zookeeper获取offset,如果有,直接使用,如果没有,直接报错 "auto.offset.reset" -> "latest", "enable.auto.commit" -> (false:java.lang.Boolean) ) // 创建DStream // 从kafka中消费数据 拿到的每一条数据都是messege,里面是key-value val adRealTimeDStream = KafkaUtils.createDirectStream[String,String]( streamingContext, // 让kafka分区均匀地在excutor上分配 有三种选择 LocationStrategies.PreferConsistent, // 消费者订阅 ConsumerStrategies.Subscribe[String,String](Array(kafka_topics),kafkaParam) ) // 取出了DStream里面每一条数据的value值 // adReadTimeValueDStream:DStream[RDD RDD RDD ...] RDD[String] // String: timestamp province city userid adid val adReadTimeValueDStream = adRealTimeDStream.map(item => item.value()) // adRealTimeFilterDStream 所有不在黑名单里的实时数据都在里面了 val adRealTimeFilterDStream =adReadTimeValueDStream.transform{ logRDD => { // blackListArray:Array[AdBlacklist] AdBlacklist:userId val blackListArray = AdBlacklistDAO.findAll() // 这里连接了数据库 创建了mySqlPool // userIdArray:Array[Long] [userId1,userId2,...] var userIdArray = blackListArray.map(item => item.userid) // 过滤掉已经存在在黑名单里的 logRDD.filter { // log: timestamp province city userid adid case log => val logSplit = log.split(" ") val userId = logSplit(3).toLong !userIdArray.contains(userId) } } // end logRDD } //end adRealTimeFilterDStream // 测试 本地能否消费kafka集群的数据 // adRealTimeFilterDStream.foreachRDD(rdd=>rdd.foreach(println(_))) // todo 需求七:实时维护黑名单 // 1 generateBlackList(adRealTimeFilterDStream) // todo 需求八:各省各城市(即各城市)一天中的各广告点击量(累积统计) // 在使用 前要指定一下checkpoint目录 // 因为它是以k-v保存在目录里 每次累积的时候把v反序列化出来 累积后再序列化进去 streamingContext.checkpoint("./spark-streaming") // 对所有不在黑名单里的数据checkpoint 时间间隔必须是创建streamingContext的指定时间间隔的倍数 adRealTimeFilterDStream.checkpoint(Duration(10000)) // 2 provinceCityClickStat(adRealTimeFilterDStream) // 开启 streamingContext.start() streamingContext.awaitTermination() }