背景
前兩天面試中遇到一個比較基礎的計算UV & PV 的問題。思路比較簡單,最重要的是 手寫代碼 ,平常我們都是在IDE 中編寫代碼,手寫代碼的時候大多是情況下都是使用IDE 的提示,遇到手寫的時候,就算這種簡單的代碼也不一定寫得出來。
那天採取的一個思路是:先把思路寫出來,然後,時間夠再添代碼進去。有時候確實一些函數拼不出來什麼的,但是思路在,好過白卷。
由上面的背景引出 使用SparkSQL 實現 統計 UV & PV 的問題。
數據如下:格式 ip,請求方式,路徑
192.168.0.112,post,/app2/index.html
192.168.2.11,get,/app1/user?id=3
192.168.2.11,post,/app1/submittoder
192.168.0.122,post,/app1/goods
....
需求: 求出每個APP 的訪問訪問次數(UV)和獨立IP 訪問次數(PV)
整個過程大概分爲4步:
1、先構建SparkSession 入口
//構建 sparksession
val sparkSession: SparkSession = SparkSession.builder().appName("pv_uv").master("local[3]").getOrCreate()
2、讀取文件
val lines: RDD[String] = sparkSession.sparkContext.textFile("C:\\Users\\Administrator\\Desktop\\testData\\log.txt")
3、整理數據並轉換爲 DataFrame
第一方式:導入隱式轉換,使用 toDF 函數把 RDD 轉換成 DataFrame
import sparkSession.implicits._
val ipAndAppDF: DataFrame = lines.map(line => {
//切分一行數據
val fileds: Array[String] = line.split(",")
//提取 ip
val ip: String = fileds(0)
//應用
val app: String = fileds(2).split("/")(1)
(ip, app)
}).toDF("ip", "app")
第二種方式:使用 RDD[Row] + schema , 然後轉換爲 DataFrame
val ipAndAppRDD: RDD[Row] = lines.map(line => {
//切分一行數據
val fileds: Array[String] = line.split(",")
//提取 ip
val ip: String = fileds(0)
//應用
val app: String = fileds(2).split("/")(1)
// 包裝成 Row
Row(ip, app)
})
//準備schema 信息
val schema = StructType(List(
StructField("ip", StringType, true),
StructField("app", StringType, true)
))
//將RowRDD 關聯 schema
val bdf: DataFrame = sparkSession.createDataFrame(ipAndAppRDD, schema)
4、計算 PV & UV
第一種方式: 使用 SQL 方式,需要先進行註冊臨時表信息
//執行 sql 計算 uv & pv
val pv_uv: DataFrame = sparkSession.sql(" select app,count(1) as pv,count(distinct ip) as uv from v_log group by app ")
// 打印數據
pv_uv.show()
第二種方式: 使用調用內置函數的方式計算 pv & uv,需要導入函數
// 導入spark.sql 內置函數 ,去重方法 countDistinct 注意使用方法
import org.apache.spark.sql.functions._
val result: DataFrame = ipAndAppDF.groupBy($"app").agg(count("*") as "pv", countDistinct('ip) as "uv")
// 打印數據
result.show()
5、關閉資源
sparkSession.stop()
最終結果:
建議:平常在寫代碼的時候,儘量能自己把 api 敲全,能不使用提示最好,這樣會更能加深印象。遇到變態的手寫代碼面試題,可能會有幫助
最後上完整代碼:
package com.zhouq.spark.sql
import org.apache.spark.rdd.RDD
import org.apache.spark.sql.types._
import org.apache.spark.sql.{DataFrame, Row, SparkSession}
/**
* 面試題 使用sparksql 查UV PV
*
* 數據格式:ip,請求方式,url
*
* 192.168.0.112,post,/app2/index.html
*
*/
object LogUV_PV {
def main(args: Array[String]): Unit = {
/**
* 第一步:創建 SparkSession
*/
//構建 sparksession
val sparkSession: SparkSession = SparkSession.builder().appName("pv_uv").master("local[3]").getOrCreate()
/**
* 第二步:讀取文件
*/
val lines: RDD[String] = sparkSession.sparkContext.textFile("C:\\Users\\Administrator\\Desktop\\testData\\log.txt")
/**
* 第三步:整理數據並轉換爲 DataFrame
*/
// 第一種方式:使用toDF 函數,需要導入隱式轉換,
import sparkSession.implicits._
val ipAndAppDF: DataFrame = lines.map(line => {
//切分一行數據
val fileds: Array[String] = line.split(",")
//提取 ip
val ip: String = fileds(0)
//應用
val app: String = fileds(2).split("/")(1)
(ip, app)
}).toDF("ip", "app")
//第二種方式:使用RDD[Row] + schema ,然後轉換爲 DataFrame
// val ipAndAppRDD: RDD[Row] = lines.map(line => {
// //切分一行數據
// val fileds: Array[String] = line.split(",")
// //提取 ip
// val ip: String = fileds(0)
// //應用
// val app: String = fileds(2).split("/")(1)
// Row(ip, app)
// })
//
// //準備schema 信息
// val schema = StructType(List(
// StructField("ip", StringType, true),
// StructField("app", StringType, true)
// ))
//
// //將RowRDD 關聯 schema
// val bdf: DataFrame = sparkSession.createDataFrame(ipAndAppRDD, schema)
/**
* 第四步: 使用執行 SQL 的方式 計算 pv & uv
*/
//註冊臨時表
ipAndAppDF.createTempView("v_log")
//執行 sql 計算 uv & pv
val pv_uv: DataFrame = sparkSession.sql(" select app,count(1) as pv,count(distinct ip) as uv from v_log group by app ")
pv_uv.show()
// /**
// * 第四步: 使用調用內置函數的方式計算 pv & uv
// * 需要導入 函數
// */
// import org.apache.spark.sql.functions._
// val result: DataFrame = ipAndAppDF.groupBy($"app").agg(count("*") as "pv", countDistinct('ip) as "uv")
//
// result.show()
/**
* 第五步:關閉資源
*/
sparkSession.stop()
}
}
有興趣的歡迎關注,大家一起交流學習。