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)
}
posted @   FireOnFire  阅读(78)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示