大数据项目(三)————电商模块二(三)

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

1、模块二介绍——页面单跳转化率

这里写图片描述

页面单跳转化率,计算出来以后,还是蛮有用的,蛮有价值的。

产品经理,可以根据这个指标,去尝试分析,整个网站,产品,各个页面的表现怎么样,是不是需要去优化产品的布局;吸引用户最终可以进入最后的支付页面;

数据分析师,可以基于咱们的这个数据,做更深一步的计算和分析

企业管理层,可以看到整个公司的网站,各个页面的之间的跳转的表现,如何?心里有数,可以适当调整公司的经营战略或策略

2、需求分析
这里写图片描述

基本的需求:

1、接收J2EE系统传入进来的taskid,从mysql查询任务的参数,日期范围、页面流id
2、针对指定范围日期内的用户访问行为数据,去判断和计算,页面流id中,每两个页面组成的页面切片,它的访问量是多少
3、根据指定页面流中各个页面切片的访问量,计算出来各个页面切片的转化率
4、计算出来的转化率,写入mysql数据库中

3、技术方案设计

用户指定的页面流id:
3,5,7,9,10,21

页面3->页面5的转换率是多少;
页面5->页面7的转化率是多少;
页面7->页面9的转化率是多少;

页面3->页面5的访问量是多少;页面5到页面7的访问量是多少;两两相除,就可以计算出来

实现步骤:
1、获取任务的日期范围参数
2、查询指定日期范围内的用户访问行为数据
3、获取用户访问行为中,每个session,计算出各个在指定页面流中的页面切片的访问量;实现,页面单跳切片生成以及页面流匹配的算法;session,3->8->7,3->5->7,是不匹配的;
4、计算出符合页面流的各个切片的pv(访问量)
5、针对用户指定的页面流,去计算各个页面单跳切片的转化率
6、将计算结果持久化到数据库中

这里写图片描述

数据表设计
这里写图片描述

4、代码实现

4.1 页面切片生成以及页面流量匹配算法

package cn.ctgu.sparkproject.spark.page;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.spark.SparkConf;
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.PairFlatMapFunction;
import org.apache.spark.api.java.function.PairFunction;
import org.apache.spark.broadcast.Broadcast;
import org.apache.spark.sql.Row;
import org.apache.spark.sql.SQLContext;

import scala.Tuple2;

import com.alibaba.fastjson.JSONObject;
import cn.ctgu.sparkproject.constant.Constants;
import cn.ctgu.sparkproject.dao.ITaskDAO;
import cn.ctgu.sparkproject.dao.factory.DAOFactory;
import cn.ctgu.sparkproject.domain.Task;
import cn.ctgu.sparkproject.util.DateUtils;
import cn.ctgu.sparkproject.util.ParamUtils;
import cn.ctgu.sparkproject.util.SparkUtils;

/**
 * 页面单跳转化率模块spark作业
 * @author Administrator
 *
 */
public class PageOneStepConvertRateSpark {

    public static void main(String[] args) {
        // 1、构造Spark上下文
        SparkConf conf = new SparkConf()
                .setAppName(Constants.SPARK_APP_NAME_PAGE);
        SparkUtils.setMaster(conf);  

        JavaSparkContext sc = new JavaSparkContext(conf);
        SQLContext sqlContext = SparkUtils.getSQLContext(sc.sc());

        // 2、生成模拟数据
        SparkUtils.mockData(sc, sqlContext);  

        // 3、查询任务,获取任务的参数
        long taskid = ParamUtils.getTaskIdFromArgs(args, Constants.SPARK_LOCAL_TASKID_PAGE);

        ITaskDAO taskDAO = DAOFactory.getTaskDAO();
        Task task = taskDAO.findById(taskid);
        if(task == null) {
            System.out.println(new Date() + ": cannot find this task with id [" + taskid + "].");  
            return;
        }

        JSONObject taskParam = JSONObject.parseObject(task.getTaskParam());

        // 4、查询指定日期范围内的用户访问行为数据
        JavaRDD<Row> actionRDD = SparkUtils.getActionRDDByDateRange(
                sqlContext, taskParam);

        // 对用户访问行为数据做一个映射,将其映射为<sessionid,访问行为>的格式
        // 咱们的用户访问页面切片的生成,是要基于每个session的访问数据,来进行生成的
        // 脱离了session,生成的页面访问切片,是么有意义的
        // 举例,比如用户A,访问了页面3和页面5
        // 用于B,访问了页面4和页面6
        // 漏了一个前提,使用者指定的页面流筛选条件,比如页面3->页面4->页面7
        // 你能不能说,是将页面3->页面4,串起来,作为一个页面切片,来进行统计呢
        // 当然不行
        // 所以说呢,页面切片的生成,肯定是要基于用户session粒度的

        JavaPairRDD<String, Row> sessionid2actionRDD = getSessionid2actionRDD(actionRDD);

        // 对<sessionid,访问行为> RDD,做一次groupByKey操作
        // 因为我们要拿到每个session对应的访问行为数据,才能够去生成切片
        JavaPairRDD<String, Iterable<Row>> sessionid2actionsRDD = sessionid2actionRDD.groupByKey();

        // 最核心的一步,每个session的单跳页面切片的生成,以及页面流的匹配,算法
        JavaPairRDD<String, Integer> pageSplitRDD = generateAndMatchPageSplit(
                sc, sessionid2actionsRDD, taskParam);
        Map<String, Object> pageSplitPvMap = pageSplitRDD.countByKey();
    }

    /**
     * 获取<sessionid,用户访问行为>格式的数据
     * @param actionRDD 用户访问行为RDD
     * @return <sessionid,用户访问行为>格式的数据
     */
    private static JavaPairRDD<String, Row> getSessionid2actionRDD(
            JavaRDD<Row> actionRDD) {
        return actionRDD.mapToPair(new PairFunction<Row, String, Row>() {

            private static final long serialVersionUID = 1L;

            @Override
            public Tuple2<String, Row> call(Row row) throws Exception {
                String sessionid = row.getString(2);
                return new Tuple2<String, Row>(sessionid, row);   
            }

        });
    }

