22.Spark大型电商项目-用户访问session分析-按筛选参数对session粒度聚合数据进行过滤

目录

代码

UserVisitSessionAnalyzeSpark.java

Constants.java


本篇文章将介绍按筛选参数对session粒度聚合数据进行过滤。

代码

UserVisitSessionAnalyzeSpark.java

package main.xxx.java.test;

/**
 * FileName: UserVisitSessionAnlyizSpark
 * Author:   hadoop
 * Email:    3165845957@qq.com
 * Date:     19-3-1 上午10:41
 * Description:
 * 用户访问session分析Spark作业
 *
 * 接收用户创建的分析任务,用户可能指定的条件如下:
 *
 * 1、时间范围:起始日期~结束日期
 * 2、性别:男或女
 * 3、年龄范围
 * 4、职业:多选
 * 5、城市:多选
 * 6、搜索词:多个搜索词,只要某个session中的任何一个action搜索过指定的关键词,那么session就符合条件
 * 7、点击品类:多个品类,只要某个session中的任何一个action点击过某个品类,那么session就符合条件
 *
 * 我们的spark作业如何接受用户创建的任务?
 *
 * J2EE平台在接收用户创建任务的请求之后,会将任务信息插入MySQL的task表中,任务参数以JSON格式封装在task_param
 * 字段中
 *
 * 接着J2EE平台会执行我们的spark-submit shell脚本,并将taskid作为参数传递给spark-submit shell脚本
 * spark-submit shell脚本,在执行时,是可以接收参数的,并且会将接收的参数,传递给Spark作业的main函数
 * 参数就封装在main函数的args数组中
 *
 * 这是spark本身提供的特性
 */

import com.alibaba.fastjson.JSONObject;
import main.xxx.java.conf.ConfigurationManager;
import main.xxx.java.constant.Constants;
import main.xxx.java.dao.ITaskDAO;
import main.xxx.java.domain.Task;
import main.xxx.java.impl.DAOFactory;
import main.xxx.java.util.ParamUtils;
import main.xxx.java.util.StringUtils;
import main.xxx.java.util.ValidUtils;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.spark.SparkConf;
import org.apache.spark.SparkContext;
import org.apache.spark.api.java.JavaPairRDD;
import org.apache.spark.api.java.JavaRDD;
import org.apache.spark.api.java.JavaSparkContext;
import org.apache.spark.api.java.function.Function;
import org.apache.spark.api.java.function.PairFunction;
import org.apache.spark.sql.Dataset;
import org.apache.spark.sql.Row;
import org.apache.spark.sql.SQLContext;
import org.apache.spark.sql.hive.HiveContext;
import scala.Tuple2;

import java.util.Iterator;


/**
 * 用户访问session分析Spark作业
 *
 *
 */
public class UserVisitSessionAnalyzeSpark {

    public static void main(String[] args) {
        args = new String[]{"1"};
        // 构建Spark上下文
        SparkConf conf = new SparkConf()
                .setAppName(Constants.SPARK_APP_NAME_SESSION)
                .setMaster("local");
        JavaSparkContext sc = new JavaSparkContext(conf);
        SQLContext sqlContext = getSQLContext(sc.sc());

        // 生成模拟测试数据
        mockData(sc, sqlContext);
        //创建需要使用的DAO组件
        ITaskDAO taskDAO = DAOFactory.getTaskDAO();

        //首先查询书来指定的任务,并获取任务查询参数
        long taskid = ParamUtils.getTaskIdFromArgs(args);
        Task task = taskDAO.findById(taskid);
        JSONObject taskParam = JSONObject.parseObject(task.getTaskParam());

        // 如果要进行session粒度的数据聚合
        // 首先要从user_visit_action表中,查询出来指定日期范围内的行为数据
        JavaRDD<Row> actionRDD = getActionRDDByDateRange(sqlContext,taskParam);

        // 首先,可以将行为数据,按照session_id进行groupByKey分组
        // 此时的数据的粒度就是session粒度了,然后呢,可以将session粒度的数据
        // 与用户信息数据,进行join
        // 然后就可以获取到session粒度的数据,同时呢,数据里面还包含了session对应的user的信息
        JavaPairRDD<String,String> sessionid2AggrInfoRDD = aggregateBySession(sqlContext,actionRDD);
        sessionid2AggrInfoRDD.count();
        JavaPairRDD<String,String> filteredSessionid2InfoRDD = filterSession(sessionid2AggrInfoRDD,taskParam);
        filteredSessionid2InfoRDD.count();
        for(Tuple2<String,String> tuple : filteredSessionid2InfoRDD.take(10)){
            System.out.println(tuple);
        }


        // 关闭Spark上下文
        sc.close();
    }

