79、Spark SQL之与Spark Core整合之每日top3热点搜索词统计案例实战

每日top3热点搜索词统计案例实战

数据格式:
日期 用户 搜索词 城市 平台 版本

需求:

  1. 筛选出符合查询条件(城市、平台、版本)的数据
  2. 统计出每天搜索uv排名前3的搜索词
  3. 按照每天的top3搜索词的uv搜索总次数,倒序排序
  4. 将数据保存到hive表中

实现思路分析

  1. 针对原始数据(HDFS文件),获取输入的RDD
  2. 使用filter算子,去针对输入RDD中的数据,进行数据过滤,过滤出符合查询条件的数据。
    2.1. 普通的做法:直接在fitler算子函数中,使用外部的查询条件(Map),但是,这样做的话,是不是查询条件Map,会发送到每一个task上一份副本。(性能并不好)
    2.2. 优化后的做法:将查询条件,封装为Broadcast广播变量,在filter算子中使用Broadcast广播变量进行数据筛选。
  3. 将数据转换为“(日期_搜索词, 用户)”格式,然后呢,对它进行分组,然后再次进行映射,对每天每个搜索词的搜索用户进行去重操作,并统计去重后的数量,即为每天每个搜索词的uv。最后,获得“(日期_搜索词, uv)”
  4. 将得到的每天每个搜索词的uv,RDD,映射为元素类型为Row的RDD,将该RDD转换为DataFrame
  5. 将DataFrame注册为临时表,使用Spark SQL的开窗函数,来统计每天的uv数量排名前3的搜索词,以及它的搜索uv,最后获取,是一个DataFrame
  6. 将DataFrame转换为RDD,继续操作,按照每天日期来进行分组,并进行映射,计算出每天的top3搜索词的搜索uv的总数,然后将uv总数作为key,将每天的top3搜索词以及搜索次数,拼接为一个字符串
  7. 按照每天的top3搜索总uv,进行排序,倒序排序
  8. 将排好序的数据,再次映射回来,变成“日期_搜索词_uv”的格式
  9. 再次映射为DataFrame,并将数据保存到Hive中即可

实现

Java版本

