32.Spark大型电商项目-用户访问session分析-session聚合统计之重构实现思路与重构session聚合

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/someby/article/details/88106122

目录

session聚合统计(统计出访问时长和访问步长,各个区间的session数量占总session数量的比例)

  如果不进行重构,直接来实现,思路

 普通实现思路的问题

 重构实现思路

 开发Spark大型复杂项目的一些经验准则

重构的代码

UserVisitSessionAnalyzeSpark.java

DateUtils.java

Constants.java


本篇文章将介绍用户访问session分析-session聚合统计之重构实现思路与重构session聚合。


session聚合统计(统计出访问时长和访问步长,各个区间的session数量占总session数量的比例)

 
 如果不进行重构,直接来实现,思路


 1、actionRDD,映射成<sessionid,Row>的格式
 2、按sessionid聚合,计算出每个session的访问时长和访问步长,生成一个新的RDD
 3、遍历新生成的RDD,将每个session的访问时长和访问步长,去更新自定义Accumulator中的对应的值
 4、使用自定义Accumulator中的统计值,去计算各个区间的比例
 5、将最后计算出来的结果,写入MySQL对应的表中


 普通实现思路的问题


 1、为什么还要用actionRDD,去映射?其实我们之前在session聚合的时候,映射已经做过了。多此一举
 2、是不是一定要,为了session的聚合这个功能,单独去遍历一遍session?其实没有必要,已经有session数据
         之前过滤session的时候,其实,就相当于,是在遍历session,那么这里就没有必要再过滤一遍了


 重构实现思路


 1、不要去生成任何新的RDD(处理上亿的数据)
 2、不要去单独遍历一遍session的数据(处理上千万的数据)
 3、可以在进行session聚合的时候,就直接计算出来每个session的访问时长和访问步长
 4、在进行过滤的时候,本来就要遍历所有的聚合session信息,此时,就可以在某个session通过筛选条件后
         将其访问时长和访问步长,累加到自定义的Accumulator上面去
 5、就是两种截然不同的思考方式,和实现方式,在面对上亿,上千万数据的时候,甚至可以节省时间长达
         半个小时,或者数个小时


 开发Spark大型复杂项目的一些经验准则


 1、尽量少生成RDD
 2、尽量少对RDD进行算子操作,如果有可能,尽量在一个算子里面,实现多个需要做的功能
 3、尽量少对RDD进行shuffle算子操作,比如groupByKey、reduceByKey、sortByKey(map、mapToPair)
         shuffle操作,会导致大量的磁盘读写,严重降低性能
         有shuffle的算子,和没有shuffle的算子,甚至性能,会达到几十分钟,甚至数个小时的差别
         有shfufle的算子,很容易导致数据倾斜,一旦数据倾斜,简直就是性能杀手(完整的解决方案)
 4、无论做什么功能,性能第一
         在传统的J2EE或者.NET后者PHP,软件/系统/网站开发中,我认为是架构和可维护性,可扩展性的重要
         程度,远远高于了性能,大量的分布式的架构,设计模式,代码的划分,类的划分(高并发网站除外)

         在大数据项目中,比如MapReduce、Hive、Spark、Storm,我认为性能的重要程度,远远大于一些代码
         的规范,和设计模式,代码的划分,类的划分;大数据,大数据,最重要的,就是性能
         主要就是因为大数据以及大数据项目的特点,决定了,大数据的程序和项目的速度,都比较慢
         如果不优先考虑性能的话,会导致一个大数据处理程序运行时间长度数个小时,甚至数十个小时
         此时,对于用户体验,简直就是一场灾难

         所以,推荐大数据项目,在开发和代码的架构中,优先考虑性能;其次考虑功能代码的划分、解耦合

        我们如果采用第一种实现方案,那么其实就是代码划分(解耦合、可维护)优先,设计优先
        如果采用第二种方案,那么其实就是性能优先

        讲了这么多,其实大家不要以为我是在岔开话题,大家不要觉得项目的课程,就是单纯的项目本身以及
        代码coding最重要,其实项目,我觉得,最重要的,除了技术本身和项目经验以外;非常重要的一点,就是
        积累了,处理各种问题的经验

重构的代码

UserVisitSessionAnalyzeSpark.java

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的起始和结束时间
                        Date startTime = null;
                        Date endTime = null;
                        // session的访问步长
                        int stepLength = 0;

                        // 遍历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 + ",");
                                }
                            }

                            // 计算session开始和结束时间
                            Date actionTime = DateUtils.parseTime(row.getString(4));

                            if(startTime == null) {
                                startTime = actionTime;
                            }
                            if(endTime == null) {
                                endTime = actionTime;
                            }

                            if(actionTime.before(startTime)) {
                                startTime = actionTime;
                            }
                            if(actionTime.after(endTime)) {
                                endTime = actionTime;
                            }

                            // 计算session访问步长
                            stepLength++;
                        }

                        String searchKeywords = StringUtils.trimComma(searchKeywordsBuffer.toString());
                        String clickCategoryIds = StringUtils.trimComma(clickCategoryIdsBuffer.toString());

                        // 计算session访问时长(秒)
                        long visitLength = (endTime.getTime() - startTime.getTime()) / 1000;

                        // 大家思考一下
                        // 我们返回的数据格式,即使<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 + "|"
                                + Constants.FIELD_VISIT_LENGTH + "=" + visitLength + "|"
                                + Constants.FIELD_STEP_LENGTH + "=" + stepLength;
                        //测试
                        //System.out.println("partAggrInfo: "+ partAggrInfo);

                        return new Tuple2<Long, String>(userid, partAggrInfo);
                    }

                });

DateUtils.java

 /**
     * 转化时间字符串
     * @param time 时间字符串
     * @return Date
     */

    public static Date parseTime(String time){
        try {
            return DATE_FORMAT.parse(time);
        } catch (Exception e){
            e.printStackTrace();
        }
        return  null;
    }

Constants.java

String FIELD_VISIT_LENGTH="visitLength";
String FIELD_STEP_LENGTH ="stepLength";

猜你喜欢

转载自blog.csdn.net/someby/article/details/88106122