Spark SQL(十一):與Spark Core整合

每日top3熱點搜索詞統計Demo

1、數據格式:

日期 用戶 搜索詞 城市 平臺 版本

2、需求:

1、篩選出符合查詢條件(城市、平臺、版本)的數據
2、統計出每天搜索uv排名前3的搜索詞
3、按照每天的top3搜索詞的uv搜索總次數,倒序排序
4、將數據保存到hive表中

3、實現思路:

  • 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中即可
package cn.spark.study.sql;

import java.util.ArrayList;
import java.util.Arrays;
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.Function;
import org.apache.spark.api.java.function.PairFunction;
import org.apache.spark.broadcast.Broadcast;
import org.apache.spark.sql.DataFrame;
import org.apache.spark.sql.Row;
import org.apache.spark.sql.RowFactory;
import org.apache.spark.sql.hive.HiveContext;
import org.apache.spark.sql.types.DataTypes;
import org.apache.spark.sql.types.StructField;
import org.apache.spark.sql.types.StructType;
import scala.Tuple2;

/**
 * 每日top3熱點搜索詞統計案例
 */
public class DailyTop3Keyword {

    @SuppressWarnings("deprecation")
    public static void main(String[] args) {
        SparkConf conf = new SparkConf()
                .setAppName("DailyTop3Keyword");  
        JavaSparkContext sc = new JavaSparkContext(conf);
        HiveContext sqlContext = new HiveContext(sc.sc());  
        
        // 偽造出一份數據,查詢條件
        // 備注:實際上,在工作中,這個查詢條件,是通過J2EE平臺插入到某個MySQL表中的
        // 然后,在這里通過Spring框架和ORM框架(MyBatis),去提取MySQL表中的查詢條件
        Map<String, List<String>> queryParamMap = new HashMap<String, List<String>>();
        queryParamMap.put("city", Arrays.asList("beijing"));  
        queryParamMap.put("platform", Arrays.asList("android"));  
        queryParamMap.put("version", Arrays.asList("1.0", "1.2", "1.5", "2.0"));  
        
        // 根據我們實現思路中的分析,這里最合適的方式,
        //是將該查詢參數Map封裝為一個Broadcast廣播變量
        // 這樣可以進行優化,每個Worker節點,只拷貝一份數據即可
        final Broadcast<Map<String, List<String>>> queryParamMapBroadcast = 
                sc.broadcast(queryParamMap);
        
        // 1、針對HDFS文件中的日志,獲取輸入RDD
        JavaRDD<String> rawRDD = sc.textFile("hdfs://spark1:9000/spark-study/keyword.txt"); 
        
        // 2、使用查詢參數Map廣播變量,進行篩選
        JavaRDD<String> filterRDD = rawRDD.filter(new Function<String, Boolean>() {
            
            private static final long serialVersionUID = 1L;

            @Override
            public Boolean call(String log) throws Exception {
                // 切割原始日志,獲取城市、平臺和版本
                String[] logSplited = log.split("\t");  
                
                String city = logSplited[3];
                String platform = logSplited[4];
                String version = logSplited[5];
                
                // 與查詢條件進行比對,任何一個條件,只要該條件設置了,且日志中的數據沒有滿足條件
                // 則直接返回false,過濾該日志
                // 否則,如果所有設置的條件,都有日志中的數據,則返回true,保留日志
                Map<String, List<String>> queryParamMap = queryParamMapBroadcast.value();
                
                List<String> cities = queryParamMap.get("city");  
                if(cities.size() > 0 && !cities.contains(city)) {
                    return false;
                }
                
                List<String> platforms = queryParamMap.get("platform");  
                if(platforms.size() > 0 && !platforms.contains(platform)) {
                    return false;
                }
                
                List<String> versions = queryParamMap.get("version");  
                if(versions.size() > 0 && !versions.contains(version)) {
                    return false;
                }
                
                return true;
            }
            
        });
        
        // 3、過濾出來的原始日志,映射為(日期_搜索詞, 用戶)的格式
        JavaPairRDD<String, String> dateKeywordUserRDD = filterRDD.mapToPair(
                
                new PairFunction<String, String, String>() {

                    private static final long serialVersionUID = 1L;

                    @Override
                    public Tuple2<String, String> call(String log) throws Exception {
                        String[] logSplited = log.split("\t");  
                        
                        String date = logSplited[0];
                        String user = logSplited[1];
                        String keyword = logSplited[2];
                        
                        return new Tuple2<String, String>(date + "_" + keyword, user);
                    }
                    
                });
        
        // 進行分組,獲取每天每個搜索詞,有哪些用戶搜索了(沒有去重)
        JavaPairRDD<String, Iterable<String>> dateKeywordUsersRDD = dateKeywordUserRDD.groupByKey();
        
        // 對每天每個搜索詞的搜索用戶,執行去重操作,獲得其uv
        JavaPairRDD<String, Long> dateKeywordUvRDD = dateKeywordUsersRDD.mapToPair(
                
                new PairFunction<Tuple2<String,Iterable<String>>, String, Long>() {

                    private static final long serialVersionUID = 1L;
        
                    @Override
                    public Tuple2<String, Long> call(
                            Tuple2<String, Iterable<String>> dateKeywordUsers) throws Exception {
                        String dateKeyword = dateKeywordUsers._1;
                        Iterator<String> users = dateKeywordUsers._2.iterator();
                        
                        // 對用戶進行去重,并統計去重后的數量
                        List<String> distinctUsers = new ArrayList<String>();
                        
                        while(users.hasNext()) {
                            String user = users.next();
                            if(!distinctUsers.contains(user)) {
                                distinctUsers.add(user);
                            }
                        }
                        
                        // 獲取uv
                        long uv = distinctUsers.size();
                        
                        return new Tuple2<String, Long>(dateKeyword, uv);  
                    }
                    
                });
        
        //4、 將每天每個搜索詞的uv數據,轉換成DataFrame
        JavaRDD<Row> dateKeywordUvRowRDD = dateKeywordUvRDD.map(
                
                new Function<Tuple2<String,Long>, Row>() {

                    private static final long serialVersionUID = 1L;
        
                    @Override
                    public Row call(Tuple2<String, Long> dateKeywordUv) throws Exception {
                        String date = dateKeywordUv._1.split("_")[0];
                        String keyword = dateKeywordUv._1.split("_")[1];
                        long uv = dateKeywordUv._2;
                        return RowFactory.create(date, keyword, uv);
                    }
                    
                });
        
        List<StructField> structFields = Arrays.asList(
                DataTypes.createStructField("date", DataTypes.StringType, true),
                DataTypes.createStructField("keyword", DataTypes.StringType, true),
                DataTypes.createStructField("uv", DataTypes.LongType, true));
        StructType structType = DataTypes.createStructType(structFields);
        // 將該RDD轉換為DataFrame
        DataFrame dateKeywordUvDF = sqlContext.createDataFrame(dateKeywordUvRowRDD, structType);
        
        // 5、使用Spark SQL的開窗函數,統計每天搜索uv排名前3的熱點搜索詞
        dateKeywordUvDF.registerTempTable("daily_keyword_uv");  
        
        DataFrame dailyTop3KeywordDF = sqlContext.sql(""
                + "SELECT date,keyword,uv "
                + "FROM ("
                    + "SELECT "
                        + "date,"
                        + "keyword,"
                        + "uv,"
                        + "row_number() OVER (PARTITION BY date ORDER BY uv DESC) rank "
                    + "FROM daily_keyword_uv"  
                + ") tmp "
                + "WHERE rank<=3");  
        
        // 6、將DataFrame轉換為RDD,然后映射,計算出每天的top3搜索詞的搜索uv總數
        JavaRDD<Row> dailyTop3KeywordRDD = dailyTop3KeywordDF.javaRDD();
        
        JavaPairRDD<String, String> top3DateKeywordUvRDD = dailyTop3KeywordRDD.mapToPair(
                new PairFunction<Row, String, String>() {

                    private static final long serialVersionUID = 1L;

                    @Override
                    public Tuple2<String, String> call(Row row)
                            throws Exception {
                        String date = String.valueOf(row.get(0));  
                        String keyword = String.valueOf(row.get(1));  
                        Long uv = Long.valueOf(String.valueOf(row.get(2)));                         
                        return new Tuple2<String, String>(date, keyword + "_" + uv);
                    }
                    
                });
        
        JavaPairRDD<String, Iterable<String>> top3DateKeywordsRDD = top3DateKeywordUvRDD.groupByKey();
        
        JavaPairRDD<Long, String> uvDateKeywordsRDD = top3DateKeywordsRDD.mapToPair(
                new PairFunction<Tuple2<String,Iterable<String>>, Long, String>() {

                    private static final long serialVersionUID = 1L;

                    @Override
                    public Tuple2<Long, String> call(
                            Tuple2<String, Iterable<String>> tuple)
                            throws Exception {
                        String date = tuple._1;
                        
                        Long totalUv = 0L;
                        String dateKeywords = date;  
                        
                        Iterator<String> keywordUvIterator = tuple._2.iterator();
                        while(keywordUvIterator.hasNext()) {
                            String keywordUv = keywordUvIterator.next();
                            
                            Long uv = Long.valueOf(keywordUv.split("_")[1]);  
                            totalUv += uv;
                            
                            dateKeywords += "," + keywordUv;
                        }
                        
                        return new Tuple2<Long, String>(totalUv, dateKeywords);
                    }
                    
                });
        
        // 7、按照每天的總搜索uv進行倒序排序
        JavaPairRDD<Long, String> sortedUvDateKeywordsRDD = uvDateKeywordsRDD.sortByKey(false);
        
        //8、再次進行映射,將排序后的數據,映射回原始的格式,Iterable<Row>
        JavaRDD<Row> sortedRowRDD = sortedUvDateKeywordsRDD.flatMap(
                
                new FlatMapFunction<Tuple2<Long,String>, Row>() {

                    private static final long serialVersionUID = 1L;

                    @Override
                    public Iterable<Row> call(Tuple2<Long, String> tuple)
                            throws Exception {
                        String dateKeywords = tuple._2;
                        String[] dateKeywordsSplited = dateKeywords.split(",");  
                        
                        String date = dateKeywordsSplited[0];
                        
                        List<Row> rows = new ArrayList<Row>();
                        rows.add(RowFactory.create(date, 
                                dateKeywordsSplited[1].split("_")[0],
                                Long.valueOf(dateKeywordsSplited[1].split("_")[1]))); 
                        rows.add(RowFactory.create(date, 
                                dateKeywordsSplited[2].split("_")[0],
                                Long.valueOf(dateKeywordsSplited[2].split("_")[1]))); 
                        rows.add(RowFactory.create(date, 
                                dateKeywordsSplited[3].split("_")[0],
                                Long.valueOf(dateKeywordsSplited[3].split("_")[1]))); 
                        
                        return rows;
                    }
                    
                });
        
        //9、將最終的數據,轉換為DataFrame,并保存到Hive表中
        DataFrame finalDF = sqlContext.createDataFrame(sortedRowRDD, structType);
        
        finalDF.saveAsTable("daily_top3_keyword_uv");
        
        sc.close();
    }
}
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,837評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,196評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,688評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,654評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,456評論 6 406
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,955評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,044評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,195評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,725評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,608評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,802評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,318評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,048評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,422評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,673評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,424評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,762評論 2 372

推薦閱讀更多精彩內容