    /**
     * 页面切片生成与匹配算法
     * @param sc 
     * @param sessionid2actionsRDD
     * @param taskParam
     * @return
     */
    private static JavaPairRDD<String, Integer> generateAndMatchPageSplit(
            JavaSparkContext sc,
            JavaPairRDD<String, Iterable<Row>> sessionid2actionsRDD,
            JSONObject taskParam) {
        String targetPageFlow = ParamUtils.getParam(taskParam, Constants.PARAM_TARGET_PAGE_FLOW);
        final Broadcast<String> targetPageFlowBroadcast = sc.broadcast(targetPageFlow);

        return sessionid2actionsRDD.flatMapToPair(

                new PairFlatMapFunction<Tuple2<String,Iterable<Row>>, String, Integer>() {

                    private static final long serialVersionUID = 1L;

                    @Override
                    public Iterable<Tuple2<String, Integer>> call(
                            Tuple2<String, Iterable<Row>> tuple)
                            throws Exception {
                        // 定义返回list
                        List<Tuple2<String, Integer>> list = 
                                new ArrayList<Tuple2<String, Integer>>();
                        // 获取到当前session的访问行为的迭代器
                        Iterator<Row> iterator = tuple._2.iterator();
                        // 获取使用者指定的页面流
                        // 使用者指定的页面流,1,2,3,4,5,6,7
                        // 1->2的转化率是多少?2->3的转化率是多少?
                        String[] targetPages = targetPageFlowBroadcast.value().split(",");  

                        // 这里,我们拿到的session的访问行为,默认情况下是乱序的
                        // 比如说,正常情况下,我们希望拿到的数据,是按照时间顺序排序的
                        // 但是问题是,默认是不排序的
                        // 所以,我们第一件事情,对session的访问行为数据按照时间进行排序

                        // 举例,反例
                        // 比如,3->5->4->10->7
                        // 3->4->5->7->10
                        // 排序

                        List<Row> rows = new ArrayList<Row>();
                        while(iterator.hasNext()) {
                            rows.add(iterator.next());  
                        }

                        Collections.sort(rows, new Comparator<Row>() {

                            @Override
                            public int compare(Row o1, Row o2) {
                                String actionTime1 = o1.getString(4);
                                String actionTime2 = o2.getString(4);

                                Date date1 = DateUtils.parseTime(actionTime1);
                                Date date2 = DateUtils.parseTime(actionTime2);

                                return (int)(date1.getTime() - date2.getTime());
                            }

                        });

                        // 页面切片的生成,以及页面流的匹配
                        Long lastPageId = null;

                        for(Row row : rows) {
                            long pageid = row.getLong(3);

                            if(lastPageId == null) {
                                lastPageId = pageid;
                                continue;
                            }

                            // 生成一个页面切片
                            // 3,5,2,1,8,9
                            // lastPageId=3
                            // 5,切片,3_5

                            String pageSplit = lastPageId + "_" + pageid;

                            // 对这个切片判断一下,是否在用户指定的页面流中
                            for(int i = 1; i < targetPages.length; i++) {
                                // 比如说,用户指定的页面流是3,2,5,8,1
                                // 遍历的时候,从索引1开始,就是从第二个页面开始
                                // 3_2
                                String targetPageSplit = targetPages[i - 1] + "_" + targetPages[i];

                                if(pageSplit.equals(targetPageSplit)) {
                                    list.add(new Tuple2<String, Integer>(pageSplit, 1));  
                                    break;
                                }
                            }

                            lastPageId = pageid;
                        }

                        return list;
                    }

                });
    }

}

4.2 计算页面流其实页面的pv

/*
     * 获取页面流中初始页面的pv
     * 
     * */
    private  static long getStartPagePv(JSONObject taskParam,
            JavaPairRDD<String,Iterable<Row>>sessionid2actionRDD) {
        String targetPageFlow=ParamUtils.getParam(taskParam,
                Constants.PARAM_TARGET_PAGE_FLOW);
        final long startPageId=Long.valueOf(targetPageFlow.split(",")[0]);

        JavaRDD<Long>startPageRDD=sessionid2actionRDD.flatMap(
                new FlatMapFunction<Tuple2<String,Iterable<Row>>,Long>() {

                    private static final long serialVersionUID = 1L;

                    @Override
                    public Iterable<Long> call(
                            Tuple2<String, Iterable<Row>> tuple)
                            throws Exception {
                        List<Long>list=new ArrayList<Long>();
                        Iterator<Row>iterator=tuple._2.iterator();

                        while(iterator.hasNext()) {
                            Row row=iterator.next();
                            long pageid=row.getLong(3);

                            if(pageid==startPageId) {
                                list.add(pageid);
                            }
                        }

                        return list;
                    }

        });
        return startPageRDD.count();
    }

4.3 计算页面切片的转化率

/*
     * 计算页面切片转化率
     * 
     * */
    private static Map<String,Double>computePageSplitConvertRate(
            JSONObject taskParam,
            Map<String,Object>pageSplitPvMap,
            long startPagePv){

        Map<String,Double>convertRateMap=new HashMap<String,Double>();

        String[] targetPages=ParamUtils.getParam(taskParam,
                Constants.PARAM_TARGET_PAGE_FLOW).split(",");
        long lastPageSplitPv=0L;

        //3,5,2,4,6
        //3_5
        //转化率:3_5 pv /3 pv
        //5_2 rate=5_2 pv/3_5 pv

        //通过for循环,获取目标页面流中的各个页面切片(pv)
        for(int i=1;i<targetPages.length;i++) {
            String targetPageSplit=targetPages[i-1]+"_"+targetPages[i];
            long targetPageSplitPv=Long.valueOf(String.valueOf(
                    pageSplitPvMap.get(targetPageSplit)));
            double convertRate=0.0;
            if(i==1) {
                convertRate=NumberUtils.formatDouble(
                        (double)targetPageSplitPv/(double)startPagePv, 2);

            }else {
                convertRate=NumberUtils.formatDouble(
                        (double)(targetPageSplitPv)/(double)lastPageSplitPv, 2);
            }
            convertRateMap.put(targetPageSplit, convertRate);

            lastPageSplitPv=targetPageSplitPv;
        }
        return convertRateMap;


    }

