场景

[2.0.0]已经对用户session分析模块的统计session访问时长占比功能点的需求分析进行了完美的解说。本文旨在对统计session访问时长占比的代码进行解读,并抽取核心spark知识点单独说明

分析

1、由action粒度的数据 map 得到 session粒度的数据;进一步group,得到聚合后的session粒度数据
2、join用户表数据得到,包含用户信息的session粒度数据
3、按照请求参数过滤session粒度数据的数据

代码架构

/**
         *  0、session粒度的聚合聚合:对用户session表信息进行session粒度的聚合,并且关联上用户相关信息
         */
        JavaRDD<Row> actionRDD = getActionRDDByDateRange(sqlContext,taskParam);
        JavaPairRDD<String, Row>  session2actionRDD = generateSess2ActionRDD(actionRDD);

        // session->Row
        //  JavaPairRDD<String, Row> sessionRDD = getSessionRDD(actionRDD);

        //Group(session) -> Iterable<Row>
        JavaPairRDD<String, Iterable<Row>> session2actionsRDD = session2actionRDD.groupByKey();

        // Key: userId ->  Value: String(partSessionInfo)
        JavaPairRDD<Long, String> userid2partAggSesionInfoRDD = getUserid2partAggSessionInfoRDD(session2actionsRDD);

        //Key: userId -> Value: userInfoRow
        JavaPairRDD<Long,Row> userid2userInfoRDD = getUserid2userInfoRDD(sqlContext);

        //Key : sessionid ->  Value : fullAggSessionInfo 
        JavaPairRDD<String, String> sessionid2fullAggSessionInfoRDD=
getSessionid2fullAggSessionInfoRDD(userid2partAggSesionInfoRDD, userid2userInfoRDD);

        /**
         * 1、统计过滤后session访问时长占比
         *  过滤:按照用户提交任务的其他查询参数,对聚合后的session信息进行过滤 
         */
        //自定义 Accumulator 进行计数
        Accumulator<String> sessionAggStatisticAccumulator = sc.accumulator("", new SessionAggStatisticAccumulator());

        JavaPairRDD<String, String> filteredSessionid2fullAggSessionInfoRDD=
getFilteredSessionid2fullAggSessionInfo(
taskParam,sessionid2fullAggSessionInfoRDD,sessionAggStatisticAccumulator);

        //将计算出的session访问占比写入mysql表中
        calculateAndPersistAggrStat(sessionAggStatisticAccumulator.value(),taskId);
解读

1、按日期对hive仓库中use_visit_action表数据进行筛选,得到 JavaRDD actionRDD
获取用户指定日期的action数据(注:行为数据,行为={搜索、点击、下单与支付})。‘日期’ 这个参数是j2ee前端必传的,在于大幅度过滤hive中的数据。use_visit_action表中的数据量可能达到PB级别,按日期过滤后的数据量可能就只有TB级别的数据量。

JavaRDD<Row> actionRDD = getActionRDDByDateRange(sqlContext,taskParam);

内部实现:getActionRDDByDateRange方法内部,通过HiveContext对象直接查询并过滤use_visit_action表中数据

String sql = 
            "select * "
                + " from user_visit_action "
                + " where date >='" + startDate +"'"
                + "and date <='"+ endDate +"'"; 
        DataFrame actionDF = sqlContext.sql(sql);

2、获取 session 级别的数据:JavaPairRDD

JavaPairRDD<String, Row>  session2actionRDD = generateSess2ActionRDD(actionRDD);
JavaPairRDD<String, Iterable<Row>> session2actionsRDD = session2actionRDD.groupByKey();

其中,session2actionsRDD的 Key:sessionid Value : actions (对应use_visit_action表中的多条记录)。

3、获取包含用户详情的session聚合信息:JavaPairRDD[String,String] sessionid2fullAggSessionInfoRDD

// Key: userId ->  Value: String(partSessionInfo)
        JavaPairRDD<Long, String> userid2partAggSesionInfoRDD = getUserid2partAggSessionInfoRDD(session2actionsRDD);        
        //Key: userId -> Value: userInfoRow
        JavaPairRDD<Long,Row> userid2userInfoRDD = getUserid2userInfoRDD(sqlContext);       
        //Key : sessionid ->  Value : fullAggSessionInfo 
        JavaPairRDD<String, String> sessionid2fullAggSessionInfoRDD=
getSessionid2fullAggSessionInfoRDD(userid2partAggSesionInfoRDD, userid2userInfoRDD);

其中,sessionid2fullAggSessionInfoRDD中的一条数据内容大致如下:

[123456]=[sessionid=123456|searchKeywords=music|clickCategoryIds=10,25,08|visitTimeLength=60s|visitStepLength=10|age=23|professional=教师|city=深圳|sex=woman]

注释:遍历聚合session的时候,随便计算出访问时长与访问步长。

4、按照用户提交的其他参数进一步过滤得到:JavaPairRDD[String, String] filteredSessionid2fullAggSessionInfoRDD