public class DailyTop3Keyword {
    public static void main(String[] args) {
        SparkConf conf = new SparkConf().setAppName("DailyTop3KeywordJava");
        JavaSparkContext javaSparkContext = new JavaSparkContext(conf);
        HiveContext hiveContext = new HiveContext(javaSparkContext.sc());

        // 伪造出一份数据,查询条件
        // 备注:实际上,在实际的企业项目开发中,很可能,这个查询条件,是通过J2EE平台插入到某个MySQL表中的
        // 然后,这里呢,实际上,通常是会用Spring框架和ORM框架(MyBatis)的,去提取MySQL表中的查询条件
        Map<String, List<String>> optisons = new HashMap<String, List<String>>();
        optisons.put("location", Arrays.asList("beijing"));
        optisons.put("plat", Arrays.asList("android"));
        optisons.put("version", Arrays.asList("1.0","1.2","1.5","2.0"));

        // 根据我们实现思路中的分析,这里最合适的方式,是将该查询参数Map封装为一个Broadcast广播变量
        // 这样可以进行优化,每个Worker节点,就拷贝一份数据即可
        Broadcast<Map<String, List<String>>> optionsBroadcast = javaSparkContext.broadcast(optisons);

        // 针对HDFS文件中的日志,获取输入RDD
        JavaRDD<String> lines = javaSparkContext.textFile("hdfs://hadoop-100:9000/sql/keyword.txt");

        // 使用查询参数Map广播变量,进行筛选
        JavaRDD<String> linesFilter = lines.filter(new Function<String, Boolean>() {
            @Override
            public Boolean call(String v1) throws Exception {

                // 切割原始日志,获取城市、平台和版本
                String[] strings = v1.split("\t");

                // 与查询条件进行比对,任何一个条件,只要该条件设置了,且日志中的数据没有满足条件
                // 则直接返回false,过滤该日志
                // 否则,如果所有设置的条件,都有日志中的数据,则返回true,保留日志
                Map<String, List<String>> value = optionsBroadcast.getValue();
                List<String> location = value.get("location");
                List<String> plat = value.get("plat");
                List<String> version = value.get("version");
                if (location.size() > 0 && !location.contains(strings[3])) {
                    return false;
                }
                if (plat.size() > 0 && !plat.contains(strings[4])) {
                    return false;
                }
                if (version.size() > 0 && !version.contains(strings[5])) {
                    return false;
                }
                return true;
            }
        });

        // 过滤出来的原始日志,映射为(日期_搜索词, 用户)的格式
        JavaPairRDD<String, String> dataKeyWordUserRDD = linesFilter.mapToPair(new PairFunction<String, String, String>() {
            @Override
            public Tuple2<String, String> call(String s) throws Exception {
                String[] strings = s.split("\t");
                return new Tuple2<>(strings[0] + "_" + strings[2], strings[1]);
            }
        });

        // 进行分组,获取每天每个搜索词,有哪些用户搜索了(没有去重)
        JavaPairRDD<String, Iterable<String>> dataKeywordUserGroupByRDD = dataKeyWordUserRDD.groupByKey();

        JavaPairRDD<String, Integer> dataKeywordUV = dataKeywordUserGroupByRDD.mapToPair(new PairFunction<Tuple2<String, Iterable<String>>, String, Integer>() {
            @Override
            public Tuple2<String, Integer> call(Tuple2<String, Iterable<String>> stringIterableTuple2) throws Exception {
                String dataKeyword = stringIterableTuple2._1;
                // 对用户进行去重,并统计去重后的数量
                Set<String> user = new HashSet<String>();
                Iterator<String> iterator = stringIterableTuple2._2.iterator();
                while (iterator.hasNext()) {
                    user.add(iterator.next());
                }

                // 获取uv  user.size()
                return new Tuple2<>(dataKeyword, user.size());
            }
        });

        // 将每天每个搜索词的uv数据,转换成DataFrame
        JavaRDD<Row> dataKeywordUVRow = dataKeywordUV.map(new Function<Tuple2<String, Integer>, Row>() {
            @Override
            public Row call(Tuple2<String, Integer> v1) throws Exception {
                String data = v1._1.split("_")[0];
                String keyWord = v1._1.split("_")[1];
                Integer uv = v1._2;

                return RowFactory.create(data, keyWord, uv);
            }
        });


        List<StructField> fieldList = new ArrayList<StructField>();
        fieldList.add(DataTypes.createStructField("data", DataTypes.StringType, true));
        fieldList.add(DataTypes.createStructField("keyword", DataTypes.StringType, true));
        fieldList.add(DataTypes.createStructField("uv", DataTypes.IntegerType, true));
        StructType structType = DataTypes.createStructType(fieldList);
        DataFrame dataKeywordUVDF = hiveContext.createDataFrame(dataKeywordUVRow, structType);

        // 使用Spark SQL的开窗函数,统计每天搜索uv排名前3的热点搜索词
        dataKeywordUVDF.registerTempTable("data_keyword_uv");

        DataFrame top3DF = hiveContext.sql("select data, keyword, uv " +
                "from (" +
                "select data, keyword, uv, " +
                "row_number() OVER (partition by data order by uv desc) rank " +
                "from data_keyword_uv " +
                ") s " +
                "where s.rank < 4");

        // 将DataFrame转换为RDD,然后映射,计算出每天的top3搜索词的搜索uv总数
        JavaPairRDD<String, String> top3RDD = top3DF.javaRDD().mapToPair(new PairFunction<Row, String, String>() {
            @Override
            public Tuple2<String, String> call(Row row) throws Exception {
                return new Tuple2<String, String>(row.getString(0), row.getString(1) + "," + row.getInt(2));
            }
        });

        JavaPairRDD<String, Iterable<String>> top3GroupByKey = top3RDD.groupByKey();

        JavaPairRDD<Integer, String> sumTop3RDD = top3GroupByKey.mapToPair(new PairFunction<Tuple2<String, Iterable<String>>, Integer, String>() {
            @Override
            public Tuple2<Integer, String> call(Tuple2<String, Iterable<String>> stringIterableTuple2) throws Exception {
                String data = stringIterableTuple2._1;
                String keywords = "";
                int sum = 0;
                Iterator<String> iterator = stringIterableTuple2._2.iterator();
                while (iterator.hasNext()) {
                    String next = iterator.next();
                    keywords = keywords + "," + next.split(",")[0];
                    sum += Integer.parseInt(next.split(",")[1]);
                }
                return new Tuple2<Integer, String>(sum, data + keywords);
            }
        });


        // 按照每天的总搜索uv进行倒序排序
        JavaPairRDD<Integer, String> sumTop3SortedRDD = sumTop3RDD.sortByKey(false);

        // 再次进行映射,将排序后的数据,映射回原始的格式,Iterable<Row>
        JavaRDD<Row> resultRDD = sumTop3SortedRDD.flatMap(new FlatMapFunction<Tuple2<Integer, String>, Row>() {
            @Override
            public Iterable<Row> call(Tuple2<Integer, String> v1) throws Exception {
                List<Row> rowList = new ArrayList<>(3);
                rowList.add(RowFactory.create(v1._2.split(",")[0], v1._2.split(",")[1], v1._1));
                rowList.add(RowFactory.create(v1._2.split(",")[0], v1._2.split(",")[2], v1._1));
                rowList.add(RowFactory.create(v1._2.split(",")[0], v1._2.split(",")[3], v1._1));
                return rowList;
            }
        });

        DataFrame resultDF = hiveContext.createDataFrame(resultRDD, structType);
        // 将最终的数据,转换为DataFrame,并保存到Hive表中
        resultDF.saveAsTable("daily_top3_keyword_uv");
        javaSparkContext.close();
    }
}

Scala版本


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值