4.4 将转化率写进MySQL

/*
     * 持久化转化率
     * 
     * */
    private static void persistConvertRate(long taskid,
            Map<String,Double>convertRateMap) {
        StringBuffer buffer=new StringBuffer("");
        for(Map.Entry<String, Double>convertRateEntry:convertRateMap.entrySet()) {
            String pageSplit=convertRateEntry.getKey();
            double convertRate=convertRateEntry.getValue();
            buffer.append(pageSplit+"="+convertRate+"|");
        }
        String convertRate=buffer.toString();
        convertRate=convertRate.substring(0,convertRate.length()-1);

        PageSplitConvertRate pageSplitConvertRate=new PageSplitConvertRate();
        pageSplitConvertRate.setTaskid(taskid);
        pageSplitConvertRate.setConvertRate(convertRate);

        IPageSplitConvertRateDAO pageSplitConvertRateDAO=DAOFactory.getPageSplitConvertRateDAO();
        pageSplitConvertRateDAO.insert(pageSplitConvertRate);
    }

5、完整代码

package cn.ctgu.sparkproject.spark.page;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.spark.SparkConf;
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.FlatMapFunction;
import org.apache.spark.api.java.function.PairFlatMapFunction;
import org.apache.spark.api.java.function.PairFunction;
import org.apache.spark.broadcast.Broadcast;
import org.apache.spark.sql.Row;
import org.apache.spark.sql.SQLContext;

import com.alibaba.fastjson.JSONObject;

import cn.ctgu.sparkproject.constant.Constants;
import cn.ctgu.sparkproject.dao.IPageSplitConvertRateDAO;
import cn.ctgu.sparkproject.dao.ITaskDAO;
import cn.ctgu.sparkproject.dao.factory.DAOFactory;
import cn.ctgu.sparkproject.domain.PageSplitConvertRate;
import cn.ctgu.sparkproject.domain.Task;
import cn.ctgu.sparkproject.util.DateUtils;
import cn.ctgu.sparkproject.util.NumberUtils;
import cn.ctgu.sparkproject.util.ParamUtils;
import cn.ctgu.sparkproject.util.SparkUtils;
import scala.Tuple2;

/*
 * 页面单跳转化率模块
 * 
 * 
 * */
public class PageOneStepConvertRateSpark {

    public static void main(String[] args) {
        //1、构造spark上下文
        SparkConf conf=new SparkConf()
                .setAppName(Constants.SPARK_APP_NAME_PAGE);
        SparkUtils.setMaster(conf);     

        JavaSparkContext sc=new JavaSparkContext();
        SQLContext sqlContext=SparkUtils.getSQLContext(sc.sc());

        //2、生成模拟数据
        SparkUtils.mockData(sc, sqlContext);

        //3、查询任务,获取任务的参数
        long taskid=ParamUtils.getTaskIdFromArgs(args,Constants.SPARK_LOCAL_TASKID_PAGE);

        ITaskDAO taskDAO=DAOFactory.getTaskDAO();
        Task task=taskDAO.findById(taskid);
        if(task==null) {
            System.out.println(new Date()+": cannot find this task with id["+taskid+"].");
            return;
        }
        JSONObject taskParam=JSONObject.parseObject(task.getTaskParam());

        //4、查询指定日期范围内的用户访问行为数据
        JavaRDD<Row>actionRDD=SparkUtils.getActionRDDByDateRange(sqlContext, taskParam);

        /*
         * 对用户访问行为数据做一个映射,将其映射为<sessionid,访问行为>的格式
         * 咱们的用户访问页面切片的生成是要基于每个session的访问数据来进行生成的
         * 脱离了session,生成的页面访问切片是没有意义的
         * 比如:用户A访问了页面3和页面5
         * 用户B访问了页面4和页面6
         * 漏了一个前提,使用者指定的页面筛选条件,比如页面3->页面4->页面7
         * 将页面3->页面4穿起来作为一个页面切片来进行统计是不行的
         * 所以说页面切片的生成是要基于用户session粒度的
         * 
         * */
        JavaPairRDD<String,Row>sessionid2actionRDD=getSessionid2actionRDD(actionRDD);
        sessionid2actionRDD=sessionid2actionRDD.cache();//相当于persist(StorageLevl.MEMORY_ONLY)

        //对<sessionid,访问行为>RDD,做一次groupByKey操作
        //因为要拿到每个session对应的访问行为数据才能够去生成切片

        JavaPairRDD<String,Iterable<Row>>sessionid2actionsRDD=sessionid2actionRDD.groupByKey();

        //最核心的一步,每个session的单跳页面切片的生成以及页面流的匹配算法
        JavaPairRDD<String,Integer>pageSplitRDD=generateAndMatchPageSplit(
                sc,sessionid2actionsRDD,taskParam);

        Map<String,Object> pageSplitPvMap=pageSplitRDD.countByKey();

        //使用者指定的页面流是3,2,5,8,6
        //咱们现在拿到的这个pageSplitPvMap就是3->2,2->5,5->8,8->6
        long startPagePv=getStartPagePv(taskParam,sessionid2actionsRDD);

        //计算目标页面流的各个页面切片的转化率
        Map<String,Double>convertRateMap=computePageSplitConvertRate(
                taskParam,pageSplitPvMap,startPagePv);
        //持久化页面切片转化率
        persistConvertRate(taskid, convertRateMap);

    }

    /*
     * 获取<sessionid,用户访问行为>格式的数据
     * 
     * */
    private static JavaPairRDD<String,Row> getSessionid2actionRDD(
            JavaRDD<Row>actionRDD) {
        return actionRDD.mapToPair(new PairFunction<Row,String,Row>(){

            private static final long serialVersionUID = 1L;

            @Override
            public Tuple2<String, Row> call(Row row) throws Exception {

                String sessionid=row.getString(2);
                return new Tuple2<String,Row>(sessionid,row);
            }


        });
    }

