十月底, 參加了公司的一個spark大數據比賽, 題目比較簡單, 但是由於自己缺乏此方面的業務知識, 所以對我來說解答的過程還是很有收穫的 , 現在記錄如下:
題目
數據表:
CREATE EXTERNAL TABLE fact_ipp_flux_limit(
clttime timestamp,
clttimeint bigint,
imsi string,
ci bigint,
url string,
tcpwrldelay bigint
)
PARTITIONED BY (
reportdate string,
reporthour int,
reportneid int)
ROW FORMAT DELIMITED FIELDS TERMINATED BY ‘,’
STORED AS TEXTFILE LOCATION ‘/zxvmax/telecom/cn/test_data/fact_ipp_flux_limit’;
該數據表是統計的某地區電信用戶手機上網情況, 其中clttime是訪問對應url的時間, imsi是手機卡標識, ci是小區ID, url則是訪問鏈接. 數據如下圖所示:
問題 :
1. 根據提供的數據,輸出每五分鐘,人流量最大的TOP3小區。(簡單)
時間:14:00 – 17:00
字段:時間(clttime),小區(ci), 用戶(imsi)
2. 根據URL識別搜索引擎的關鍵詞,並給出搜索熱度排名。(中等)
時間:00:00 – 23:59
字段:url,時間(clttime)
3. 根據URL識別什麼時段大家更喜歡看新聞。(中等)
時間:00:00 – 23:59
字段:url,時間(clttime)
4. 根據URL識別最受歡迎的網站。(中等)
時間:00:00 – 23:59
字段:url,時間(clttime)**
提交指南
我們以wordcount爲例介紹下在linux環境下的提交方式
示例代碼(wordcount.scala):
package bigdata
import org.apache.spark.SparkConf
import org.apache.spark.SparkContext
import org.apache.spark.SparkContext._
object WordCount
{
def main(args: Array[String])
{
if (args.length < 1)
{
System.err.println("Usage: <file>")
System.exit(1)
}
val conf = new SparkConf()
val sc = new SparkContext(conf)
val line = sc.textFile(args(0))
val array = line.flatMap(_.split(" ")).map((_, 1)).reduceByKey(_ + _)
array.saveAsTextFile("/odpp/files/output")
sc.stop()
}
}
編譯
scalac -classpath /home/mr/spark/lib/spark-assembly-1.4.1-hadoop2.5.0-cdh5.3.2.jar -d wordcount.jar wordcount.scala
此處要注意scala的版本要與spark-assembly的scala編譯版本最好一致, 不然可能在運行時出錯.上述jar是由scala 2.10.4編譯的
提交
spark-submit --master yarn-cluster --queue user07_space --driver-memory 3G --executor-memory 2G --num-executors 3 --class bigdata.wordcount /home/wordcount.jar xxx.txt
其中,(./spark-submit --help可以查看詳情)
yarn-cluster意思是放到yarn集羣中執行作業, 如果單機模式的話可以用--master local[8]
--queue gzwspace 指定作業執行的用戶空間,
--driver-memory 3G 指定啓動作業時需要的內存大小
--executor-memory 2G 指定執行作業時,每個集羣節點的內存大小
--num-executors 3 執行執行的節點數
--class bigdata.wordcount 指定class名
/home/wordcount.jar 指定jar包
xxx.txt 指定args參數
–master參數的補充說明:
Master URL Meaning local 本地單線程 local[K] 本地K個線程, 建議設置城與cpu核數想相同 local[*] 本地多線程運行, 運行時的線程與cpu核數相同 spark://HOST:PORT 鏈接Spark Standlone集羣管理器主機,默認端口爲7077 yarn-client 採用client模式鏈接Yarn資源管理器, 集羣位置信息配置在HADOOP_CONF_DIR或 YARN_CONF_DIR變量中 yarn-cluster 採用CLuster模式鏈接Yarn資源管理器, 集羣位置信息與上相同 simr://HOST:PORT 兼容Hadoop 1.0 mesos://HOST:PORT 鏈接Mesos資源管理器
用到的點
- 使用spark-shell時候, 可以直接使用sqlContext提交sql語句, 比如:
sqlContext.sql(“select * from fact_ipp_flux_limit limit 10”).show
show是查看, 或者也可以打印: .collect.foreach(println) 但是使用spark-submit提交jar作業的時候, 要自己定義sqlContext:
val conf = new SparkConf()
val sc = new SparkContext(conf)
val sqlContext = new HiveContext(sc)shuffle時候task數目優化(經驗值是可用cpu核數的三倍)
val buckets = try { sc.getConf.get("spark.cores.max").toInt * 3 } catch { case e: Exception => 200 } sqlContext.sql(s"set spark.sql.shuffle.partitions=$buckets")
粗略的解釋就是:如果這個值太低, 也就是task任務數太少, 使得每隔task處理時間過長容易Straggle, 而且有GC. 如果太大就有很多任務啓動開銷. 雖然設置大一點可以解決GC, 但有時候大了也沒用是因爲有數據傾斜(某一個task處理了大量任務,而其他task是清閒的)
url解碼, 使用java的URLDecoder
try { result = java.net.URLDecoder.decode("%e4%bd%a0%e5%a5%bd", ENCODE);//encode可有可無,代表編碼 } catch (UnsupportedEncodingException e) { e.printStackTrace(); }
用零寬斷言來提取搜索引擎詞彙,如 百度的鏈接:
"""(?<=&word=).*(?=(&|$))""".r findFirstIn """fdsfsd&word=fdsfsdfsfdsf"""
第一題裏, 每五分鐘, 可以將時間換算成分鐘然後 /5, 數值相同的即是在同一個五分鐘內
- 用開窗函數取每個分組的top3值:
row_number() over (partition by hour,minute5 order by usercount desc)
- spark裏的dataframe相關api要多學一下比如show, select等都很有用.
結論(簡單版)
- 題目一
- 題目二
- 題目三
- 題目四
源代碼
// 此次比賽提供的spark-assembly-1.4.1-hadoop2.5.0-cdh5.3.2版本是由scala2.10.4編譯的
// 但是環境上scala版本爲2.11.8, 執行會報錯, 所以編譯時請選擇2.10.4的環境
package bigdata
import org.apache.spark.sql.SQLContext
import org.apache.spark.sql.hive.HiveContext
import org.apache.spark.{SparkConf, SparkContext}
import scala.util.Try
object SparkGame {
def main(args: Array[String]) {
if (args.length < 1) {
System.err.println("============請在提交時添加參數: 1,2,3,4分別代表四個題目================")
System.exit(1)
}
val conf = new SparkConf()
val sc = new SparkContext(conf)
val sqlContext = new HiveContext(sc)
val buckets = try {
sc.getConf.get("spark.cores.max").toInt * 3
} catch {
case e: Exception => 200
}
sqlContext.sql(s"set spark.sql.shuffle.partitions=$buckets")
try {
args(0) match {
case "1" => top3Cell(sqlContext)
case "2" => searchHot(sqlContext)
case "3" => hotNews(sqlContext)
case "4" => popularWebSite(sqlContext)
case _ =>
System.err.println("=========參數錯啦=========")
System.exit(1)
}
} catch {
case e: Exception => e.printStackTrace()
} finally {
sc.stop()
}
}
// 第一題 各時段Top3小區
private def top3Cell(sqlContext: HiveContext): Unit = {
def sql =
"""
select ci,usercount,date,hour,minute5*5 as minute
from (
select ci,date,hour,minute5,usercount,row_number() over (partition by hour,minute5 order by usercount desc) as rn
from(
select date,count(distinct imsi) as usercount,ci,hour,minute5
from (
select reportdate as date,reporthour as hour,cast(minute(clttime)/5 as int) as minute5,ci,imsi
from fact_ipp_flux_limit
where reportdate='2016-05-28' and reporthour in (14,15,16) and length(ci)=15
) a
group by hour,minute5,ci,date
) b
) c where rn<=3
"""
val temp = sqlContext.sql(sql).rdd.persist
temp.collect.foreach(println)
}
// 第二題 搜索引擎關鍵詞排名
private def searchHot(sqlContext: HiveContext): Unit = {
def sql =
"""
select url from fact_ipp_flux_limit
"""
val baiduReg = """(?<=&word=).*(?=(&|$))""".r
val shenmaReg = """(?<=s\?q=).*(?=(&|$))""".r
val sogouReg = """(?<=keyword=).*(?=&)""".r
val q360Reg = """(?<=index.php\?q=).*(?=&)""".r
val searchKW = sqlContext.sql(sql).rdd.map(row => row.getString(0)).filter(_.length > 1)
.map { url =>
val baidu = baiduReg findFirstIn url
val shenma = shenmaReg findFirstIn url
val sogou = sogouReg findFirstIn url
val q360 = q360Reg findFirstIn url
val list = List(baidu, shenma, sogou, q360).filter(_ != None)
if (list.isEmpty)
""
else
Try {
list.head.getOrElse("").split("&").head
}.getOrElse("")
}.filter(_.length > 0)
.map { x => Try(java.net.URLDecoder.decode(x, "utf-8")).getOrElse("") }
.filter(_.length > 0)
.map((_, 1)).reduceByKey(_ + _)
.sortBy(_._2, ascending = false).persist
searchKW.take(150).foreach(println)
}
// 第三題 新聞熱度
private def hotNews(sqlContext: HiveContext): Unit = {
import sqlContext.implicits._
def sql =
"""
select imsi,url,reporthour from fact_ipp_flux_limit
"""
val newsWebs = List("info.3g.qq.com", "kb.qq.com", "sina.cn", "toutiao.com", "yidianzixun", "ifeng.com", "sohu.com", "3g.163.com", "xinhuanet", "people.com", "myzaker", "weibo.com","news")
val hotNews = sqlContext.sql(sql).rdd.map(row => (row.getString(0), row.getString(1), row.getInt(2))).filter(_._2.length > 1)
.map { urlInfo => if (newsWebs.exists(urlInfo._2.contains(_))) URLInfo(urlInfo._1, "1", urlInfo._3) else URLInfo(urlInfo._1, "0", urlInfo._3) }
hotNews.toDF.registerTempTable("newsurl")
def sql2 =
"""select sum(p) as hotrate,reporthour
from (
select count(case when url=1 then 1 else null end)/count(*) as p,imsi,reporthour
from newsurl
group by imsi,reporthour
) as userrate
group by reporthour"""
sqlContext.sql(sql2).show()
}
// 第四題
private def popularWebSite(sqlContext: HiveContext): Unit = {
def sql =
"""
select url from fact_ipp_flux_limit
"""
val popularRDD = sqlContext.sql(sql).rdd
.map(row => row.getString(0))
.filter(_.length > 1)
.map(x => filterDomin(x))
.filter(_.length > 1)
.map(x => x.split("\\.").init.last + "." + x.split("\\.").last)
.map((_, 1)).reduceByKey(_ + _)
.sortBy(_._2, ascending = false).persist
//popularRDD.saveAsTextFile("/odpp/spaces/user07_space/files/popularwebsite")
popularRDD.take(150).foreach(println)
}
private def filterDomin(str: String): String = {
str.split("/", -1).dropRight(1).find(x => findTopDomain(x)).getOrElse("")
}
private def findTopDomain(x: String): Boolean = {
val domain = List(".cn", ".com", ".net", ".org", ".gov", ".edu")
domain.exists(x.contains(_))
}
}
case class URLInfo(imsi: String, url: String, reporthour: Int)