目录
需求2:Top10热门品类中每个品类的Top10活跃Session统计
HotCategoryTop10SessionController
数据说明
- 上面的黑色底框的数据对应下面的白色底框之中的数据,只是分隔符是不一样的
说明:上面的数据图是从数据文件中截取的一部分内容,表示为电商网站的用户行为数据,主要包含用户的4种行为:搜索,点击,下单,支付。数据规则如下:
- 数据文件中每行数据采用下划线分隔数据
- 每一行数据表示用户的一次行为,这个行为只能是4种行为的一种
- 如果搜索关键字为null,表示数据不是搜索数据
- 如果点击的品类ID和产品ID为-1,表示数据不是点击数据
- 针对于下单行为,一次可以下单多个商品,所以品类ID和产品ID可以是多个,id之间采用逗号分隔,如果本次不是下单行为,则数据采用null表示
- 支付行为和下单行为类似
详细字段说明:
样品类:
//用户访问动作表
case class UserVisitAction(
date: String,//用户点击行为的日期
user_id: Long,//用户的ID
session_id: String,//Session的ID
page_id: Long,//某个页面的ID
action_time: String,//动作的时间点
search_keyword: String,//用户搜索的关键词
click_category_id: Long,//某一个商品品类的ID
click_product_id: Long,//某一个商品的ID
order_category_ids: String,//一次订单中所有品类的ID集合
order_product_ids: String,//一次订单中所有商品的ID集合
pay_category_ids: String,//一次支付中所有品类的ID集合
pay_product_ids: String,//一次支付中所有商品的ID集合
city_id: Long
)//城市 id
需求1:Top10热门品类
1.1 需求说明
品类是指产品的分类,大型电商网站品类分多级,咱们的项目中品类只有一级,不同的公司可能对热门的定义不一样。我们按照每个品类的点击、下单、支付的量来统计热门品类。
鞋 点击数 下单数 支付数
衣服 点击数 下单数 支付数
电脑 点击数 下单数 支付数
例如,综合排名 = 点击数*20%+下单数*30%+支付数*50%
本项目需求优化为:先按照点击数排名,靠前的就排名高;如果点击数相同,再比较下单数;下单数再相同,就比较支付数。
1.2 实现方案一
1.2.1 需求分析
分别统计每个品类点击的次数,下单的次数和支付的次数:
(品类,点击总数)(品类,下单总数)(品类,支付总数)
1.2.2 需求实现
- 奇怪的面试题,两个杯子,分别是五升水和三升水的杯子,如何得到四升水??
方式一:5+5-(3+3)=4.首先是将五升水的杯子之中的水倒入三升水空杯子之中,倒出三升水杯子之中的水,将五升水杯子之中剩余的两升水倒入三升水杯子之中。灌满五升水的杯子,将五升水的杯子倒入只有两升水的另一个杯子,剩下的就是四升水。
方式二:3+3+3-5=4,首先将三升水倒入空的五升水的水杯,在将三升水灌满水倒入五升水的水杯,此刻三升水的水杯剩下一升水,将五升水的水杯之中的水倒掉,三升水的水杯之中的水转移到五生水的水杯之中,再将三升水导入五升水的水杯之中,得到四升水.
- 补充:如何自动调出RDD的类型,设置步骤是如下所示:
- 需求实现代码:
package req import org.apache.spark.rdd.RDD import org.apache.spark.{SparkConf, SparkContext} /** * @anthor Yang * @create 2022-07-21 22:47 */ object Spark01_Req_HotCategoryTop10 { def main(args: Array[String]): Unit = { val conf= new SparkConf().setMaster("local[*]").setAppName("HotCategoryTop10") val sc = new SparkContext(conf) //TODO 需求一:Top10热门品类 //TODO 获取原始文件 val fileDatas = sc.textFile("data/user_visit_action.txt") //TODO 统计分析之前需要将不需要的数据进行过滤-先保留所有的统计数据-对点击数据进行统计 val clickDatas = fileDatas.filter( data => { val datas = data.split("_") val cid = datas(6) cid != "-1" } ) //TODO 统计品类点击数量 val clickCntDatas = clickDatas.map( data => { val datas = data.split("_") val cid = datas(6) (cid,1) } ).reduceByKey(_+_) //TODO 统计品类下单数量(方法是和上面差不多的) val orderDatas = fileDatas.filter( data => { val datas = data.split("_") val cid = datas(8) cid != "null" } ) //(1,2,3,4)->(1,1) (2,1) (3.1) 整体变成个体是使用扁平化 //TODO 整体变成部分就是进行一个扁平化的过程 val orderCntDatas = orderDatas.flatMap( data => { val datas = data.split("_") val cid = datas(8) val cids = cid.split(",") cids.map((_,1)) } ).reduceByKey(_+_) //TODO 统计品类支付数量 val payDatas = fileDatas.filter( data => { val datas = data.split("_") val cid = datas(10) cid != "null" } ) //(1,2,3,4)->(1,1) (2,1) (3.1) 整体变成个体是使用扁平化 //TODO 整体变成部分就是进行一个扁平化的过程 val payCntDatas = payDatas.flatMap( data => { val datas = data.split("_") val cid = datas(10) val cids = cid.split(",") cids.map((_,1)) } ).reduceByKey(_+_) //TODO 对统计结果进行排序 =》 点击,下单,支付(三个指标全部进行排序完成之后进行选取 -> Tuple) //val clickSortedDatas = clickCntDatas.sortBy(_._2,false) //这里不是用sortByKey,sortByKet只是对于Key进行一个排序的过程,false表示是降序的过程 //TODO 将点击-》Tuple(品类ID,点击) //TODO 下单-》Tuple(品类ID,下单) //TODO 支付-》Tuple(品类ID,支付) //TODO 将上述的数据变成(品类ID,(点击,下单,支付)) //上述的方式是不能采用JOIN的方式,因为有的数据可能是空的,join要求两边都是有数据的,join的底层是cogroup //这里不使用下面这一条语句的原因是由于括号的原因 // val value: RDD[(String, (Option[(Option[Int], Option[Int])], Option[Int]))] = clickCntDatas.fullOuterJoin(orderCntDatas).fullOuterJoin(payCntDatas) val value: RDD[(String, (Iterable[Int], Iterable[Int], Iterable[Int]))] = clickCntDatas.cogroup(orderCntDatas,payCntDatas) val mapDatas = value.map{ case(cid,(clickIter,orderIter,payIter)) => { var clickcnt =0 var ordercnt =0 var paycnt = 0 val iterator1: Iterator[Int] = clickIter.iterator if(iterator1.hasNext) { clickcnt = iterator1.next() } val iterator2: Iterator[Int] = orderIter.iterator if(iterator2.hasNext) { ordercnt = iterator2.next() } val iterator3: Iterator[Int] = payIter.iterator if(iterator3.hasNext) { paycnt = iterator3.next() } (cid,(clickcnt,ordercnt,paycnt)) } } val top10: Array[(String, (Int, Int, Int))] = mapDatas.sortBy(_._2,false).take(10) //TODO 将统计采集之后的结果打印到控制台上 top10.foreach(println) sc.stop() } }
1.2.3 问题
1.三个过滤,同一个RDD(过滤)的重复使用.RDD是不保留数据的,重复使用浪费性能.
2.cogroup可能存在笛卡尔乘积的问题,相同的时候出现OneToOneDependency,不相同的时候可能会出现ShuffleDependency的现象.------性能瓶颈(可能会出现问题),十几万数据如果要是出现笛卡尔乘积,算子性能会低下.
1.3 实现方案二
1.3.1 需求分析
一次性统计每个品类点击的次数,下单的次数和支付的次数:
(品类,(点击总数,下单总数,支付总数))
1.3.2 需求实现---结局方案一之中的问题
采用reduceByKey的方式代替cogroup,避免出现笛卡尔乘积的问题.
package req
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
/**
* @anthor Yang
* @create 2022-07-21 22:47
*/
object Spark02_Req_HotCategoryTop10 {
def main(args: Array[String]): Unit = {
val conf= new SparkConf().setMaster("local[*]").setAppName("HotCategoryTop10")
val sc = new SparkContext(conf)
//TODO 需求一:Top10热门品类
//TODO 获取原始文件
val fileDatas = sc.textFile("data/user_visit_action.txt")
//TODO 统计分析之前需要将不需要的数据进行过滤-先保留所有的统计数据-对点击数据进行统计
val clickDatas = fileDatas.filter(
data => {
val datas = data.split("_")
val cid = datas(6)
cid != "-1"
}
)
//TODO 统计品类点击数量
val clickCntDatas = clickDatas.map(
data => {
val datas = data.split("_")
val cid = datas(6)
(cid,1)
}
).reduceByKey(_+_)
//TODO 统计品类下单数量(方法是和上面差不多的)
val orderDatas = fileDatas.filter(
data => {
val datas = data.split("_")
val cid = datas(8)
cid != "null"
}
)
//(1,2,3,4)->(1,1) (2,1) (3.1) 整体变成个体是使用扁平化
//TODO 整体变成部分就是进行一个扁平化的过程
val orderCntDatas = orderDatas.flatMap(
data => {
val datas = data.split("_")
val cid = datas(8)
val cids = cid.split(",")
cids.map((_,1))
}
).reduceByKey(_+_)
//TODO 统计品类支付数量
val payDatas = fileDatas.filter(
data => {
val datas = data.split("_")
val cid = datas(10)
cid != "null"
}
)
//(1,2,3,4)->(1,1) (2,1) (3.1) 整体变成个体是使用扁平化
//TODO 整体变成部分就是进行一个扁平化的过程
val payCntDatas = payDatas.flatMap(
data => {
val datas = data.split("_")
val cid = datas(10)
val cids = cid.split(",")
cids.map((_,1))
}
).reduceByKey(_+_)
//TODO 对统计结果进行排序 =》 点击,下单,支付(三个指标全部进行排序完成之后进行选取 -> Tuple)
//val clickSortedDatas = clickCntDatas.sortBy(_._2,false) //这里不是用sortByKey,sortByKet只是对于Key进行一个排序的过程,false表示是降序的过程
//TODO 将点击-》Tuple(品类ID,点击) ---(品类ID,(点击,0,0))
//TODO 下单-》Tuple(品类ID,下单) ---(品类ID,(0,下单,0))
//TODO 支付-》Tuple(品类ID,支付) ---(品类ID,(0,0,支付))
//TODO 将上述的数据变成(品类ID,(点击,下单,支付))
//上述的方式是不能采用JOIN的方式,因为有的数据可能是空的,join要求两边都是有数据的,join的底层是cogroup
//这里不使用下面这一条语句的原因是由于括号的原因
// val value: RDD[(String, (Option[(Option[Int], Option[Int])], Option[Int]))] = clickCntDatas.fullOuterJoin(orderCntDatas).fullOuterJoin(payCntDatas)
//TODO reduceByKey存在shuffle,但是不存在笛卡尔乘积----修改的地方
val clickMapDatas = clickCntDatas.map{
case( cid,clickCnt ) => {
(cid , (clickCnt,0,0))
}
}
val orderkMapDatas = orderCntDatas.map{
case( cid,orderCnt ) => {
(cid , (0,orderCnt,0))
}
}
val payMapDatas = payCntDatas.map{
case( cid,payCnt ) => {
(cid , (0,0,payCnt))
}
}
//将上述的三个数据进行合并,使用union
val unionRDD: RDD[(String, (Int, Int, Int))] = clickMapDatas.union(orderkMapDatas).union(payMapDatas)
//使用reduceByKey
val reduceRDD = unionRDD.reduceByKey(
(t1,t2) =>
{
(t1._1+t2._1 ,t1._2+t2._2 ,t1._3+t2._3)
}
)
val top10 = reduceRDD.sortBy(_._2,false).take(10)
//TODO 将统计采集之后的结果打印到控制台上
top10.foreach(println)
sc.stop()
}
}
1.3.3 问题
reduceByKey存在shuffle的问题,这里我们使用了四个reduceByKey,使用的shuffle次数太多,那么落盘的次数就是很多,想要提高性能,就要想办法减少落盘的过程.
1.4 实现方案三
1.4.1 需求分析
对于上述的问题再次改进
1.4.2 需求实现
- 没有使用累加器,但是减少了shuffle.过滤的过程
package req import org.apache.spark.rdd.RDD import org.apache.spark.{SparkConf, SparkContext} import org.codehaus.jackson.annotate.JsonTypeInfo.Id /** * @anthor Yang * @create 2022-07-21 22:47 */ object Spark03_Req_HotCategoryTop10 { def main(args: Array[String]): Unit = { val conf= new SparkConf().setMaster("local[*]").setAppName("HotCategoryTop10") val sc = new SparkContext(conf) //TODO 需求一:Top10热门品类 //TODO 获取原始文件 val fileDatas = sc.textFile("data/user_visit_action.txt") val flatDatas = fileDatas.flatMap(//flatMap要求返回的是可以迭代的集合 data => { var datas = data.split("_") if( datas(6) != "-1" ) {//点击数据的场合 List((datas(6),(1,0,0))) } else if(datas(8) != "null") {//下单的场合 val id = datas(8) val ids = id.split(",") ids.map(//map返回就是迭代的集合 id => { (id,(0,1,0)) } ) } else if(datas(10) != "null") { val id= datas(10) val ids = id.split(",") ids.map( id=> { (id,(0,0,1)) } ) } else { Nil //全量函数的集合 } } ) val top10 = flatDatas.reduceByKey( (t1 , t2) => { (t1._1 + t2._1 ,t1._2+t2._2 ,t1._3+t2._3) } ).sortBy(_._2,false).take(10) //TODO 将统计采集之后的结果打印到控制台上 top10.foreach(println) sc.stop() } }
1.5 实现方案四
1.5.1 需求分析
使用累加器的过程
1.5.2 需求实现
- 实现代码:比较难一些
package com.atguigu.bigdata.spark.req import org.apache.spark.util.AccumulatorV2 import org.apache.spark.{SparkConf, SparkContext} import scala.collection.mutable object Spark01_Req_HotCategoryTop10_3 { def main(args: Array[String]): Unit = { val conf = new SparkConf().setMaster("local[*]").setAppName("HotCategoryTop10") val sc = new SparkContext(conf) val fileDatas = sc.textFile("data/user_visit_action.txt") // 创建累加器对象 val acc = new HotCategoryAccumulator() // 注册累加器 sc.register(acc, "HotCategory") fileDatas.foreach( data => { val datas = data.split("_") if ( datas(6) != "-1" ) { // 点击的场合 acc.add( (datas(6), "click") ) } else if ( datas(8) != "null" ) { // 下单的场合 val id = datas(8) val ids = id.split(",") ids.foreach( id => { acc.add( (id, "order") ) } ) } else if ( datas(10) != "null" ) { // 支付的场合 val id = datas(10) val ids = id.split(",") ids.foreach( id => { acc.add( (id, "pay") ) } ) } } ) // TODO 获取累加器的结果 val resultMap: mutable.Map[String, HotCategoryCount] = acc.value val top10 = resultMap.map(_._2).toList.sortWith( (left, right) => { if ( left.clickCnt > right.clickCnt ) { true } else if ( left.clickCnt == right.clickCnt ) { if ( left.orderCnt > right.orderCnt ) { true } else if ( left.orderCnt == right.orderCnt ) { left.payCnt > right.payCnt } else { false } } else { false } } ).take(10) top10.foreach(println) sc.stop() } case class HotCategoryCount( cid:String, var clickCnt : Int, var orderCnt : Int, var payCnt : Int ) // TODO 自定义热门点击累加器 // 1. 继承AccumulatorV2 // 2. 定义泛型 // IN : (品类ID,行为类型) // OUT : Map[品类ID, HotCategoryCount] // 3. 重写方法 (3 + 3) class HotCategoryAccumulator extends AccumulatorV2[(String, String), mutable.Map[String, HotCategoryCount]]{ private val map = mutable.Map[String, HotCategoryCount]() override def isZero: Boolean = { map.isEmpty } override def copy(): AccumulatorV2[(String, String), mutable.Map[String, HotCategoryCount]] = { new HotCategoryAccumulator() } override def reset(): Unit = { map.clear() } override def add(v: (String, String)): Unit = { val (cid, actionType) = v val hcc: HotCategoryCount = map.getOrElse(cid, HotCategoryCount(cid, 0, 0, 0)) actionType match { case "click" => hcc.clickCnt += 1 case "order" => hcc.orderCnt += 1 case "pay" => hcc.payCnt += 1 } map.update(cid, hcc) } override def merge(other: AccumulatorV2[(String, String), mutable.Map[String, HotCategoryCount]]): Unit = { other.value.foreach { case ( cid, otherHCC ) => { val thisHCC: HotCategoryCount = map.getOrElse(cid, HotCategoryCount(cid, 0, 0, 0)) thisHCC.clickCnt += otherHCC.clickCnt thisHCC.orderCnt += otherHCC.orderCnt thisHCC.payCnt += otherHCC.payCnt map.update(cid, thisHCC) } } } override def value: mutable.Map[String, HotCategoryCount] = { map } } }
需求2:Top10热门品类中每个品类的Top10活跃Session统计
2.1 需求说明
在需求一的基础上,增加每个品类用户session的点击统计
2.2需求分析
JavaEE Web:浏览器(zhangsan) => 服务器(lisi) [lisi懵了] Session:会话(通信状态)
2.3 功能实现(这里代码之中的架构需要进行注意)
HotCataegoryTop10的代码
- Common之中的代码,用来呈递上面架构之中的架构部分,对里面的代码进行相应的封装:
①对于Application部分的封装
package com.atguigu.bigdata.spark.summer.common
import com.atguigu.bigdata.spark.summer.util.EnvCache
import org.apache.spark.{SparkConf, SparkContext}
trait TApplication {
//下面第一个括号之中代表的是参数,第二个代表的是进行执行的操作
def execute(master:String = "local[*]", appName:String)( op: =>Unit ): Unit = {
val conf: SparkConf = new SparkConf().setMaster(master).setAppName(appName)
val sc = new SparkContext(conf)
EnvCache.put(sc) //用来存放sc这个值,后面可以拿来用
try {
op
} catch {
case e: Exception => e.printStackTrace()
}
sc.stop()
EnvCache.clear()
}
}
②对于Controller部分的封装
package com.atguigu.bigdata.spark.summer.common
trait TController {
def dispatch(): Unit
}
③对于Dao部分的封装
package com.atguigu.bigdata.spark.summer.common
import com.atguigu.bigdata.spark.summer.util.EnvCache
import org.apache.spark.SparkContext
import scala.io.{BufferedSource, Source}
trait TDao {
def readFile( path : String ) = {
// e:/data/word.txt
val source: BufferedSource = Source.fromFile(EnvCache.get() + path)
val lines = source.getLines().toList
source.close()
lines
}
def readFileBySpark( path : String ) = {
//对于前面使用的sc进行一个读取的过程
EnvCache.get().asInstanceOf[SparkContext].textFile(path)
}
}
④Service部分的封装
package com.atguigu.bigdata.spark.summer.common
trait TService {
def analysis() : Any = {
}
def analysis( data : Any ) : Any = {
}
}
- Application(应用的起点)部分的代码:
package com.atguigu.bigdata.spark.summer.application
import com.atguigu.bigdata.spark.summer.common.TApplication
import com.atguigu.bigdata.spark.summer.controller.{HotCategoryTop10Controller, WordCountController}
//这里进行继承App便是可以之间执行的主函数,可以不用写出main函数
object HotCategoryTop10Application extends TApplication with App{
//这里的execute本来是两个参数,但是其中一个是已经定义好的默认参数
execute(appName = "HotCategoryTop10"){
//执行controller
val controller = new HotCategoryTop10Controller
controller.dispatch()
}
}
- Controller(进行调度)部分的代码:
package com.atguigu.bigdata.spark.summer.controller
import com.atguigu.bigdata.spark.summer.common.TController
import com.atguigu.bigdata.spark.summer.service.HotCategoryTop10Service
class HotCategoryTop10Controller extends TController {
private val hotCategoryTop10Service = new HotCategoryTop10Service
override def dispatch(): Unit = {
val result: Array[(String, (Int, Int, Int))] = hotCategoryTop10Service.analysis()
result.foreach(println)
}
}
- Dao(跟数据打交道)部分的代码
package com.atguigu.bigdata.spark.summer.dao
import com.atguigu.bigdata.spark.summer.common.TDao
class HotCategoryTop10Dao extends TDao {
}
- Service(执行逻辑)部分的代码
package com.atguigu.bigdata.spark.summer.service
import com.atguigu.bigdata.spark.summer.common.TService
import com.atguigu.bigdata.spark.summer.dao.HotCategoryTop10Dao
class HotCategoryTop10Service extends TService {
private val hotCategoryTop10Dao = new HotCategoryTop10Dao
override def analysis() = {
val fileDatas = hotCategoryTop10Dao.readFileBySpark("data/user_visit_action.txt")
val flatDatas = fileDatas.flatMap(
data => {
var datas = data.split("_")
if ( datas(6) != "-1" ) {
// 点击数据的场合
List((datas(6), (1, 0, 0)))
} else if ( datas(8) != "null" ) {
// 下单数据的场合
val id = datas(8)
val ids = id.split(",")
ids.map(
id => {
(id, (0, 1, 0))
}
)
} else if ( datas(10) != "null" ) {
// 支付数据的场合
val id = datas(10)
val ids = id.split(",")
ids.map(
id => {
(id, (0, 0, 1))
}
)
} else {
Nil
}
}
)
val top10 = flatDatas.reduceByKey(
(t1, t2) => {
( t1._1 + t2._1, t1._2 + t2._2, t1._3 + t2._3 )
}
).sortBy(_._2, false).take(10)
top10
}
}
- Util(主线程之中用来存放rootPath连接)的代码:
package com.atguigu.bigdata.spark.summer.util
object EnvCache {
private val envCache : ThreadLocal[Object] = new ThreadLocal[Object]
def put( data : Object ): Unit = {
envCache.set(data)
}
def get() = {
envCache.get()
}
def clear(): Unit = {
envCache.remove()
}
}
HotCategoryTop10SessionController
这部分的代码是在上面的原有基础上进行一个增加的过程.
- Application(应用的起点)部分的代码:
package com.atguigu.bigdata.spark.summer.application
import com.atguigu.bigdata.spark.summer.common.TApplication
import com.atguigu.bigdata.spark.summer.controller.{HotCategoryTop10Controller, HotCategoryTop10SessionController}
object HotCategoryTop10SessionApplication extends TApplication with App{
execute(appName = "HotCategoryTop10Session"){
val controller = new HotCategoryTop10SessionController
controller.dispatch()
}
}
- Controller(进行调度)部分的代码:
package com.atguigu.bigdata.spark.summer.controller
import com.atguigu.bigdata.spark.summer.common.TController
import com.atguigu.bigdata.spark.summer.service.{HotCategoryTop10Service, HotCategoryTop10SessionService}
class HotCategoryTop10SessionController extends TController {
//由于这里是需要用到上面的逻辑,因此这里是存在两个代码
private val hotCategoryTop10Service = new HotCategoryTop10Service
private val hotCategoryTop10SessionService = new HotCategoryTop10SessionService
override def dispatch(): Unit = {
val top10: Array[(String, (Int, Int, Int))] = hotCategoryTop10Service.analysis()
val result = hotCategoryTop10SessionService.analysis(top10.map(_._1))//只是存取Id
result.foreach(println)
}
}
- Dao(跟数据打交道)部分的代码
package com.atguigu.bigdata.spark.summer.dao
import com.atguigu.bigdata.spark.summer.common.TDao
class HotCategoryTop10SessionDao extends TDao {
}
- Service(执行逻辑)部分的代码
package com.atguigu.bigdata.spark.summer.service
import com.atguigu.bigdata.spark.summer.bean.UserVisitAction
import com.atguigu.bigdata.spark.summer.common.TService
import com.atguigu.bigdata.spark.summer.dao.{HotCategoryTop10Dao, HotCategoryTop10SessionDao}
import org.apache.spark.rdd.RDD
class HotCategoryTop10SessionService extends TService {
private val hotCategoryTop10SessionDao = new HotCategoryTop10SessionDao
override def analysis( data : Any ) = {
val topIds: Array[String] = data.asInstanceOf[Array[String]]
val fileDatas = hotCategoryTop10SessionDao.readFileBySpark("data/user_visit_action.txt")
val actionDatas = fileDatas.map(
data => {
val datas = data.split("_")
//将字符串转换为long类型
UserVisitAction(
datas(0),
datas(1).toLong,
datas(2),
datas(3).toLong,
datas(4),
datas(5),
datas(6).toLong,
datas(7).toLong,
datas(8),
datas(9),
datas(10),
datas(11),
datas(12).toLong
)
}
)
val clickDatas = actionDatas.filter {
data => {
if ( data.click_category_id != -1 ) //点击不是-1
{
topIds.contains(data.click_category_id.toString)
} else {
false
}
}
}
val reduceDatas = clickDatas.map(
data => {
(( data.click_category_id, data.session_id ), 1)
}
).reduceByKey(_+_)
val groupDatas: RDD[(Long, Iterable[(String, Int)])] = reduceDatas.map {
case ((cid, sid), cnt) => {
(cid, (sid, cnt))
}
}.groupByKey()
groupDatas.mapValues(
iter => {
iter.toList.sortBy(_._2)(Ordering.Int.reverse).take(10)
}
).collect()
}
}
- Bean之中增加UserVisitAcition代码如下所示:
package com.atguigu.bigdata.spark.summer.bean
//用户访问动作表
case class UserVisitAction(
date: String,//用户点击行为的日期
user_id: Long,//用户的ID
session_id: String,//Session的ID
page_id: Long,//某个页面的ID
action_time: String,//动作的时间点
search_keyword: String,//用户搜索的关键词
click_category_id: Long,//某一个商品品类的ID
click_product_id: Long,//某一个商品的ID
order_category_ids: String,//一次订单中所有品类的ID集合
order_product_ids: String,//一次订单中所有商品的ID集合
pay_category_ids: String,//一次支付中所有品类的ID集合
pay_product_ids: String,//一次支付中所有商品的ID集合
city_id: Long//城市 id
)
需求3:页面单跳转换率统计
3.1 需求说明
1)页面单跳转化率
计算页面单跳转化率,什么是页面单跳转换率,比如一个用户在一次 Session 过程中访问的页面路径 3,5,7,9,10,21,那么页面 3 跳到页面 5 叫一次单跳,7-9 也叫一次单跳,那么单跳转化率就是要统计页面点击的概率。
比如:计算 3-5 的单跳转化率,先获取符合条件的 Session 对于页面 3 的访问次数(PV)为 A,然后获取符合条件的 Session 中访问了页面 3 又紧接着访问了页面 5 的次数为 B,那么 B/A 就是 3-5 的页面单跳转化率。
为什么IE被淘汰了?因为IE不支持新的技术,而且不能够装入插件.不要说什么产品经理没用,你是可以替换的,但是产品经理是不可以替换的.
2)统计页面单跳转化率意义
产品经理和运营总监,可以根据这个指标,去尝试分析,整个网站,产品,各个页面的表现怎么样,是不是需要去优化产品的布局;吸引用户最终可以进入最后的支付页面。
数据分析师,可以此数据做更深一步的计算和分析。
企业管理层,可以看到整个公司的网站,各个页面的之间的跳转的表现如何,可以适当调整公司的经营战略或策略。
信息产生过剩:产生的大量的垃圾信息,在垃圾信息之中,找出有用的信息.
3.2 需求分析
第一列是进行一个采集的过程,数据产生乱序的原因,如下流程图所示:
上述过程是需要进行计算一个分子与分母的过程,分子(首页 - 详情) 分母(首页)
package com.atguigu.bigdata.spark.req
import com.atguigu.bigdata.spark.summer.bean.UserVisitAction
object Spark03_Req_PageFlow {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setMaster("local[*]").setAppName("Pageflow")
val sc = new SparkContext(conf)
val fileDatas = sc.textFile("data/user_visit_action.txt")
//包装成为样例类
val actionDatas = fileDatas.map(
data => {
val datas = data.split("_")
UserVisitAction(
datas(0),
datas(1).toLong,
datas(2),
datas(3).toLong,
datas(4),
datas(5),
datas(6).toLong,
datas(7).toLong,
datas(8),
datas(9),
datas(10),
datas(11),
datas(12).toLong
)
}
)
actionDatas.cache()//做一个缓存过程
// 【1,2,3,4,5,6,7】
val okIds = List(1,2,3,4,5,6,7)
// 【(1,2),(2,3)】
val okFlowIds = okIds.zip(okIds.tail)
// TODO 分母的计算 Long是页面id,int是统计结果
val result: Map[Long, Int] = actionDatas.filter(
action => {
okIds.init.contains(action.page_id.toInt)
}
).map(
action => {
(action.page_id, 1)
}
).reduceByKey(_ + _).collect().toMap
// TODO 分子的计算
// 1. 将数据按照session进行分组
val groupRDD: RDD[(String, Iterable[UserVisitAction])] = actionDatas.groupBy(_.session_id)
// 将分组后的数据进行组内排序
val mapRDD = groupRDD.mapValues(
iter => {
//toList含义就是将上面的东西进行转换,进行可以迭代操作
val actions: List[UserVisitAction] = iter.toList.sortBy(_.action_time)
//【1,2,3,4,5,6,7】
//【2,3,4,5,6,7】
// 滑窗
//【1-2,2-3,3-4,4-5,5-6,6-7】
val ids: List[Int] = actions.map(_.page_id.toInt)
// val iterator: Iterator[List[Long]] = ids.sliding(2)
// while ( iterator.hasNext ) {
// val longs: List[Long] = iterator.next()
// (longs.head, longs.last)
// }
val flowIds: List[(Int, Int)] = ids.zip(ids.tail)
flowIds.filter(
ids => {
okFlowIds.contains(ids)
}
)
}
)
val mapRDD2 = mapRDD.map(_._2)
val flatRDD = mapRDD2.flatMap(list => list)
// 分子计算完毕
val reduceRDD = flatRDD.map((_, 1)).reduceByKey(_ + _)
// TODO 单挑转换率的统计
reduceRDD.foreach {
case ( (id1, id2), cnt ) => {
println(s"页面【${id1}-${id2}】单挑转换率为 :" + ( cnt.toDouble / result.getOrElse(id1, 1) ))
}
}
sc.stop()
}
}