SparkCore:案例实操
数据说明
- 用户有四种行为:搜索、点击、下单、支付。
- 每行数据用下划线分割不同含义的数据。
- 每行数据表示用户的一种行为。
- 如果搜索关键字为 null,这行数据就不是搜索数据。
- 如果点击的品类 ID 和产品 ID 为-1,这行数据就不是点击数据。
- 一次可以下单多个商品,所以品类 ID 和产品 ID 可以是多个,ID 之间采用逗号分隔。如果本次不是下单行为,则数据采用 null 表示。
- 支付行为和下单行为类似。
编号 | 字段名称 | 字段类型 | 字段描述 |
---|---|---|---|
1 | date | String | 用户点击行为的日期 |
2 | user_id | Long | 用户的 ID |
3 | session_id | String | Session 的 ID |
4 | page_id | Long | 页面的 ID |
5 | action_time | String | 发生行为的时间 |
6 | search_keyword | String | 搜索的关键词 |
7 | click_category_id | Long | 商品品类的 ID |
8 | click_product_id | Long | 商品的 ID |
9 | order_category_ids | String | 一次订单中所有品类的 ID 集合 |
10 | order_product_ids | String | 一次订单中所有商品的 ID 集合 |
11 | pay_category_ids | String | 一次支付中所有品类的 ID 集合 |
12 | pay_product_ids | String | 一次支付中所有商品的 ID 集合 |
13 | city_id | Long | 城市的 ID |
Top10 热门品类
需求分析
品类是指产品的分类,本案例中每个商品只有一级品类。我们按照每个品类的点击、下单、支付的量来统计热门品类。
综合排名 = 点击数 * 20% + 下单数 * 30% + 支付数 * 50%
排名规则:先按照点击数排名,再按照下单数排名,最后按照支付数排名。
方案一
// 1. 读取原始日志数据
val actionRDD = sc.textFile("datas/user_visit_action.txt")
// 2. 统计品类的点击数量:(品类ID,点击数量)
val clickActionRDD: RDD[String] = actionRDD.filter(
action => {
val datas = action.split("_")
datas(6) != "-1"
}
)
val clickCountRDD: RDD[(String, Int)] = clickActionRDD.map(
action => {
val datas = action.split("_")
(datas(6), 1)
}
).reduceByKey(_ + _)
// 3. 统计品类的下单数量:(品类ID,下单数量)
val orderActionRDD = actionRDD.filter(
action => {
val datas = action.split("_")
datas(8) != "null"
}
)
// orderid => 1,2,3
// 【(1,1),(2,1),(3,1)】
val orderCountRDD: RDD[(String, Int)] = orderActionRDD.flatMap(
action => {
val datas = action.split("_")
val cid = datas(8)
val cids = cid.split(",")
cids.map(id => (id, 1))
}
).reduceByKey(_ + _)
// 4. 统计品类的支付数量:(品类ID,支付数量)
val payActionRDD = actionRDD.filter(
action => {
val datas = action.split("_")
datas(10) != "null"
}
)
val payCountRDD = payActionRDD.flatMap(
action => {
val datas = action.split("_")
val cid = datas(10)
val cids = cid.split(",")
cids.map(id=>(id, 1))
}
).reduceByKey(_+_)
// 5. 将品类进行排序,并且取前10名
// 元组排序:先比较第一个,再比较第二个,再比较第三个,依此类推
// ( 品类ID, ( 点击数量, 下单数量, 支付数量 ) )
val cogroupRDD: RDD[(String, (Iterable[Int], Iterable[Int], Iterable[Int]))] = clickCountRDD.cogroup(orderCountRDD, payCountRDD)
val analysisRDD = cogroupRDD.mapValues{
case (clickIter, orderIter, payIter) => {
var clickCnt = 0
// 使用迭代器是因为clickIter可能为null,是为了赋值为0
val iter1 = clickIter.iterator
if (iter1.hasNext) {
clickCnt = iter1.next()
}
var orderCnt = 0
val iter2 = orderIter.iterator
if (iter2.hasNext) {
orderCnt = iter2.next()
}
var payCnt = 0
val iter3 = payIter.iterator
if (iter3.hasNext) {
payCnt = iter3.next()
}
(clickCnt, orderCnt, payCnt)
}
}
val resultRDD = analysisRDD.sortBy(_._2, false).take(10)
// 6. 将结果采集到控制台打印出来
resultRDD.foreach(println)
方案二
// 1. 读取原始日志数据
val actionRDD = sc.textFile("datas/user_visit_action.txt")
actionRDD.cache()
// 2. 数据转换
// 点击:(品类ID,(1,0,0))
// 下单:(品类ID,(0,1,0))
// 支付:(品类ID,(0,0,1))
val flatRDD: RDD[(String, (Int, Int, Int))] = actionRDD.flatMap(
action => {
val datas = action.split("_")
if (datas(6) != "-1") List((datas(6), (1, 0, 0)))
else if (datas(8) != "null") {
val ids = datas(8).split(",")
ids.map(id => (id, (0, 1, 0)))
} else if (datas(10) != "null") {
val ids = datas(10).split(",")
ids.map(id => (id, (0, 0, 1)))
} else Nil
}
)
val analysisRDD: RDD[(String, (Int, Int, Int))] = flatRDD.reduceByKey(
(t1, t2) => {
(t1._1 + t2._1, t1._2 + t2._2, t1._3 + t2._3)
}
)
val resultRDD = analysisRDD.sortBy(_._2, false).take(10)
// 3. 将结果采集到控制台打印出来
resultRDD.foreach(println)
方案三
// 使用累加器,避免shuffle,提高性能
def main(args: Array[String]): Unit = {
val sparConf = new SparkConf().setMaster("local[*]").setAppName("HotCategoryTop10Analysis")
val sc = new SparkContext(sparConf)
// 1. 读取原始日志数据
val actionRDD = sc.textFile("datas/user_visit_action.txt")
val acc: HotCategoryAccumulator = new HotCategoryAccumulator
sc.register(acc, "HotCategory")
// 2. 使用累加器
actionRDD.foreach(
action => {
val datas = action.split("_")
if (datas(6) != "-1") acc.add((datas(6), "click"))
else if (datas(8) != "null") {
val ids = datas(8).split(",")
ids.foreach(id => acc.add((id, "order")))
} else if (datas(10) != "null") {
val ids = datas(10).split(",")
ids.foreach(id => acc.add((id, "pay")))
}
}
)
val accValue: mutable.Map[String, HotCategory] = acc.value
val categories: mutable.Iterable[HotCategory] = accValue.map(_._2)
val sortCategories: List[HotCategory] = categories.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
}
)
sortCategories.take(10).foreach(println)
sc.stop()
}
case class HotCategory(cid: String, var clickCnt: Int, var orderCnt: Int, var payCnt: Int)
/**
* 自定义累加器
* 1、继承 AccumulatorV2 并定义泛型
* IN:(品类,行为类型)
* OUT:mutable.Map[String, HotCategory]
* 2、重写方法
*/
class HotCategoryAccumulator extends AccumulatorV2[(String, String), mutable.Map[String, HotCategory]] {
private val hcMap = mutable.Map[String, HotCategory]()
override def isZero: Boolean = hcMap.isEmpty
override def copy(): AccumulatorV2[(String, String), mutable.Map[String, HotCategory]] = new HotCategoryAccumulator()
override def reset(): Unit = hcMap.clear()
override def add(v: (String, String)): Unit = {
val cid = v._1
val actionType = v._2
val category: HotCategory = hcMap.getOrElse(cid, HotCategory(cid, 0, 0, 0))
if (actionType == "click") category.clickCnt += 1
else if (actionType == "order") category.orderCnt += 1
else if (actionType == "pay") category.payCnt += 1
hcMap.update(cid, category)
}
override def merge(other: AccumulatorV2[(String, String), mutable.Map[String, HotCategory]]): Unit = {
val map1 = this.hcMap
val map2 = other.value
map2.foreach{
case (cid, hc) => {
val category: HotCategory = map1.getOrElse(cid, HotCategory(cid, 0, 0, 0))
category.clickCnt += hc.clickCnt
category.payCnt += hc.payCnt
category.orderCnt += hc.orderCnt
map1.update(cid, category)
}
}
}
override def value: mutable.Map[String, HotCategory] = hcMap
}
Top10热门品类中每个品类Top10活跃Session
def main(args: Array[String]): Unit = {
val sparConf = new SparkConf().setMaster("local[*]").setAppName("HotCategoryTop10Session")
val sc = new SparkContext(sparConf)
val actionRDD = sc.textFile("datas/user_visit_action.txt")
actionRDD.cache()
val top10Ids: Array[String] = top10Category(actionRDD)
// 1.过滤原始数据,保留点击和前10品类ID
val filterRDD: RDD[String] = actionRDD.filter(
action => {
val datas = action.split("_")
if (datas(6) != "-1") top10Ids.contains(datas(6))
else false
}
)
// 2.根据品类ID和sessionid进行点击量的统计
val reduceRDD: RDD[((String, String), Int)] = filterRDD.map(
action => {
val datas = action.split("_")
((datas(6), datas(2)), 1)
}
).reduceByKey(_ + _)
// 3.将统计的结果进行结构的转换
// ((品类ID,SessionID),sum) => (品类ID,(SessionID,sum))
val mapRDD: RDD[(String, (String, Int))] = reduceRDD.map {
case ((cid, sid), sum) => {
(cid, (sid, sum))
}
}
// 4.相同的品类进行分组
val groupRDD: RDD[(String, Iterable[(String, Int)])] = mapRDD.groupByKey()
// 5.将分组后的数据进行点击量的排序,取前10名
val resultRDD: RDD[(String, List[(String, Int)])] = groupRDD.mapValues(
iter => {
iter.toList.sortBy(_._2)(Ordering.Int.reverse).take(10)
}
)
resultRDD.foreach(println)
sc.stop()
}
def top10Category(actionRDD: RDD[String]) = {
val flatRDD: RDD[(String, (Int, Int, Int))] = actionRDD.flatMap(
action => {
val datas = action.split("_")
if (datas(6) != "-1") List((datas(6), (1, 0, 0)))
else if (datas(8) != "null") {
val ids = datas(8).split(",")
ids.map(id => (id, (0, 1, 0)))
} else if (datas(10) != "null") {
val ids = datas(10).split(",")
ids.map(id => (id, (0, 0, 1)))
} else Nil
}
)
val analysisRDD: RDD[(String, (Int, Int, Int))] = flatRDD.reduceByKey(
(t1, t2) => {
(t1._1 + t2._1, t1._2 + t2._2, t1._3 + t2._3)
}
)
analysisRDD.sortBy(_._2, false).take(10).map(_._1)
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步