    /*
     * 页面切片生成与匹配算法
     * 
     * */
    private static JavaPairRDD<String,Integer>generateAndMatchPageSplit(
            JavaSparkContext sc,
            JavaPairRDD<String,Iterable<Row>>sessionid2actionsRDD,
            JSONObject taskParam){

        String targetPageFlow=ParamUtils.getParam(taskParam, Constants.PARAM_TARGET_PAGE_FLOW);
        final Broadcast<String>targetPageFlowBroadcast=sc.broadcast(targetPageFlow);

        return sessionid2actionsRDD.flatMapToPair(
                new PairFlatMapFunction<Tuple2<String,Iterable<Row>>,String,Integer>(){

                    private static final long serialVersionUID = 1L;

                    @Override
                    public Iterable<Tuple2<String, Integer>> call(
                            Tuple2<String, Iterable<Row>> tuple)
                            throws Exception {
                    //定义返回list
                    List<Tuple2<String,Integer>>list=
                            new ArrayList<Tuple2<String,Integer>>();
                    //获取到当前session的访问行为的迭代器
                    Iterator<Row>iterator=tuple._2.iterator();
                    //获取使用者指定的页面流
                    //使用者指定的页面流,1,2,3,4,5,6,7
                    //1->2的转化率是多少?2->3的转化率是多少?
                    String[] targetPages=targetPageFlowBroadcast.value().split(",");

                    //这里,我们拿到的session的访问行为,默认情况下是乱序的
                    //比如说,正常情况下,我们希望拿到的数据是按照时间顺序排序的
                    //但是问题是,默认是不排序的
                    //所以需要对session的访问行为数据按照时间进行排序

                    //比如:访问的session顺序为:3->5->4->10->7
                    //但是我们拿到的可能是:3->4->5->7->10
                    //所以需要根据时间进行排序
                    List<Row>rows=new ArrayList<Row>();
                    while(iterator.hasNext()) {
                        rows.add(iterator.next());
                    }
                    Collections.sort(rows,new Comparator<Row>() {

                        @Override
                        public int compare(Row o1, Row o2) {

                            String actionTime1=o1.getString(4);
                            String actionTime2=o2.getString(4);

                            Date date1=DateUtils.parseTime(actionTime1);
                            Date date2=DateUtils.parseTime(actionTime2);


                            return (int)(date1.getTime()-date2.getTime());
                        }

                    });

                    //页面切片的生成和页面流的匹配
                    Long lastPageId=null;
                    for(Row row:rows) {
                        long pageid=row.getLong(3);

                        if(lastPageId==null) {
                            lastPageId=pageid;
                            continue;
                        }
                        //生成一个页面切片(页面切片的对象是两个连着的页面)
                        //比如:访问顺序为 3,5,2,1,8,9
                        //lastPageId=3
                        //5,切片为3_5
                        String pageSplit=lastPageId+"_"+pageid;

                        //对这个切片判断一下,是否在用户指定的页面流中
                        for(int i=1;i<targetPages.length;i++) {
                            //比如说用户指定的页面流是3,2,5,8,1
                            //遍历的时候,从索引1开始就是从第二个页面开始
                            //3_2 2_5 5_8

                            String targetPageSplit=targetPages[i-1]+"_"+targetPages[i];

                            if(pageSplit.equals(targetPageSplit)) {
                                //匹配成功
                                list.add(new Tuple2<String,Integer>(pageSplit,1));
                                break;
                            }
                        }
                        lastPageId=pageid;

                    }

                        return list;
                    }

                });
    }
    /*
     * 获取页面流中初始页面的pv
     * 
     * */
    private  static long getStartPagePv(JSONObject taskParam,
            JavaPairRDD<String,Iterable<Row>>sessionid2actionRDD) {
        String targetPageFlow=ParamUtils.getParam(taskParam,
                Constants.PARAM_TARGET_PAGE_FLOW);
        final long startPageId=Long.valueOf(targetPageFlow.split(",")[0]);

        JavaRDD<Long>startPageRDD=sessionid2actionRDD.flatMap(
                new FlatMapFunction<Tuple2<String,Iterable<Row>>,Long>() {

                    private static final long serialVersionUID = 1L;

                    @Override
                    public Iterable<Long> call(
                            Tuple2<String, Iterable<Row>> tuple)
                            throws Exception {
                        List<Long>list=new ArrayList<Long>();
                        Iterator<Row>iterator=tuple._2.iterator();

                        while(iterator.hasNext()) {
                            Row row=iterator.next();
                            long pageid=row.getLong(3);

                            if(pageid==startPageId) {
                                list.add(pageid);
                            }
                        }

                        return list;
                    }

        });
        return startPageRDD.count();
    }