    *//**
     * 获取SQLContext
     * 如果是在本地测试环境的话,那么就生成SQLContext对象
     * 如果是在生产环境运行的话,那么就生成HiveContext对象
     * @param sc SparkContext
     * @return SQLContext
     *//*
    private static SQLContext getSQLContext(SparkContext sc) {
        boolean local = ConfigurationManager.getBoolean(Constants.SPARK_LOCAL);
        if(local) {
            return new SQLContext(sc);
        } else {
            return new HiveContext(sc);
        }
    }

    *//**
     * 生成模拟数据(只有本地模式,才会去生成模拟数据)
     * @param sc
     * @param sqlContext
     *//*
    private static void mockData(JavaSparkContext sc, SQLContext sqlContext) {
        boolean local = ConfigurationManager.getBoolean(Constants.SPARK_LOCAL);
        if(local) {
            MockData.mock(sc, sqlContext);
        }
    }

    *//**
     * 获取指定日期范围内的用户访问行为数据
     * @param sqlContext SQLContext
     * @param taskParam 任务参数
     * @return 行为数据RDD
     *//*
    private static JavaRDD<Row> getActionRDDByDateRange(
            SQLContext sqlContext,JSONObject taskParam
    ){
        String startDate = ParamUtils.getParam(taskParam,Constants.PARAM_START_DATE);
        String endDate = ParamUtils.getParam(taskParam,Constants.PARAM_END_DATE );
        String sql = "select * "
                + "from user_visit_action "
                +"where date>='"+startDate +"'"
                + "and date <='"+endDate +"'";
        Dataset actionDF = sqlContext.sql(sql);
        return actionDF.javaRDD();

    }


