Spark大數據比賽經驗記錄(含sparksubmit 提交方法)

十月底, 參加了公司的一個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資源管理器

用到的點

  1. 使用spark-shell時候, 可以直接使用sqlContext提交sql語句, 比如:
    sqlContext.sql(“select * from fact_ipp_flux_limit limit 10”).show
    show是查看, 或者也可以打印: .collect.foreach(println)
  2. 但是使用spark-submit提交jar作業的時候, 要自己定義sqlContext:
    val conf = new SparkConf()
    val sc = new SparkContext(conf)
    val sqlContext = new HiveContext(sc)

  3. 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是清閒的)

  4. url解碼, 使用java的URLDecoder

    try {
            result = java.net.URLDecoder.decode("%e4%bd%a0%e5%a5%bd", ENCODE);//encode可有可無,代表編碼
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
  5. 零寬斷言來提取搜索引擎詞彙,如 百度的鏈接:

    """(?<=&word=).*(?=(&|$))""".r findFirstIn """fdsfsd&word=fdsfsdfsfdsf"""
  6. 第一題裏, 每五分鐘, 可以將時間換算成分鐘然後 /5, 數值相同的即是在同一個五分鐘內

  7. 用開窗函數取每個分組的top3值: row_number() over (partition by hour,minute5 order by usercount desc)
  8. spark裏的dataframe相關api要多學一下比如show, select等都很有用.

結論(簡單版)

  1. 題目一
    這裏寫圖片描述
  2. 題目二
    這裏寫圖片描述
  3. 題目三
    這裏寫圖片描述
  4. 題目四
    這裏寫圖片描述

源代碼

// 此次比賽提供的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)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章