    /*
     * 计算页面切片转化率
     * 
     * */
    private static Map<String,Double>computePageSplitConvertRate(
            JSONObject taskParam,
            Map<String,Object>pageSplitPvMap,
            long startPagePv){

        Map<String,Double>convertRateMap=new HashMap<String,Double>();

        String[] targetPages=ParamUtils.getParam(taskParam,
                Constants.PARAM_TARGET_PAGE_FLOW).split(",");
        long lastPageSplitPv=0L;

        //3,5,2,4,6
        //3_5
        //转化率:3_5 pv /3 pv
        //5_2 rate=5_2 pv/3_5 pv

        //通过for循环,获取目标页面流中的各个页面切片(pv)
        for(int i=1;i<targetPages.length;i++) {
            String targetPageSplit=targetPages[i-1]+"_"+targetPages[i];
            long targetPageSplitPv=Long.valueOf(String.valueOf(
                    pageSplitPvMap.get(targetPageSplit)));
            double convertRate=0.0;
            if(i==1) {
                convertRate=NumberUtils.formatDouble(
                        (double)targetPageSplitPv/(double)startPagePv, 2);

            }else {
                convertRate=NumberUtils.formatDouble(
                        (double)(targetPageSplitPv)/(double)lastPageSplitPv, 2);
            }
            convertRateMap.put(targetPageSplit, convertRate);

            lastPageSplitPv=targetPageSplitPv;
        }
        return convertRateMap;


    }
    /*
     * 持久化转化率
     * 
     * */
    private static void persistConvertRate(long taskid,
            Map<String,Double>convertRateMap) {
        StringBuffer buffer=new StringBuffer("");
        for(Map.Entry<String, Double>convertRateEntry:convertRateMap.entrySet()) {
            String pageSplit=convertRateEntry.getKey();
            double convertRate=convertRateEntry.getValue();
            buffer.append(pageSplit+"="+convertRate+"|");
        }
        String convertRate=buffer.toString();
        convertRate=convertRate.substring(0,convertRate.length()-1);

        PageSplitConvertRate pageSplitConvertRate=new PageSplitConvertRate();
        pageSplitConvertRate.setTaskid(taskid);
        pageSplitConvertRate.setConvertRate(convertRate);

        IPageSplitConvertRateDAO pageSplitConvertRateDAO=DAOFactory.getPageSplitConvertRateDAO();
        pageSplitConvertRateDAO.insert(pageSplitConvertRate);
    }

}

cn.ctgu.sparkproject.constant.Constants

package cn.ctgu.sparkproject.constant;

//常量接口

public interface Constants {
    /*
     * 项目配置相关的常量
     * */
    String JDBC_DRIVER="jdbc.driver";
    String JDBC_DATASOURCE_SIZE="jdbc.datasource.size";
    String JDBC_URL="jdbc.url";
    String JDBC_USER="jdbc.user";
    String JDBC_PASSWORD="jdbc.password";
    String JDBC_URL_PROD="jdbc.url.prod";
    String JDBC_USER_PROD="jdbc.user.prod";
    String JDBC_PASSWORD_PROD="jdbc.password.prod";
    String SPARK_LOCAL="spark.local";
    String SPARK_LOCAL_TASKID_SESSION="spark.local.taskid.session";
    String SPARK_LOCAL_TASKID_PAGE="spark.local.taskid.page";

    /*
     * spark作业相关的常量
     * */
    String SPARK_APP_NAME_SESSION="UserVisitSessionAnalyzeSpark";
    String SPARK_APP_NAME_PAGE="PageOneStepConvertRateSpark";
    String FIELD_SESSION_ID="sessionid";
    String FIELD_SEARCH_KEYWORDS="searchKeywords";
    String FIELD_CLICK_CATEGORY_IDS="clickCategoryIds";
    String FIELD_AGE="age";
    String FIELD_PROFESSIONAL="professional";
    String FIELD_CITY="city";
    String FIELD_SEX="sex";
    String FIELD_VISIT_LENGTH="visitLength";
    String FIELD_STEP_LENGTH="stepLength";
    String FIELD_START_TIME="startTime";
    String FIELD_CLICK_COUNT="clickCount";
    String FIELD_ORDER_COUNT="orderCount";
    String FIELD_PAY_COUNT="payCount";
    String FIELD_CATEGORY_ID="categoryid";

    String SESSION_COUNT = "session_count";

    String TIME_PERIOD_1s_3s = "1s_3s";
    String TIME_PERIOD_4s_6s = "4s_6s";
    String TIME_PERIOD_7s_9s = "7s_9s";
    String TIME_PERIOD_10s_30s = "10s_30s";
    String TIME_PERIOD_30s_60s = "30s_60s";
    String TIME_PERIOD_1m_3m = "1m_3m";
    String TIME_PERIOD_3m_10m = "3m_10m";
    String TIME_PERIOD_10m_30m = "10m_30m";
    String TIME_PERIOD_30m = "30m";

    String STEP_PERIOD_1_3 = "1_3";
    String STEP_PERIOD_4_6 = "4_6";
    String STEP_PERIOD_7_9 = "7_9";
    String STEP_PERIOD_10_30 = "10_30";
    String STEP_PERIOD_30_60 = "30_60";
    String STEP_PERIOD_60 = "60";

    /*
     * 任务相关的常量
     * */
    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";
    String PARAM_TARGET_PAGE_FLOW="targetPageFlow";

}

cn.ctgu.sparkproject.dao.IPageSplitConvertRateDAO

package cn.ctgu.sparkproject.dao;

import cn.ctgu.sparkproject.domain.PageSplitConvertRate;

/*
 * 页面切片转化率DAO接口
 * 
 * */
public interface IPageSplitConvertRateDAO {
    void insert(PageSplitConvertRate pageSplitConvertRate);
}

cn.ctgu.sparkproject.dao.impl.PageSplitConvertRateDAOImpl

package cn.ctgu.sparkproject.dao.impl;

import cn.ctgu.sparkproject.dao.IPageSplitConvertRateDAO;
import cn.ctgu.sparkproject.domain.PageSplitConvertRate;
import cn.ctgu.sparkproject.jdbc.JDBCHelper;

/*
 * 页面切片转化率DAO实现类
 * 
 * */
public class PageSplitConvertRateDAOImpl implements IPageSplitConvertRateDAO{

    @Override
    public void insert(PageSplitConvertRate pageSplitConvertRate) {

        String sql="insert into page_split_convert_rate value(?,?)";
        Object[]params=new Object[] {pageSplitConvertRate.getTaskid(),
                pageSplitConvertRate.getConvertRate()};
        JDBCHelper jdbcHelper=JDBCHelper.getInstance();
        jdbcHelper.executeUpdate(sql, params);


    }

}

cn.ctgu.sparkproject.dao.factory.DAOFactory

package cn.ctgu.sparkproject.dao.factory;

