本案例旨在綜合使用Spark Core 和Spark Sql完成業務需求,具有一定的參考價值。
案例需求
- 篩選出符合查詢條件的數據
- 統計出每天搜索uv排名前3的搜索詞
- 按照每天的top3搜索詞的uv搜索總次數,倒序排序
- 將統計結果輸出
案例數據
日期 | 搜索詞 | 用戶 | 城市 | 平臺 | 版本 |
---|---|---|---|---|---|
2017-05-17 | Hadoop | user1 | 北京 | Android | 1.2 |
2017-05-17 | Hadoop | user2 | 北京 | Android | 1.2 |
2017-05-17 | Hadoop | user2 | 北京 | Android | 1.2 |
2017-05-17 | Hadoop | user3 | 北京 | Android | 1.2 |
2017-05-17 | Hadoop | user4 | 北京 | Android | 1.2 |
2017-05-17 | Scala | user1 | 天津 | Android | 1.2 |
2017-05-17 | Hadoop | user3 | 天津 | ISO | 1.2 |
2017-05-17 | Scala | user4 | 天津 | ISO | 1.2 |
2017-05-17 | Scala | user6 | 南京 | Android | 1.2 |
2017-05-18 | Scala | user1 | 天津 | Android | 1.2 |
2017-05-18 | Scala | user3 | 天津 | ISO | 1.2 |
2017-05-18 | Scala | user4 | 天津 | ISO | 1.2 |
2017-05-18 | Scala | user6 | 南京 | Android | 1.2 |
2017-05-18 | Spark | user7 | 天津 | Android | 1.2 |
2017-05-18 | Spark | user9 | 天津 | ISO | 1.2 |
2017-05-18 | Spark | user4 | 天津 | ISO | 1.2 |
2017-05-18 | Spark | user6 | 南京 | Android | 1.2 |
2017-05-18 | Spark | user6 | Android | 1.2 | |
2017-05-18 | Hadoop | user1 | 北京 | Android | 1.2 |
2017-05-18 | Hadoop | user2 | 北京 | Android | 1.2 |
2017-05-18 | Hadoop | user5 | 北京 | Android | 1.2 |
2017-05-18 | Hadoop | user3 | 北京 | Android | 1.2 |
2017-05-18 | Hadoop | user4 | 北京 | Android | 1.2 |
2017-05-18 | Hive | user1 | 北京 | Android | 1.2 |
2017-05-18 | Hive | user2 | 北京 | Android | 1.2 |
2017-05-18 | Hive | user5 | 北京 | Android | 1.2 |
2017-05-18 | Hive | user3 | 北京 | Android | 1.2 |
2017-05-18 | kafka | user4 | 北京 | Android | 1.2 |
2017-05-18 | kafka | user6 | 北京 | Android | 1.2 |
2017-05-18 | kafka | user7 | 北京 | Android | 1.2 |
2017-05-18 | kafka | user1 | 北京 | Android | 1.2 |
2017-05-18 | kafka | user2 | 北京 | Android | 1.2 |
2017-05-18 | SQL | user5 | 北京 | Android | 1.2 |
2017-05-18 | SQL | user3 | 北京 | Android | 1.2 |
2017-05-18 | SQL | user4 | 北京 | Android | 1.2 |
2017-05-18 | SQL | user2 | 北京 | Android | 1.2 |
2017-05-18 | SQL | user5 | 北京 | Android | 1.2 |
2017-05-18 | Mongodb | user3 | 北京 | Android | 1.2 |
2017-05-18 | redis | user4 | 北京 | Android | 1.2 |
原始數據存儲在hdfs中,數據項之間使用\t進行分割,部分數據項可能會有缺失。
實現思路
- 讀取hdfs上的原始數據并轉換為RDD
- 使用filter算子顧慮有效的數據
- 從系統日志中獲取有用的數據項并封裝成Row對象
- 將RDD<Row>轉換為Dataset<Row>
- 按日期分組統計搜索詞的搜索次數
- 使用窗口函數row_number獲取每日top 3熱詞
- 將統計結果輸出
示例代碼
以本地環境為例,生成環境只需把master和文件路徑變更一下即可。
Spark API 實現方式
//創建SparkSession對象
SparkSession session =SparkSession.builder()
.appName("DailyTop3Keyword")
.master("local[*]")
.getOrCreate();
//創建JavaSparkContext對象
JavaSparkContext jsc = JavaSparkContext.fromSparkContext(session.sparkContext());
//過濾條件
List<String> list = Arrays.asList(new String[]{"北京","天津","南京"});
//使用廣播變量進行性能優化
final Broadcast<List<String>> cities = jsc.broadcast(list);
//加載系統日志
JavaRDD<Row> rdd = jsc.textFile("D:/keywords.txt")
//過濾掉無效的日志信息
.filter(new Function<String, Boolean>() {
private static final long serialVersionUID = 1L;
@Override
public Boolean call(String line) throws Exception {
boolean flg = false;
for(String city : cities.value()){
if(line.contains(city)){
flg =true;
break;
}
}
return flg;
}
}).cache()
//從每行日志中獲取有用信息并封裝成Row對象
.map(new Function<String, Row>() {
private static final long serialVersionUID = 1L;
@Override
public Row call(String line) throws Exception {
String[] values = line.split("\t");
return RowFactory.create(values[0],values[1],values[2]);
}
}
);
//將JavaRDD<Row>轉換成Dataset<Row>
Dataset<Row> rows = session.createDataFrame(rdd, new StructType(new StructField[]{
DataTypes.createStructField("date", DataTypes.StringType, true),
DataTypes.createStructField("keyword", DataTypes.StringType, true),
DataTypes.createStructField("user", DataTypes.StringType, true)
}));
//按日期統計關鍵詞的搜索次數
Dataset<Row> kv = rows
.select(new Column("date"),new Column("keyword"),new Column("user"))
.groupBy("date","keyword")
.agg(countDistinct("user").alias("kv"))
.orderBy(new Column("date").asc(),new Column("kv").desc());
//使用窗口函數row_number獲取每日排名前三的搜索關鍵詞
kv.select(new Column("date")
,new Column("keyword")
,new Column("kv")
,row_number().over(Window.partitionBy(new Column("date")).orderBy(new Column("kv").desc())).alias("rank"))
.where("rank <=3")
.show();
SQL腳本實現方式
最后兩步也可以使用SQL腳本的方式進行實現。
//將倒數第三步的結果注冊成一個臨時表rows
rows.createOrReplaceTempView("rows");
//按日期統計關鍵詞的搜索次數并將統計結果注冊成臨時表kv
session.sql("select date,keyword,count(distinct user) kv from rows group by date,keyword order by date asc,kv desc")
.createOrReplaceTempView("kv");
//使用窗口函數row_number獲取每日排名前三的搜索關鍵詞
session.sql("select * from (select date,keyword,kv,row_number() over(partition by date order by kv desc) rank from kv) tmp where rank <= 3")
.show();
示例數據統計結果
date | keyword | kv | rank |
---|---|---|---|
2017-05-18 | Hadoop | 6 | 1 |
2017-05-18 | kafka | 5 | 2 |
2017-05-18 | Scala | 4 | 3 |
2017-05-17 | Hadoop | 4 | 1 |
2017-05-17 | Scala | 3 | 2 |
示例中需要引入的class
import static org.apache.spark.sql.functions.countDistinct;
import static org.apache.spark.sql.functions.row_number;
import java.util.Arrays;
import java.util.List;
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.broadcast.Broadcast;
import org.apache.spark.sql.Column;
import org.apache.spark.sql.Dataset;
import org.apache.spark.sql.Row;
import org.apache.spark.sql.RowFactory;
import org.apache.spark.sql.SparkSession;
import org.apache.spark.sql.expressions.Window;
import org.apache.spark.sql.types.DataTypes;
import org.apache.spark.sql.types.StructField;
import org.apache.spark.sql.types.StructType;