    private static JavaPairRDD<String,String> aggregateBySession(
            SQLContext sqlContext,JavaRDD<Row> actionRDD
    ){
        // 现在actionRDD中的元素是Row,一个Row就是一行用户访问行为记录,比如一次点击或者搜索
        // 我们现在需要将这个Row映射成<sessionid,Row>的格式
        JavaPairRDD<String,Row> sessionid2ActionRDD = actionRDD.mapToPair(
                *//**
                 * PairFunction
                 * 第一个参数,相当于是函数的输入
                 * 第二个参数和第三个参数,相当于是函数的输出(Tuple),分别是Tuple第一个和第二个值
                 *//*
                new PairFunction<Row, String, Row>() {
                    private static final long serialVersionUID = 1L;
                    @Override
                    public Tuple2<String, Row> call(Row row) throws Exception {
                        return new Tuple2<String,Row>(row.getString(2),row);
                    }
                }
        );

        *//**
         * 对行为数据按session粒度进行分组
         *//*
        JavaPairRDD<String,Iterable<Row>> sessionid2ActionsRDD = sessionid2ActionRDD.groupByKey();
        // 对每一个session分组进行聚合,将session中所有的搜索词和点击品类都聚合起来
        // 到此为止,获取的数据格式,如下:<userid,partAggrInfo(sessionid,searchKeywords,clickCategoryIds)>
         JavaPairRDD<Long,String>  userid2PartAggrInfoRDD = sessionid2ActionsRDD.mapToPair(
                 new PairFunction<Tuple2<String, Iterable<Row>>, Long, String>() {
                     private static final long serialVersionUID = 1L;

                     @Override
                     public Tuple2<Long, String> call(Tuple2<String, Iterable<Row>> tuple) throws Exception {

                         String sessionid = tuple._1;
                         Iterator<Row> iterator = tuple._2.iterator();
                         StringBuffer searchKeywordsBuffer = new StringBuffer("");
                         StringBuffer clickCategoryIdsBuffer = new StringBuffer("");
                         Long userid = null;
                         //遍历session所有的访问行为
                         while (iterator.hasNext()) {
//                             获取每一个访问行为的搜索字段和点击品类字段
                             Row row = iterator.next();
                             if (userid == null) {
                                 userid = row.getLong(1);
                             }
                             String searchKeyword = row.getString(5);
                             Long clickCategoryId = row.getLong(6);
                             //实际上这里要对数据说明一下
                             // 并不是每一行访问行为都有searchKeyword何clickCategoryId两个字段的
                             // 其实,只有搜索行为,是有searchKeyword字段的
                             // 只有点击品类的行为,是有clickCategoryId字段的
                             // 所以,任何一行行为数据,都不可能两个字段都有,所以数据是可能出现null值的

                             // 我们决定是否将搜索词或点击品类id拼接到字符串中去
                             // 首先要满足:不能是null值
                             // 其次,之前的字符串中还没有搜索词或者点击品类id
                             if (StringUtils.isNotEmpty(searchKeyword)) {
                                 if (!searchKeywordsBuffer.toString().contains(searchKeyword)) {
                                     searchKeywordsBuffer.append(searchKeyword + ",");
                                 }
                             }
                             if (clickCategoryId != null) {
                                 if (!clickCategoryIdsBuffer.toString().contains(String.valueOf(clickCategoryId))) {
                                     clickCategoryIdsBuffer.append(clickCategoryId + ",");
                                 }
                             }

                         }
                         String searchKeywords = StringUtils.trimComma(searchKeywordsBuffer.toString());
                         String clickCategoryIds = StringUtils.trimComma(clickCategoryIdsBuffer.toString());
                         // 大家思考一下
                         // 我们返回的数据格式,即使<sessionid,partAggrInfo>
                         // 但是,这一步聚合完了以后,其实,我们是还需要将每一行数据,跟对应的用户信息进行聚合
                         // 问题就来了,如果是跟用户信息进行聚合的话,那么key,就不应该是sessionid
                         // 就应该是userid,才能够跟<userid,Row>格式的用户信息进行聚合
                         // 如果我们这里直接返回<sessionid,partAggrInfo>,还得再做一次mapToPair算子
                         // 将RDD映射成<userid,partAggrInfo>的格式,那么就多此一举

                         // 所以,我们这里其实可以直接,返回的数据格式,就是<userid,partAggrInfo>
                         // 然后跟用户信息join的时候,将partAggrInfo关联上userInfo
                         // 然后再直接将返回的Tuple的key设置成sessionid
                         // 最后的数据格式,还是<sessionid,fullAggrInfo>

                         // 聚合数据,用什么样的格式进行拼接?
                         // 我们这里统一定义,使用key=value|key=value

                         String partAggrInfo = Constants.FIELD_SESSION_ID +"="+ sessionid +"|"
                                 +Constants.FIELD_SEARCH_KEYWORDS +"=" +searchKeywords +"|"
                                 +  Constants.FIELD_CLICK_CATEGORY_IDS +"="+ clickCategoryIds;
                         return new Tuple2<Long,String>(userid,partAggrInfo);

                     }
                 });
         //查询所有用户的数据,并映射成<userid,Row>的格式
        String sql = "select * from user_info";
        JavaRDD<Row> userInfoRDD = sqlContext.sql(sql).javaRDD();

        JavaPairRDD<Long ,Row> userid2InfoRDD = userInfoRDD.mapToPair(
                new PairFunction<Row, Long, Row>() {
                    private static final long serialVersionUID = 1L;

                    @Override
                    public Tuple2<Long, Row> call(Row row) throws Exception {
                        return new Tuple2<Long,Row>(row.getLong(0),row);
                    }
                }
        );
        //将session粒度聚合数据,与用户信息进行join
        JavaPairRDD<Long,Tuple2<String,Row>> userid2FullInfoRDD = userid2PartAggrInfoRDD.join(userid2InfoRDD);

        //对join起来的数据进行拼接,并且返回<sessionid,fullAggrInfo>格式数据
        JavaPairRDD<String,String> sessionid2FullAggrInfoRDD = userid2FullInfoRDD.mapToPair(
                new PairFunction<Tuple2<Long, Tuple2<String, Row>>, String, String>() {
                    private static final long serialVersionUID = 1L;
                    @Override
                    public Tuple2<String, String> call(Tuple2<Long, Tuple2<String, Row>> tuple) throws Exception {
                        String partAggrInfo = tuple._2._1;
                        Row userInfoRow = tuple._2._2;
                        String sessionid = StringUtils.getFieldFromConcatString(partAggrInfo,"\\|",Constants.FIELD_SESSION_ID);
                        int age = userInfoRow.getInt(3);
                        String professional = userInfoRow.getString(4);
                        String city = userInfoRow.getString(5);
                        String sex = userInfoRow.getString(6);

                        String fullAggrInfo = partAggrInfo +"|"
                                + Constants.FILED_AGE + "+" +age+"|"
                                + Constants.FIELD_PREFESSIONAL +"="+ professional+"|"
                                + Constants.FIELD_CITY + "=" +city+"|"
                                + Constants.FIELD_SEX + "="+sex ;

                        return new Tuple2<String,String>(sessionid,fullAggrInfo);
                    }
                }
        );


        return sessionid2FullAggrInfoRDD;
    }
    private static JavaPairRDD<String,String> filterSession(
            JavaPairRDD<String,String> sessionid2AggrInfoRDD,final JSONObject taskParam
    ){
        String startAge = ParamUtils.getParam(taskParam,Constants.PARAM_START_AGE);
        String endAge = ParamUtils.getParam(taskParam,Constants.PARAM_END_AGE);
        String professionals = ParamUtils.getParam(taskParam, Constants.PARAM_PROFESSIONALS);
        String cities  = ParamUtils.getParam(taskParam,Constants.PARAM_CITIES);
        String sex = ParamUtils.getParam(taskParam,Constants.PARAM_SEX);
        String keywords = ParamUtils.getParam(taskParam,Constants.PARAM_KEYWORDS);
        String categoryIds = ParamUtils.getParam(taskParam,Constants.PARAM_CATEGORY_IDS);
        String _paramter = (startAge != null ? Constants.PARAM_START_AGE +"="+startAge+"|":"")
                + (endAge != null ? Constants.PARAM_END_AGE +"="+endAge+"|":"")
                +(professionals != null ? Constants.PARAM_PROFESSIONALS +"="+professionals+"|":"")
                +(cities != null ? Constants.PARAM_CITIES +"="+cities+"|":"")
                +(sex != null ? Constants.PARAM_SEX +"="+sex+"|":"")
        +(keywords != null ? Constants.PARAM_KEYWORDS +"="+keywords+"|":"")
                +(categoryIds != null ? Constants.PARAM_CATEGORY_IDS +"="+categoryIds+"|":"");
        if (_paramter.endsWith("\\|")){
            _paramter = _paramter.substring(0,_paramter.length()-1);
        }
        final String paramter = _paramter;
        JavaPairRDD<String,String> filteredSessionid2AggrInfoRDD = sessionid2AggrInfoRDD.filter(
                new Function<Tuple2<String, String>, Boolean>() {
                    private static final long serialVersionUID = 1L;
                    @Override
                    public Boolean call(Tuple2<String, String> tuple) throws Exception {
                        //首先,从tuple中获取聚合数据
                        String aggrInfo = tuple._2;

                        // 接着,依次按照筛选条件进行过滤
                        // 按照年龄范围进行过滤(startAge、endAge)
                        if (!ValidUtils.between(aggrInfo,Constants.FILED_AGE,paramter,Constants.PARAM_START_AGE,Constants.PARAM_END_AGE)){
                            return false;
                        }
                        // 按照职业范围进行过滤(professionals)
                        // 互联网,IT,软件
                        // 互联网
                        if (!ValidUtils.in(aggrInfo,Constants.FIELD_PREFESSIONAL,paramter,Constants.PARAM_PROFESSIONALS)){
                            return false;
                        }
                        // 按照城市范围进行过滤(cities)
                        // 北京,上海,广州,深圳
                        // 成都
                        if(!ValidUtils.in(aggrInfo,Constants.FIELD_CITY,paramter,Constants.PARAM_CITIES )){
                            return false;
                        }
                        // 按照性别进行过滤
                        // 男/女
                        // 男,女
                        if (!ValidUtils.equal(aggrInfo,Constants.FIELD_SEX,paramter,Constants.PARAM_SEX)){
                            return false;
                        }
                        // 按照搜索词进行过滤
                        // 我们的session可能搜索了 火锅,蛋糕,烧烤
                        // 我们的筛选条件可能是 火锅,串串香,iphone手机
                        // 那么,in这个校验方法,主要判定session搜索的词中,有任何一个,与筛选条件中
                        // 任何一个搜索词相当,即通过

                        if (!ValidUtils.in(aggrInfo,Constants.FIELD_SEARCH_KEYWORDS,paramter,Constants.PARAM_KEYWORDS)){
                            return false;
                        }
                        // 按照点击品类id进行过滤
                        if (!ValidUtils.in(aggrInfo,Constants.FIELD_CLICK_CATEGORY_IDS,paramter,Constants.PARAM_CATEGORY_IDS)){
                            return false;
                        }
                        return true;

                    }
                }
        );
        return filteredSessionid2AggrInfoRDD;
    }

}

Constants.java

    String FIELD_SEARCH_KEYWORDS = "searchKeyword";
    String FIELD_CLICK_CATEGORY_IDS = "clickCategoryIds";
    String FIELD_AGE = "age";
    String FIELD_PROFESSIONAL  = "prefessional";
    String FIELD_CITY = "city";
    String  FIELD_SEX = "sex";

    /**
     * 任务相关的常量
     */
    String PARAM_START_DATE = "startDate";
    String PARAM_END_DATE = "endDate";
    String PARAM_START_AGE="startAge";
    String PARAM_END_AGE="endAge";
    String PARAM_PROFESSIONALS="professionals";
    String PARAM_CITIES="cities";
    String PARAM_SEX="sex";
    String PARAM_KEYWORDS="keywords";
    String PARAM_CATEGORY_IDS="categoryIds";

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值