import cn.ctgu.sparkproject.dao.IPageSplitConvertRateDAO;
import cn.ctgu.sparkproject.dao.ISessionAggrStatDAO;
import cn.ctgu.sparkproject.dao.ISessionDetailDAO;
import cn.ctgu.sparkproject.dao.ISessionRandomExtractDAO;
import cn.ctgu.sparkproject.dao.ITaskDAO;
import cn.ctgu.sparkproject.dao.ITop10CategoryDAO;
import cn.ctgu.sparkproject.dao.ITop10SessionDAO;
import cn.ctgu.sparkproject.dao.impl.PageSplitConvertRateDAOImpl;
import cn.ctgu.sparkproject.dao.impl.SessionAggrStatDAOImpl;
import cn.ctgu.sparkproject.dao.impl.SessionDetailDAOImpl;
import cn.ctgu.sparkproject.dao.impl.SessionRandomExtractDAOImpl;
import cn.ctgu.sparkproject.dao.impl.TaskDAOImpl;
import cn.ctgu.sparkproject.dao.impl.Top10CategoryDAOImpl;
import cn.ctgu.sparkproject.dao.impl.Top10SessionDAOImpl;

/*
 * DAO工厂类
 * 
 * */
public class DAOFactory {
    /*
     * 获取任务管理器DAO
     * 
     * */
    public static ITaskDAO getTaskDAO() {

        return new TaskDAOImpl();
    }
    /*
     * 获取统计sessionDAO
     * 
     * */
    public static ISessionAggrStatDAO getSessionAggrStatDAO() {
        return new SessionAggrStatDAOImpl();
    }

    public static ISessionRandomExtractDAO getSessionRandomExtractDAO() {
        return new SessionRandomExtractDAOImpl();
    }

    public static ISessionDetailDAO getSessionDetailDAO() {
        return new SessionDetailDAOImpl();
    }

    public static ITop10CategoryDAO getTop10CategoryDAO() {
        return new Top10CategoryDAOImpl();
    }
    public static ITop10SessionDAO getTop10SessionDAO() {
        return new Top10SessionDAOImpl();
    }
    public static IPageSplitConvertRateDAO getPageSplitConvertRateDAO() {
        return new PageSplitConvertRateDAOImpl();
    }
}

cn.ctgu.sparkproject.domain.PageSplitConvertRate

package cn.ctgu.sparkproject.domain;

/*
 * 页面切片转化率
 * 
 * */
public class PageSplitConvertRate {
    private long taskid;
    private String convertRate;
    public long getTaskid() {
        return taskid;
    }
    public void setTaskid(long taskid) {
        this.taskid = taskid;
    }
    public String getConvertRate() {
        return convertRate;
    }
    public void setConvertRate(String convertRate) {
        this.convertRate = convertRate;
    }

}

cn.ctgu.sparkproject.jdbc.JDBCHelper

package cn.ctgu.sparkproject.jdbc;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.LinkedList;
import java.util.List;

import cn.ctgu.sparkproject.conf.ConfigurationManager;
import cn.ctgu.sparkproject.constant.Constants;

/*
 * JDBC辅助组件
 * 
 * 
 * 
 * */

public class JDBCHelper {
/*
 * 第一步:在静态代码块中,直接加载数据库的驱动
 * 加载驱动,不是直接简单的,使用com.mysql.jdbc.Driver就可以了(属于硬编码)
 *
 *com.mysql.jdbc.Driver只代表了mysql数据的驱动
 *那么,如果有一天,项目的底层的数据要进行迁移,比如迁移到SQLServer、DB2
 *那么,就必须很费经的在代码中,找到硬编码了的代码并进行修改成其他数据库的驱动类的类名
 *
 *所以正规项目是不允许硬编码的存在
 *
 *通常都是使用以一个常量接口中的某个常量来代表一个值
 *然后在这个值改变的时候,只要改变常量接口中的变量
 *
 *项目要尽量做成可配置的,就是说,我们这个数据库驱动,更进一步,也不只是放在常量接口中就可以了
 *最后的方式,是放在外部的配置文件中,跟代码彻底分离
 *常量接口中只是包含了这个值对应的key的名字
 * */
    static {
        try {
            String driver=ConfigurationManager.getProperty(Constants.JDBC_DRIVER);
            Class.forName(driver);

        }catch(Exception e) {
            e.printStackTrace();
        }
    }
    /*
     * 第二步,实现JDBCHelper的单例化
     * 为什么要实现单例化呢?因为它的内部要封装一个简单的内部的数据连接池
     * 为了保证数据库连接池有且仅有一份,所以就通过单例的方式
     * 保证JDBCHelper只有一个实例,实例中只有一根数据连接池
     * */
    private static JDBCHelper instance=null;
    /*
     * 获取单例
     * 
     * JDBCHelper在整个程序声明周期中,只会创建一次实例
     * 在这一次创建实例的过程中,就会调用JDBCHelper()构造方法
     * 此时,就可以在构造方法中,去创建自己唯一的数据库连接池
     * 
     * */
    public static JDBCHelper getInstance() {
        if(instance==null) {
            synchronized(JDBCHelper.class){
                if(instance==null) {
                    instance=new JDBCHelper();
                }
            }
        }
        return instance;
    }


