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