spark案例:Top10 热门品类

数据格式:

 数据文件中每行数据采用下划线分隔数据

每一行数据表示用户的一次行为,这个行为只能是 4 种行为的一种

如果搜索关键字为 null,表示数据不是搜索数据

如果点击的品类 ID 和产品 ID 为-1,表示数据不是点击数据

针对于下单行为,一次可以下单多个商品,所以品类 ID 和产品 ID 可以是多个,id 之 间采用逗号分隔,如果本次不是下单行为,则数据采用 null 表示

支付行为和下单行为类似

需求:按照每个品类的点击、下单、支付的量来统计热门品类(先按照点击数排名,靠前的就排名高;如果点击数相同,再比较下单数;下单数再相同,就比较支付数)

分析:数据可以统计成(品类,点击总数)(品类,下单总数)(品类,支付总数)这种格式来进行汇总,就简化成了WordCount问题

在实际的解决中我们可以进一步分析解决将数据转化成(品类,(点击,下单,支付))来解决

代码:

object demand {

    def main(args: Array[String]): Unit = {

        val sc = new SparkContext(new SparkConf().setMaster("local[*]").setAppName("demand"))

        val rdd: RDD[String] = sc.textFile("datas/user_visit_action.txt")

        // TODO 分别统计每个品类点击的次数,下单的次数和支付的次数: (品类,点击总数)(品类,下单总数)(品类,支付总数)

        val accumulator = new MyAccumulator
        sc.register(accumulator,"demand")

        // 第一种 :将数据扁平化整理成(品类,(点击,下单,支付)),按key值进行累加即可
//        rdd.flatMap(
//            value => {
//                val values: Array[String] = value.split("_")
//                if (values(6) != "-1") {
//                    List((values(6), (1, 0, 0)))
//                } else if (values(8) != "null") {
//                    val ids: Array[String] = values(8).split(",")
//                    ids.map(
//                        id => (id, (0, 1, 0))
//                    )
//                } else if (values(10) != "null") {
//                    val ids: Array[String] = values(10).split(",")
//                    ids.map(
//                        id => (id, (0, 0, 1))
//                    )
//                }else {
//                    Nil // 空集合
//                }
//            }
//        ).reduceByKey(
//            (t1,t2) => {
//                (t1._1+t2._1,t1._2+t2._2,t1._3+t2._3)
//            }
//        ).sortBy(_._2,false).take(10).foreach(println)


        // 第二种:使用累加器来避免shuffle
        rdd.foreach(
            value => {
                val values: Array[String] = value.split("_")
                if (values(6) != "-1") {
                    accumulator.add(values(6),"click")
                } else if (values(8) != "null") {
                    val ids: Array[String] = values(8).split(",")
                    ids.foreach(
                        id => accumulator.add(id,"order")
                    )
                } else if (values(10) != "null") {
                    val ids: Array[String] = values(10).split(",")
                    ids.foreach(
                        id => accumulator.add(id,"pay")
                    )
                }
            }
        )

        // 要自定义比较规则
        accumulator.value.map(_._2).toList.sortWith(
            (l,r) =>{
                if (l.clickCnt > r.clickCnt){
                    true
                }else if (l.clickCnt == r.clickCnt){
                    if (l.orderCnt > r.orderCnt){
                        true
                    }else if (l.orderCnt == r.orderCnt){
                        if (l.payCnt > r.payCnt){
                            true
                        }else false
                    }else false
                }else false
            }
        ).take(10).foreach(println)


        sc.stop()
    }

    // 辅助类
    case class UserVisitAction(cid:String,var clickCnt:Int,var orderCnt:Int,var payCnt:Int)

    // 自定义累加器
    class MyAccumulator extends AccumulatorV2[(String,String),mutable.Map[String, UserVisitAction]]{

        private var values: mutable.Map[String,UserVisitAction] = mutable.Map[String,UserVisitAction]()

        // 累加器是否为初始状态
        override def isZero: Boolean = values.isEmpty

        // 复制累加器
        override def copy(): AccumulatorV2[(String,String), mutable.Map[String,UserVisitAction]] = {
            new MyAccumulator()
        }

        // 重置累加器
        override def reset(): Unit = values.clear()

        // 向累加器中添加数据
        override def add(v: (String,String)): Unit = {

            val cid: String = v._1
            val action: String = v._2

            // 获取品类对应的UserVisitAction数据,从而来实现对相应操作的累加赋值
            val action1: UserVisitAction = values.getOrElse(cid, UserVisitAction(cid, 0, 0, 0))
            if (action == "click"){
                action1.clickCnt += 1
            }else if (action == "order"){
                action1.orderCnt += 1
            }else if (action == "pay"){
                action1.payCnt += 1
            }
            // 数据一定要进行更新
            values.update(cid,action1)
        }

        // 合并累加器
        override def merge(other: AccumulatorV2[(String,String), mutable.Map[String, UserVisitAction]]): Unit = {

            // 你将要输出的map
            val map1 = this.values
            // 相对于输出的map以外的来自其他task任务返回的map
            val map2 = other.value

            // 对结果进行merge合并
            map2.foreach{
                case (cid,use) => {
                    val action: UserVisitAction = map1.getOrElse(cid, UserVisitAction(cid, 0, 0, 0))
                    action.clickCnt += use.clickCnt
                    action.orderCnt += use.orderCnt
                    action.payCnt += use.payCnt

                    // 一定要记得对数据更新到你要输出的map中去
                    map1.update(cid,action)
                }
            }
        }

        // 返回累加器的结果
        override def value: mutable.Map[String, UserVisitAction] = values
    }
}

在解决问题时,由于spark有三种数据结构:RDD、累加器、广播变量,分别解决了一些问题,我们可以配合的使用它们来优化程序,由于计算时shuffle过程涉及磁盘IO,所有可以通过累加器的使用来避免shuffle提高效率。

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值