    private LinkedList<Connection>datasource=new LinkedList<Connection>(); 
    /*
     * 第三步:实现单例的过程中,实现唯一的数据库连接池
     * 私有化构造函数
     * */
    private JDBCHelper() {
        //第一步,获取数据库连接池的大小,就是说,数据库连接池中要放多少个数据库连接
        //这个,可以通过在配置文件中配置的方式,来灵活的设定
        int datasourceSize=ConfigurationManager.getInteger(
                Constants.JDBC_DATASOURCE_SIZE);
        //然后创建指定数量的数据库连接,并放入数据库连接池中
        for(int i=0;i<datasourceSize;i++) {
            boolean local=ConfigurationManager.getBoolean(Constants.SPARK_LOCAL);
            String url=null;
            String user=null;
            String password=null;

            if(local) {
                 url=ConfigurationManager.getProperty(Constants.JDBC_URL);
                 user=ConfigurationManager.getProperty(Constants.JDBC_USER);
                 password=ConfigurationManager.getProperty(Constants.JDBC_PASSWORD);
            }else {
                 url=ConfigurationManager.getProperty(Constants.JDBC_URL_PROD);
                 user=ConfigurationManager.getProperty(Constants.JDBC_USER_PROD);
                 password=ConfigurationManager.getProperty(Constants.JDBC_PASSWORD_PROD);
            }
            try {
                Connection conn=DriverManager.getConnection(url, user, password);
                datasource.push(conn);
            }catch(Exception e) {
                e.printStackTrace();
            }
        }


    }
    /*
     * 第四步:提供获取数据库连接的方法
     * 有可能,你去获取的时候,这个时候连接都被用光了,你暂时获取不到数据库连接
     * 所以我们要自己编码实现一个简单的等待机制,去等待获取到数据库连接
     * 
     * 为了防止数据库连接池用完了,其他线程都来判断都来拿数据库连接导致的代码重复判断
     * 所以加了一个线程同步只有第一个线程拿到了数据库连接,其他线程才会来判断
     * */
    public synchronized Connection getConnection() {
        while(datasource.size()==0) {
            try {
                Thread.sleep(10);
            }catch(Exception e) {
                e.printStackTrace();
            }
        }
        return datasource.poll();
    }
    /*
     * 第五步:开发增删改查的方法
     * 1、执行增删改查SQL语句的方法
     * 2、执行查询SQL语句的方法
     * 3、批量执行SQL语句的方法
     * 
     * */
    public int executeUpdate(String sql,Object[]params) {
        int rtn=0;
        Connection conn=null;
        PreparedStatement pstmt=null;
        try {
            conn=getConnection();
            pstmt=conn.prepareStatement(sql);
            for(int i=0;i<params.length;i++) {
                pstmt.setObject(i+1, params[i]);
            }
            rtn=pstmt.executeUpdate();
        }catch(Exception e) {
            e.printStackTrace();
        }finally{
            if(conn!=null) {
                datasource.push(conn);
            }
        }
        return rtn;
    }
    /*
     * 执行查询sql语句
     * 
     * */
    public void executeQuery(String sql,Object[]params,
            QueryCallback callback) {
        Connection conn=null;
        PreparedStatement pstmt=null;
        ResultSet rs=null;
        try {
            conn=getConnection();
            pstmt=conn.prepareStatement(sql);
            for(int i=0;i<params.length;i++) {
                pstmt.setObject(i+1, params[i]);
            }
            rs=pstmt.executeQuery();
            callback.process(rs);
        }catch(Exception e) {
            e.printStackTrace();
        }

    }
    /*
     * 批量执行sql语句
     * 批量执行SQL语句是JDBC中的一个高级功能
     * 默认情况下,每次执行一条SQL语句就会通过网络连接,向MySQL发送一次请求
     * 
     * 但是,如果在短时间内要执行多条结构完全一模一样的SQL,只是参数不同
     * 虽然使用PrepareStatement这种方式,可以只编译一次SQL,提高性能,但是,还是对于每次SQL
     * 都要向MySQL发送一次网络请求
     * 
     * 可以通过批量执行SQL语句的功能优化这个性能
     * 一次性通过PreparedStatement发送多条SQL语句,比如100条、1000条甚至是上万条
     * 执行的时候,也仅仅编译一次就可以
     * 这种批量执行sql语句的方式,可以大大提升性能
     * 
     * */
    public int[]executeBatch(String sql,List<Object[]>paramsList){
        int[]rtn=null;
        Connection conn=null;
        PreparedStatement pstmt=null;
        try {
            conn=getConnection();
            //第一步:使用Connection对象取消自动提交
            conn.setAutoCommit(false);

            pstmt=conn.prepareStatement(sql);

            //第二步:使用PrepareStatement.addBatch()方法加入批量的SQL参数
            for(Object[]params:paramsList) {
                for(int i=0;i<params.length;i++) {
                    pstmt.setObject(i+1, params[i]);
                }
                pstmt.addBatch();
            }
            //第三步:使用PreparedStatement.executeBatch()方法执行批量的SQL语句
            rtn=pstmt.executeBatch();
            //最后一步:使用Connection对象,提交批量的SQL语句
            conn.commit();
        }catch(Exception e) {
            e.printStackTrace();
        }
        return rtn;
    }

    /*
     * 内部类,查询回调接口
     * 
     * */
    public static interface QueryCallback{

        void process(ResultSet rs) throws Exception;
        /*
         * 处理查询结果
         * */
    }
}

my.properties

jdbc.driver=com.mysql.jdbc.Driver
jdbc.datasource.size=10
jdbc.url=jdbc:mysql://localhost:3306/bigdata
jdbc.user=root
jdbc.password=123456

jdbc.url.prod=jdbc:mysql://172.25.11.100:3306/spark_project
jdbc.user.prod=hive
jdbc.password.prod=hive

spark.local=false
spark.local.taskid.session=2
spark.local.taskid.page=3

cn.ctgu.sparkproject.util.SparkUtils

package cn.ctgu.sparkproject.util;
import org.apache.spark.SparkConf;
import org.apache.spark.SparkContext;
import org.apache.spark.api.java.JavaRDD;
import org.apache.spark.api.java.JavaSparkContext;
import org.apache.spark.sql.DataFrame;
import org.apache.spark.sql.Row;
import org.apache.spark.sql.SQLContext;
import org.apache.spark.sql.hive.HiveContext;

import com.alibaba.fastjson.JSONObject;

import cn.ctgu.sparkproject.conf.ConfigurationManager;
import cn.ctgu.sparkproject.constant.Constants;
import cn.ctgu.sparkproject.test.MockData;

/*
 * Spark工具类
 * 
 * */


public class SparkUtils {
    /*
     * 根据当前是否是本地测试的配置
     * 决定如何设置sparkConf的master
     * 
     * */
    public static void setMaster(SparkConf conf) {
        boolean local=ConfigurationManager.getBoolean(Constants.SPARK_LOCAL);
        if(local) {
            conf.setMaster("local");
        }
    }

