spark企业级电商分析平台项目实践(三)sessionStat模块解析

前言

本章节对项目的sessionStat模块进行解析,该模块负责用户访问session的统计,代码实现前四个需求:

  1. session访问步长/访问时长占比统计
  2. 按比例随机抽取session
  3. top10热门商品类统计
  4. 热门品类活跃session统计

一、任务流程及逻辑分析

整个用户访问session统计任务的流程图如下
在这里插入图片描述

1. 从数据表user_visit_action提取所有原始数据

数据表user_visit_action的数据模型如下
在这里插入图片描述

2. 将原始数据根据session进行聚合

首先将原始数据转换为 K-V 结构,以原始数据里的session作为key,以原始数据作为value。
得到的数据结构为(session_id,(date,user_id,session_id,...,city_id))然后根据session_id进行聚合。
聚合完成后,得到的数据结构应为:

(session_id,(date,user_id,session_id,...,city_id)
             (date,user_id,session_id,...,city_id)
             (date,user_id,session_id,...,city_id)
             ... ...

数据类型为(session_id, Iterable[UserVisitAction]),iterable可以通过map遍历其内元素。

3. 提取关键数据字段

每个需求的实现都需要对user_visit_action表进行数据处理,会造成开销,为避免这种浪费,将得到的聚合数据进行关键字段的提取,得到提取后的数据sessionId2GroupRDD,其数据格式如下:

Session_id | Search_Keywords | Click_Category_id | Visit_Length | Step_Length | Start_Time

比如,需求一只会用到Visit_Length和Step_Length,多提取出的Search_Keywords、Click_Category_id、Start_Time是为后面的需求做准备。

4. 联立user_info表

实际情况下,只希望获取指定范围的user群体的session,所以要对每条数据添加用户信息字段。
sessionId2GroupRDDuser_info通过主键user_id联立后,得到的完整信息数据如下:

Session_id | Search_Keywords | Click_Category_id | Visit_Length | Step_Length | Start_Time | Age | Professional | Sex | City

5. 数据过滤

按照指定条件对完整信息数据进行过滤,符合条件的session返回到sessionId2FilteredRDD。

6. 更新累加器,统计结果

过滤后符合条件的session,得到其时长和步长,判断属于哪一段后,更新累加器的值。

7. 数据封装,写入MySql

二、主体代码解析

本章节只进行主体部分的代码解析,四个需求的实现都基于主体部分的代码,将在后续章节解析

1. main函数

def main(args: Array[String]): Unit = {
    //得到配置信息的json对象
    val jsonStr = ConfigurationManager.config.getString(Constants.TASK_PARAMS)
    val taskParam = JSONObject.fromObject(jsonStr)

    //创建UUID
    val taskUUID = UUID.randomUUID().toString

    //创建spark入口
    val sparkSession = SparkSession.builder().config(sparkConf).enableHiveSupport().getOrCreate()

    //流程里的第一步,从数据表user_visit_action提取所有原始数据
    val actionRDD = getBasicActionData(sparkSession, taskParam)
    //流程里的第二步,先转换为K-V结构,再进行聚合操作
    val sessionId2action1 = actionRDD.map(item => (item.session_id, item))
    val sessionId2action = sessionId2action1.groupByKey()
    //缓存持久化
    sessionId2action.cache()
    //流程里的第三步和第四步,获取关键信息数据,再根据主键user_id联立数据表user_info,添加用户信息字段,获得完整信息数据
    val sessionId2FullInfoRDD = getSessionFullInfo(sparkSession, sessionId2action)
    //先定义好累加器,使用前必须注册
    val sessionStatisticAccumulator = new SessionStatAccumulator
    sparkSession.sparkContext.register(sessionStatisticAccumulator)
    //流程里的第五第六步,过滤数据,然后更新累加器
    val sessionId2filteredRDD = getFilterRDD(sparkSession, taskParam, sessionStatisticAccumulator, sessionId2FullInfoRDD)
    //执行一个action操作,至此就是主体代码
    sessionId2filteredRDD.foreach(println(_))

    //以下是四个需求的方法声明
    //需求一:各范围session占比统计
    getSessionRatio(sparkSession, taskUUID, sessionStatisticAccumulator.value)
    // 需求二:Session随机抽取
    sessionRandomExtract(sparkSession, taskUUID, sessionId2FullInfoRDD)
    //需求三:Top10热门品类统计
    //先得到符合过滤条件的action数据
    val sessionId2FilteredActionRDD = sessionId2action1.join(sessionId2filteredRDD).map {

        case (session, (action, fullInfo)) => (session, action)
    }
    //为第四个需求准备,此时top10Category是Array(sortKey,fullInfo[cid|clickCount|orderCount|payCount])
    val top10Category = getTop10PopularCategories(sparkSession, taskUUID, sessionId2FilteredActionRDD)
    //需求四:统计每一个Top10热门品类的top10活跃session
    //sessionId2filteredRDD:过滤后符合条件的action:(sessionId,action)
    getTop10ActiveSession(sparkSession,taskUUID,top10Category,sessionId2FilteredActionRDD)
}

2. getBasicActionData()

getBasicActionData()方法,从数据表user_visit_action提取所有原始数据。

def getBasicActionData(sparkSession: SparkSession, taskParm: JSONObject) = {
    val startDate = ParamUtils.getParam(taskParm, Constants.PARAM_START_DATE)
    val endDate = ParamUtils.getParam(taskParm, Constants.PARAM_END_DATE)
    val sql = "select * from user_visit_action where date >='" + startDate + "' and date <= '" + endDate + "'"

    import sparkSession.implicits._
    sparkSession.sql(sql).as[UserVisitAction].rdd
  }

返回值解析:sparkSession.sql(sql)的返回值类型为DataFrame也就是DataSet[Row]; sparkSession.sql(sql).as[UserVisitAction]的返回值类型为DataSet[UserVisitAction]; sparkSession.sql(sql).as[UserVisitAction].rdd的返回值类型为rdd[UserVisitAction]

3. getSessionFullInfo()

getSessionFullInfo()方法,获得完整信息数据。
首先获取关键信息数据,再根据主键user_id联立数据表user_info,添加用户信息字段,从而获得完整信息数据

def getSessionFullInfo(sparkSession: SparkSession,
                         sessionId2action: RDD[(String, Iterable[UserVisitAction])]) = {
    //首先获取关键信息数据                     
    val userId2AggrInfoRDD = sessionId2action.map {
        case (sessionId, iterableAction) =>
            val startTime
            val endTime
            val searchKeyWords
            val clickCategories
            val userId
            val stepLength
            for (action <- iterableAction) { update field }
            //拼接数据字段
            val aggrInfo = sessionId | searchKw | clickCG | visitLength | stepLength | startTime     
            //返回以userId作为key的 键值对
            (userId, aggrInfo)     
    }
    //从用户信息表user_info提取数据,并转换为同样以user_id为主键的 键值对
    val sql = "select * from user_info"
    val userId2UserInfoRDD = sparkSession.sql(sql).as[UserInfo].rdd.map(item => (item.user_id, item))
    //联立两张表
    //userIdAggrInfoRDD --(userId,aggrInfo)
    //userId2UserInfo --(userId,UserInfo) 
    val session2FullInfoRDD = userId2AggrInfoRDD.join(userId2UserInfoRDD).map {
         case (userId, (aggrInfo, userInfo)) =>
             val fullInfo = aggrInfo | Age | Professional | Sex | City 
             //至此,已经得到了想要的完整信息数据 fullInfo,不再需要user_id作为key,而是,从aggrInfo中取出session_id作为key
             val sessionId = StringUtils.getFieldFromConcatString(aggrInfo, "\\|", Constants.FIELD_SESSION_ID)
             (sessionId, fullInfo)
    }
    session2FullInfoRDD
}                         

4. getFilteredRDD

getFilteredRDD()方法,根据条件获取过滤后的数据,并基于合格数据更新累加器

def getFilteredRDD(sparkSession: SparkSession,
                   taskParam: JSONObject,
                   sessionStatisticAccumulator: SessionStatisticAccumulator,
                   sessionId2FullInfoRDD: RDD[(String, String)]) :RDD[(String, String)]= {
    //传入过滤条件
    val startAge = ParamUtils.getParam(taskParam, Constants.PARAM_START_AGE)
    val endAge = 
    val professional = 
    val cities = 
    val sex = 
    val searchKeywords =
    val clickCategories =
    //拼接过滤条件
    val filterInfo = startAge= |endAge= |... |clickCategories=
    //执行过滤,获得过滤后的数据
    val sessionId2FilterRDD = sessionId2FullInfoRDD.filter{
        case(sessionId, fullInfo) =>
            var success = true
            if (!ValidUtils.between/in/equal(fullInfo, Field, filterInfo, Parma)){
                success = false
            }
            //判断success的状态,如果为true说明该sessionId2FullInfoRDD是满足条件的需要使用自定义累加器对相关需求累加
            if (success){
                //对符合筛选条件的session数目进行累加
                sessionStatisticAccumulator.add(Constants.SESSION_COUNT)

                //对session时长进行分段累加
                def calculateVisitLength(visitLength: Long): Unit = {
                if(){
                }else if(){
                ...}
                //对session步长进行分段累加
                def calculateStepLength(stepLength: Long)Unit ={if... else if...}
                // 从sessionId2FullInfoRDD中取出session时长和步长
                val stepLength = StringUtils.getFieldFromConcatString(fullInfo, "\\|", Constants.FIELD_STEP_LENGTH).toLong
                val visitLength = StringUtils.getFieldFromConcatString(fullInfo, "\\|", Constants.FIELD_VISIT_LENGTH).toLong

                // 判断session中的步长和时长位于哪个段中,累加
                calculateStepLength(stepLength)
                calculateVisitLength(visitLength)
            }
            success
    }
    sessionId2FilterRDD
}                

5.SessionStatisticAccumulator

自定义累加器,用于对session数目、各段步长数目和各段时长数目进行累计

class SessionStatisticAccumulator extends AccumulatorV2[String, mutable.HashMap[String, Int]]{
    // 累加器对象,真正存储的容器
    val countMap = new mutable.HashMap[String, Int]()
    // 返回这个累加器是否为零值
    override def isZero: Boolean = countMap.isEmpty
    // 复制得到这个累加器的一个新副本
    override def copy(): AccumulatorV2[String, mutable.HashMap[String, Int]] = {
        val acc = new SessionStatisticAccumulator
        acc.countMap ++= this.countMap
        acc
    }
    // 把累加器重置为零值
    override def reset(): Unit = countMap.clear()
    // Takes the inputs and accumulates.输入一个string,如果countMap里匹配到key,就加一,如果没有,就创建一个新key,加一
    override def add(v: String): Unit = {
        // 如果匹配不到,就添加一个key,其value为0
        if (!countMap.contains(v)){
            countMap += (v -> 0)
        }
        // 对该key的value加1
        countMap.update(v, countMap(v)+1)
    }
    // 将另一个相同类型的累加器合并到这个累加器中,并更新其状态
    override def merge(other: AccumulatorV2[String, mutable.HashMap[String, Int]]): Unit = {
    	other match {
            case acc:SessionStatisticAccumulator =>
                acc.countMap.foldLeft(this.countMap){
                case (map, (k, v))=> map += (k -> (map.getOrElse(k, 0) + v))
                }
        }
    }
     // 此累加器的当前值
    override def value: mutable.HashMap[String, Int] = {
        this.countMap
    }
}
  • sessionStat模块主体部分的代码解析就ok了,后续章节进行具体需求的代码实现。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值