Spark案例实际操作

目录

数据说明

需求1:Top10热门品类

1.1 需求说明

1.2 实现方案一

1.2.1 需求分析

1.2.2 需求实现

1.2.3 问题

1.3 实现方案二

1.3.1 需求分析

1.3.2 需求实现---结局方案一之中的问题

1.3.3 问题

1.4 实现方案三

1.4.1 需求分析

1.4.2 需求实现

1.5 实现方案四

1.5.1 需求分析

1.5.2 需求实现

需求2:Top10热门品类中每个品类的Top10活跃Session统计

2.1 需求说明

2.2需求分析

2.3 功能实现(这里代码之中的架构需要进行注意)

HotCataegoryTop10的代码 

HotCategoryTop10SessionController

需求3:页面单跳转换率统计

3.1 需求说明

3.2 需求分析


数据说明

  • 上面的黑色底框的数据对应下面的白色底框之中的数据,只是分隔符是不一样的

说明:上面的数据图是从数据文件中截取的一部分内容,表示为电商网站的用户行为数据,主要包含用户的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()

    }
}
  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
当涉及到大规模数据处理时,Spark数据倾斜是一个常见的问题。下面是一个具体案例来说明Spark数据倾斜的情况: 假设我们有一个包含用户行为记录的大型数据集,其中包括用户ID、行为类型和时间戳。我们想要统计每种行为类型的数量,并找出最常见的行为类型。 在Spark中,我们可能会使用以下代码来完成这个任务: ```python # 读取数据集 data = spark.read.csv("user_behavior.csv", header=True) # 统计每种行为类型的数量 action_counts = data.groupBy("action_type").count() # 找出最常见的行为类型 most_common_action = action_counts.orderBy(desc("count")).first() ``` 然而,由于数据集的大小和分布,我们可能会遇到数据倾斜的问题。具体来说,在某些行为类型上,数据可能会非常庞大,而其他行为类型则相对较小。这会导致在执行group by操作时,某些分区的数据量远远超过其他分区,从而导致性能下降。 为了解决这个问题,我们可以使用以下方法之一: 1. 预先通过采样或其他手段了解数据分布情况,以便更好地进行数据划分和分区。 2. 使用Spark的一些内置函数(例如`repartition`和`coalesce`)来重新分区数据,使得数据更均匀地分布在不同的分区中。 3. 使用Spark的一些高级技术,如数据倾斜解决算法(例如Spark-DataSkew)或自定义聚合函数来处理数据倾斜情况。 这只是一个简单的案例来说明Spark数据倾斜的情况。实际应用中,数据倾斜可能会更加复杂和严重。因此,需要根据具体情况采取相应的解决方法来处理数据倾斜问题。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值