    /*
     * 生成模拟数据
     * 如果是local模式则生成模拟数据,否则不生成
     * 
     * */
    public static void mockData(JavaSparkContext sc,SQLContext sqlContext) {
        boolean local=ConfigurationManager.getBoolean("local");
        if(local) {
            MockData.mock(sc, sqlContext);
        }
    }

    /*
     * 获取SQLContext
     * 如果spark.local设置为true,那么就创建为SQLContext
     * 否则创建为HiveContext
     * 
     * 
     * */
    public static SQLContext getSQLContext(SparkContext sc) {
        boolean local=ConfigurationManager.getBoolean("local");
        if(local) {
            return new SQLContext(sc);
        }else {
            return new HiveContext(sc);
        }
    }

    /*
     * 获取指定日期范围内的用户行为数据RDD
     * 
     * 
     * */
    public 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+"'";
        DataFrame actionDF=sqlContext.sql(sql);

        return actionDF.toJavaRDD();
    }
}

cn.ctgu.sparkproject.util.ParamUtils

package cn.ctgu.sparkproject.util;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;

import cn.ctgu.sparkproject.conf.ConfigurationManager;
import cn.ctgu.sparkproject.constant.Constants;

/**
 * 参数工具类
 * @author Administrator
 *
 */
public class ParamUtils {

    /**
     * 从命令行参数中提取任务id
     * @param args 命令行参数
     * @return 任务id
     */
    public static Long getTaskIdFromArgs(String[] args,String taskType) {

        boolean local=ConfigurationManager.getBoolean(Constants.SPARK_LOCAL);
        if(local) {
            return ConfigurationManager.getLong(taskType);
        }else {
            try {
                if(args != null && args.length > 0) {
                    return Long.valueOf(args[0]);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }  
        }

        return null;
    }

    /**
     * 从JSON对象中提取参数
     * @param jsonObject JSON对象
     * @return 参数
     */
    public static String getParam(JSONObject jsonObject, String field) {
        JSONArray jsonArray = jsonObject.getJSONArray(field);
        if(jsonArray != null && jsonArray.size() > 0) {
            return jsonArray.getString(0);
        }
        return null;
    }

}

cn.ctgu.sparkproject.util.NumberUtils

package cn.ctgu.sparkproject.util;

import java.math.BigDecimal;

/**
 * 数字格工具类
 * @author Administrator
 *
 */
public class NumberUtils {

    /**
     * 格式化小数
     * @param str 字符串
     * @param scale 四舍五入的位数
     * @return 格式化小数
     */
    public static double formatDouble(double num, int scale) {
        BigDecimal bd = new BigDecimal(num);  
        return bd.setScale(scale, BigDecimal.ROUND_HALF_UP).doubleValue();
    }

}

cn.ctgu.sparkproject.util.DateUtils

package cn.ctgu.sparkproject.util;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;

/**
 * 日期时间工具类
 * @author Administrator
 *
 */
public class DateUtils {

    public static final SimpleDateFormat TIME_FORMAT = 
            new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    public static final SimpleDateFormat DATE_FORMAT = 
            new SimpleDateFormat("yyyy-MM-dd");

    /**
     * 判断一个时间是否在另一个时间之前
     * @param time1 第一个时间
     * @param time2 第二个时间
     * @return 判断结果
     */
    public static boolean before(String time1, String time2) {
        try {
            Date dateTime1 = TIME_FORMAT.parse(time1);
            Date dateTime2 = TIME_FORMAT.parse(time2);

            if(dateTime1.before(dateTime2)) {
                return true;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

    /**
     * 判断一个时间是否在另一个时间之后
     * @param time1 第一个时间
     * @param time2 第二个时间
     * @return 判断结果
     */
    public static boolean after(String time1, String time2) {
        try {
            Date dateTime1 = TIME_FORMAT.parse(time1);
            Date dateTime2 = TIME_FORMAT.parse(time2);

            if(dateTime1.after(dateTime2)) {
                return true;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

    /**
     * 计算时间差值(单位为秒)
     * @param time1 时间1
     * @param time2 时间2
     * @return 差值
     */
    public static int minus(String time1, String time2) {
        try {
            Date datetime1 = TIME_FORMAT.parse(time1);
            Date datetime2 = TIME_FORMAT.parse(time2);

            long millisecond = datetime1.getTime() - datetime2.getTime();

            return Integer.valueOf(String.valueOf(millisecond / 1000));  
        } catch (Exception e) {
            e.printStackTrace();
        }
        return 0;
    }

    /**
     * 获取年月日和小时
     * @param datetime 时间(yyyy-MM-dd HH:mm:ss)
     * @return 结果
     */
    public static String getDateHour(String datetime) {
        String date = datetime.split(" ")[0];
        String hourMinuteSecond = datetime.split(" ")[1];
        String hour = hourMinuteSecond.split(":")[0];
        return date + "_" + hour;
    }  

    /**
     * 获取当天日期(yyyy-MM-dd)
     * @return 当天日期
     */
    public static String getTodayDate() {
        return DATE_FORMAT.format(new Date());  
    }

    /**
     * 获取昨天的日期(yyyy-MM-dd)
     * @return 昨天的日期
     */
    public static String getYesterdayDate() {
        Calendar cal = Calendar.getInstance();
        cal.setTime(new Date());  
        cal.add(Calendar.DAY_OF_YEAR, -1);  

        Date date = cal.getTime();

        return DATE_FORMAT.format(date);
    }

    /**
     * 格式化日期(yyyy-MM-dd)
     * @param date Date对象
     * @return 格式化后的日期
     */
    public static String formatDate(Date date) {
        return DATE_FORMAT.format(date);
    }

    /**
     * 格式化时间(yyyy-MM-dd HH:mm:ss)
     * @param date Date对象
     * @return 格式化后的时间
     */
    public static String formatTime(Date date) {
        return TIME_FORMAT.format(date);
    }
    /*
     * 解析时间字符串
     * 
     * */
    public static Date parseTime(String time) {
        try {
            return TIME_FORMAT.parse(time);
        } catch (ParseException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return null;
    }
}

猜你喜欢

转载自blog.csdn.net/Jorocco/article/details/81237831
今日推荐