//自定义 Accumulator 进行计数
        Accumulator<String> sessionAggStatisticAccumulator = sc.accumulator("", new SessionAggStatisticAccumulator());  
        JavaPairRDD<String, String> filteredSessionid2fullAggSessionInfoRDD=    getFilteredSessionid2fullAggSessionInfo(
taskParam,sessionid2fullAggSessionInfoRDD,sessionAggStatisticAccumulator);  
        //将计算出的session访问占比写入mysql表中 calculateAndPersistAggrStat(sessionAggStatisticAccumulator.value(),taskId);

知识点

自定义累加器(accumulator)的使用

如何保存0-3s、3-5s … 1-3m … 10-30m等session访问时长占比呢?
当然,你可以为每个访问时长单独定义一个累加器,但是这样就以为着得定义几十个累加器 - 这样极容易出问题:本来session的访问时长为 4s,此时3-5s对应的accumulator值应该加一,由于人为的失误你将 0-3s的accumulator值加一了。更重要的是这样写的话,代码的可扩展性、可维护性极差!怎么解决这个问题呢?自定义累加器!

  • 自定义累加器
    自己写一个类实现AccumulatorParam(Helper object defining how to accumulate values of a particular type)接口,定义如何 ‘累加’ - (这里传递的泛型参数为String,当然也可以是其他 Object:Object如何自增呢?就在这个类里定义)
package cool.pengych.sparker.session;
import org.apache.spark.AccumulatorParam;

import cool.pengych.sparker.constant.Constants;
import cool.pengych.sparker.util.StringUtils;

/**
 * 自定义累加器
 * 求各session访问时长占比
 * @author pengyucheng
 */
public class SessionAggStatisticAccumulator implements AccumulatorParam<String> 
{
    private static final long serialVersionUID = 1L;

    /**
       * Merge two accumulated values together. Is allowed to modify and return the first value
       * for efficiency (to avoid allocating objects).
       *
       * @param v1 one set of accumulated data
       * @param v2 another set of accumulated data
       * @return both data sets merged together
       */
    @Override
    public String addInPlace(String v1, String v2) 
    {
        return add( v1,  v2);
    }

    /**
       * Return the "zero" (identity) value for an accumulator type, given its initial value. For
       * example, if R was a vector of N dimensions, this would return a vector of N zeroes. 这里只定义了一个 变量来存储所有的session访问时长
       */
    @Override
    public String zero(String R)
    {
        return Constants.SESSION_COUNT+"=0|"
                + Constants.TIME_PERIOD_1s_3s + "=0|"
                + Constants.TIME_PERIOD_4s_6s + "=0|"
                + Constants.TIME_PERIOD_7s_9s + "=0|"
                + Constants.TIME_PERIOD_10s_30s + "=0|"
                + Constants.TIME_PERIOD_30s_60s + "=0|"
                + Constants.TIME_PERIOD_1m_3m + "=0|"
                + Constants.TIME_PERIOD_3m_10m + "=0|"
                + Constants.TIME_PERIOD_10m_30m + "=0|"
                + Constants.TIME_PERIOD_30m + "=0|"
                + Constants.STEP_PERIOD_1_3 + "=0|"
                + Constants.STEP_PERIOD_4_6 + "=0|"
                + Constants.STEP_PERIOD_7_9 + "=0|"
                + Constants.STEP_PERIOD_10_30 + "=0|"
                + Constants.STEP_PERIOD_30_60 + "=0|"
                + Constants.STEP_PERIOD_60 + "=0";
    }

    /**
       * Add additional data to the accumulator value. Is allowed to modify and return `r`
       * for efficiency (to avoid allocating objects).
       *
       * @param v1 the current value of the accumulator
       * @param v2 the data to be added to the accumulator
       * @return the new value of the accumulator
       */
    @Override
    public String addAccumulator(String v1, String v2)
    {
        return add( v1,  v2);
    }

    /**
     * session累加计算逻辑
     * @param v1 连接串
     * @param v2 范围区间 Key
     * @return 更新以后的连接串
     */
    private String add(String v1, String v2)
    {
        // 当前值为空,直接返回 增加的值
        if(StringUtils.isEmpty(v1)) 
        {
            return v2;
        }
        // 使用StringUtils工具类,从v1中,提取v2对应的值,并累加1
        String oldValue = StringUtils.getFieldFromConcatString(v1, "\\|", v2);
        if(oldValue != null) 
        {
            // 将范围区间原有的值,累加1
            int newValue = Integer.valueOf(oldValue) + 1;
            // 使用StringUtils工具类,将v1中,v2对应的值,设置成新的累加后的值
            return StringUtils.setFieldInConcatString(v1, "\\|", v2, String.valueOf(newValue));  
        }
        return v1;
    }
}
  • 用法
    同普通累加器一样:先申明,再使用
Accumulator<String> sessionAggStatisticAccumulator = sc.accumulator("", new SessionAggStatisticAccumulator());
sessionAggStatisticAccumulator.add(Constants.SESSION_COUNT);
sessionAggStatisticAccumulator.value() // 获取累加器中的值

总结

  • RDD的命名一定要规范:filteredSessionid2fullAggSessionInfoRDD
  • 自定义累加器的使用有待后续进一步实战体验