大數據平臺的使用(Hadoop 生態圈、CDH)

目錄

一、shell自行搭建Hadoop集羣(2節點以上)

1.1 系統準備

準備centos6.5系統,命名爲master,按照虛擬機安裝鏡像的步驟安裝之後,克隆一份到slave,也就是準備一個mater節點,一個slave節點的系統開發環境

1.2 系統基礎配置

關閉防火牆,設置靜態IP,設置SSH免密登陸。
  關閉防火牆的目的是,我們需要配置的組件應用程序需要使用一些端口,這些端口容易被防火牆封掉,那麼這時候有兩種選擇:要麼關閉防火牆,要麼將某些應用程序需要的端口在防火牆的規則中進行配置。在實驗環境下,暫且不考慮黑客攻擊的情況,所以就直接關掉防火牆,以方便下面的配置。
  設置靜態IP的原因是,每次系統重新啓動之後,有些系統默認設置下,IP都會隨機改變,我們需要在host文件中修改主機名列表,便於master和slave IP的識別和後續操作,所以需要設置靜態IP。
  SSH免密登陸出現在scp遠程複製、Hadoop啓動、Spark啓動等操作中,如果不設置免密登陸每次都要輸入很多次密碼。

1.3 組件安裝與配置

Hadoop生態圈含有很多組件,而且呈現出一個堆積金字塔狀態,上面的組件是基於下面組建的成功運行來搭建的,所以我們再手動搭建組件的時候,也要遵循先後的原則,在這次大作業中,可能只需要用到最基礎的組件和spark,以及oozie,在這裏我只介紹最基礎的組件,oozie第五部分會詳細講。

1.3.1 Hadoop

(1) hadoop安裝與配置

我們可選擇用yum命令進行安裝,一行命令解決所有;但是在這裏採取手動安裝的方式,自行配置環境變量等,以便於自己更好的理解。首先下載hadoop-2.5.2.tar.gz,下載的方式有很多,wget+下載網址命令很方便,但是有的較老版本的組件安裝包的鏈接已經失效,所以我們可以前往apache官網根據自己的需要進行下載,隨後解壓,解壓目錄和文件列表如下。

解壓完成後的下一步就是配置環境變量運行腳本,進入修改hadoop-2.5.2/etc/hadoop/hadoop-env.sh,將之前安裝好的java路徑寫入。

以同樣的方法配置環境變量yarn-env.sh。然後,配置核心組件core-site.xml、文件系統hdfs-site.xml、調度系統yarn-site.xml、計算框架mapred-site.xml。配置完成後,用scp將整個hadoop文件遠程複製到slave節點的\~節點下。

(2) hadoop啓動

在master節點下輸入jps會發現有4個進程,在slave節點下輸入jps會發現有3個進程,說明配置成功。

爲了進一步覈實與驗證,我們打開瀏覽器,輸 http://master:18088 ,顯示出如下配置。

(3) HDFS文件目錄

hadoop fs -ls /查看hdfs文件目錄。

1.3.2 Hive

配置hive的方法同理,在這裏遇到一個問題,那就是啓動hive時報錯。

根據報錯,排查原因發現是mysql沒有開啓。解決辦法:重啓mysql服務,注意要在root下執行命令纔會成功。

重新啓動hive成功。

1.3.3 Hbase

進入到hbase目錄,運行start-hbase.sh,啓動Hbase。

瀏覽器輸入:http://master:60010, 打開控制面板,啓動成功。

1.3.4 Spark

安裝並配置spark,進入到spark目錄下並啓動spark。jps查看進程,如下圖所示,master節點下增加了兩個進程,slave節點下增加了一個進程。其中Worker進程運行具體邏輯的進程。

執行./spark-shell得到命令窗口,spark默認的是scala語言。

瀏覽器中輸入:http://master:4040,得到spark臨時控制面板,這不是主要的界面,所以我稱之爲臨時控制面板,具體內容第四部分會詳細講。

二、Cloudera CDH安裝Hadoop平臺

2.1 Cloudera quickstart 安裝

在Cloudrea官網上下載quick-start vm鏡像,在虛擬機中分配8G內存啓動。在虛擬機中安裝中文支持包,並更改系統語言爲中文,這時候將cloudera manager控制面板的語言更改爲中文,纔會成功.安裝好的CDH如下:

2.2 CDH 中HQL數據操作

在HUE頁面下,建表gps,並將gps.csv數據傳入到hive數據庫中,實施查詢功能。

三、集羣中的HQL數據操作

進入到hive目錄下並啓動hive。

3.1 創建表

創建表gps,以便後面導入2016-06-01重慶部分地區的gps數據,根據時間時間建立分區,數據之間以逗號間隔。

3.2 創建分區

爲表gps增加兩個分區,一個是20160601,代表的是2016-06-01這一天,另外一個是20160602desc gps查看錶gps的結構。

3.3 Hive數據導入

Hive數據的導入可以分爲三種,一是本地文件的導入,二是hdfs文件的導入,三是insert或者創建表時直接導入。我首先嚐試本地文件的導入,將gps數據上傳到系統中,輸入以下命令將其導入到gps表,並聲明放入的分區。

3.4 Hive操作

select基礎查詢導入的數據。

3.5 hdfs目錄分區

導入hive的數據都默認放在了hdfs下的hive倉庫中,具體路徑如下。而且分區的建立,使得數據的存放分隔而獨立。分別在gps下面建立了date=20160601date=20160602文件夾。

四、Spark程序分析HDFS或Hive數據

4.1版本以及流程測試

4.1.1 準備maven集成環境

關於第四部分用spark程序分析HDFS或者Hive數據,我首先做一個簡單的詞頻統計來實現讀取HDFS數據的任務,目的在於測試一下自己win10系統下scala-spark的版本與我的集羣的兼容情況。因爲scala與spark的版本要兼容,運行的程序才能最大限度不報錯。
  我win10下的scala是2.10.4版本,spark版本爲1.6,可以兼容運行。然而我的集羣下的spark版本爲1.2,最終要把代碼打包到集羣上運行,所以測試一下是有必要的。我的任務流程爲win10下面打jar包,上傳到master集羣,然後採用spark集羣的方式運行。首先將win10下IDEA中的依賴包替換成集羣中的版本:spark-assembly-1.2.0-hadoop2.4.0
  其次,用IDEA+Maven+Scala創建wordcount,並打包jar,項目的目錄結構如下:

注意要導入scala-sdk,之後編輯代碼,setMaster("local")代表以本地模式運行,導入文件路徑,運行WordCount,輸入控制檯如下所示,只是完成了map的功能。

4.1.2 生成jar包上傳集羣

之後準備打jar包,並上傳到集羣運行,可以在代碼中更改運行方式,更改爲setMaster("spark://master:7077"),意爲在集羣上運行,spark://master:7077是集羣運行的默認端口。也可以不更改,那麼代碼就只會在master節點上單機運行,在這裏先不做更改,第六部分學習Mllib的時候再更改爲集羣的方式運行。其次,要修改導入的文件,將第八行改爲sc.textfile(args(0))args(0)是傳入的第一個參數。
  更改完畢後,準備打jar包,參考網上的快捷打包方式很簡單的就在out文件夾裏生成了打好的jar包,在這裏要注意把多餘的依賴全都刪掉,只留下下圖中的那一個。

提交到集羣,spark-submit命令運行,運行的結果跟IDEA裏面運行的結果一模一樣,如下。

4.1.3 Spark UI界面

在運行途中,打開:http://master:4040是可以看到此時job的運行情況的,但是一旦sc.stop,也就是job任務完畢,SparkContext終止,那麼網址便不可訪問。因爲每一個SparkContext會發佈一個web界面,默認端口是4040,它顯示了應用程序的有用信息。如果某臺機器上運行多個SparkContext,它的web端口會自動連續加一,比如4041,4042,4043等等。就像第一部分中啓動./spark-shell,其實啓動過後,就是啓動了一個SparkContext,終止命令窗口時,就是執行了sc.stop命令。
  但是隻要hadoop啓動成功,spark集羣啓動成功,那麼spark集羣的web端口:8080便會一直可用,http://master:8080 可以一直訪問。

Spark 程序運行完(成功/失敗)後,我們將無法查看Application的歷史記錄,但是按照經驗來說這不應該啊,hadoop生態圈中對於運行日誌的保存至關重要,怎麼會出現這樣的情況呢?google一下,果然有解決辦法,那就是Spark history Server
  Spark history Server就是爲了應對這種情況而產生的,通過配置可以在Application執行的過程中記錄下了日誌事件信息,那麼在Application執行結束後,WEBUI就能重新渲染生成UI界面展現出該Application在執行過程中的運行時信息。具體的配置很簡單,在這裏就不在闡述。

經過測試,版本應該沒有太大的問題,那麼可以安心的學習Mllib,不再爲版本和集羣環境而困擾。

五、使用oozie調度spark job

Apache Oozie 是一個用於管理 Apache Hadoop 作業的工作流調度程序。Oozie 非常靈活。人們可以很容易啓動,停止,暫停和重新運行作業。Oozie 可以很容易地重新運行失敗的工作流。可以很容易重做因宕機或故障錯過或失敗的作業。甚至有可能跳過一個特定故障節點。oozie本身 apache 只提供源碼,需要自己編譯,在節點上自行安裝並編譯oozie稍微有點複雜,在CDH上面就可以很簡單的使用,但是也不妨嘗試一下,所以在本節中,我在我的master上自行搭建oozie的同時,也在CDH上面學會使用。

5.1 oozie的編譯、安裝與配置

首先根據網上的教程,額外安裝Maven 3.0.1+、Pig 0.7+,趁此機會也學習一下Pig。

5.1.1 pig 安裝與配置

Pig 有兩種運行模式: Local 模式和 MapReduce 模式。當 Pig 在 Local 模式運行的時候, Pig 將只訪問本地一臺主機;當 Pig 在 MapReduce 模式運行的時候, Pig 將訪問一個 Hadoop 集羣和 HDFS 的安裝位置。這時, Pig 將自動地對這個集羣進行分配和回收。因爲 Pig 系統可以自動地對 MapReduce 程序進行優化,所以當用戶使用 Pig Latin 語言進行編程的時候,不必關心程序運行的效率, Pig 系統將會自動地對程序進行優化。這樣能夠大量節省用戶編程的時間。
  Pig 的 Local 模式和 MapReduce 模式都有三種運行方式,分別爲: Grunt Shell 方式、腳本文件方式和嵌入式程序方式。
  下載pig-0.7版本,配置好profile下的環境變量,輸入pig -help驗證成功。

運行pig -x localGrunt Shell方式執行。

5.1.2 更改maven中央倉庫

國外鏡像下載很慢,所以將Maven的中央倉庫修改爲阿里雲的倉庫地址,但是修改後會出現依賴包不全的問題,下面會解決。

<mirror>
    <id>nexus-aliyun</id>
    <mirrorOf>*</mirrorOf>
    <name>Nexus aliyun</name>
    <url>http://maven.aliyun.com/nexus/content/groups/public</url>
</mirror>

5.1.3 下載oozie並編譯

去Apache官網下載oozie-4.3.0.tar.gz(不是最新版本),解壓到\~目錄。進入解壓後的目錄
oozie-4.3.0,執行mvn clean package assembly:single -DskipTests 編譯命令,在阿里雲鏡像庫中編譯oozie。

第一次編譯過程中,報出找不到依賴包的錯誤:Could not find artifact org.pentaho:pentaho-aggdesigner-algorithm:jar:5.1.5-jhyde in Central。下載不了該jar包我就去手工下載,先找到下載地址,就會發現該jar包來源根本不是maven的central倉庫,而是spring。下載地址爲:http://repo.spring.io/plugins-release/org/pentaho/pentaho-aggdesigner-algorithm/5.1.5-jhyde/pentaho-aggdesigner-algorithm-5.1.5-jhyde.jar
  進入系統的\~/.m2/repository/org/pentaho/pentaho-aggdesigner-algorithm/5.1.5-jhyde/目錄下,rm -rf \*清空目錄,手工上傳下載好的jar包。
  第二次、第三次編譯過程中,仍然報出找不到依賴包的錯誤。這時,我就依照報錯的提示找到相應的依賴包,手動下載並上傳到對應的依賴目錄。第四次編譯的時候,成功,輸入以下內容,最後一行顯示build
success。

5.1.4 報錯以及解決

生成oozie數據庫腳本文件,初始化mysql時報錯:ERROR 1045 (28000): Access denied for user \'oozie\'@\'localhost\' (using password: YES)

解決辦法,爲oozie用戶授權。

再次啓動oozie,運行bin/oozied.sh start,得到以下顯示,代表成功。

5.1.5 驗證oozie是否安裝成功

運行bin/oozie admin -oozie http://localhost:11000/oozie -status,輸出System mode: NORMAL即表示配置成功。

在瀏覽器中打開http://localhost:11000/oozie/,顯示如圖所示。

5.2 CDH中使用Oozie調度Spark

進入到HUE界面,打開hdfs,傳入依賴包SparkTest.jarword_txt文件。

點擊Query,新建一個spark任務,依照下圖傳入相應的依賴包和設置相應的參數。

然後點擊左下角的執行按鈕,在CDH裏面執行簡單的spark操作,結果如下圖。

創建Oozie工作流。注意,只有新建了spark任務之後,創建Oozie工作流時纔可以識別到傳入的spark任務。在下圖中,我們看到有hive任務、pig任務、spark-shel腳本、spark等等,這些都是Oozie支持的作業。

設置完畢後,點擊執行按鈕開始執行Oozie調度。

在執行之前,有個選項:Do a dryrun before submitting。也就是可以選擇先執行一個演練任務,具體可以在Oozie UI界面中看到。

如下圖在Oozie UI界面中我們可以看到此次調度的情況。

電腦配置問題,12G的內存,分配8G+2核CPU給CDH都會很卡,勉強趕上進度,運行一個很簡單詞頻統計都要卡半天,所以有一些細小的問題不在改動,因爲太浪費時間了,等更換了電腦之後,按照CDH的官網參看文檔,一點點再來學習,對我來說還是蠻有趣的,所以大作業中關於CDH的部分到此爲止。

六、spark mllib算法使用與數據分析

6.1 20 news_groups數據與Mllib調用

因爲20 news_groups數據量還是蠻大的(相對於的電腦的配置來說),所以我並沒有全部使用。用spark的一個好處也是我們可以本地運行小批量數據集,準確無誤後再在集羣上運行整個大數據集,節約時間。數據一共大約20000條,我從中均勻選擇了200條(每類10條)作爲訓練集,測試集只選了10條左右,只是爲了在本地測試代碼,所以效果沒那麼好。Maven項目的Pom.xml配置如下:

6.1.1 讀取數據

參考《Spark機器學習》和scala API文檔,我對數據的分析處理,代碼如下。

val sparkConf = new SparkConf().setAppName("SparkNLP").setMaster("spark://master:7077")
val data = args(0)
val sc = new SparkContext(sparkConf)
val rdd = sc.wholeTextFiles(data) //加載數據
val text = rdd.map { case (file, text) => text }
text.cache()
println(text.count)//200
val news_groups = rdd.map { case (file, text) => file.split("/").takeRight(2).head }
val countByGroup = news_groups.map { n => (n, 1) }.reduceByKey(_ + _).collect()
.sortBy(-_._2).mkString("\n")
println(countByGroup)

項目的運行環境設置爲spark://master:7077,意爲在集羣上運行。讀取第一個參數,也就是訓練集的hdfs路徑。之後加載數據,對數據進行一些轉換操作,注意在這裏將相應的RDD進行cache或者persist操作,是爲了將其緩存下來,因爲RDD懶惰執行的特性,每一次的Action操作都會將無環圖所涉及的RDD執行一遍,所以將常用的RDD緩存下來可以節約時間。
  補充一點,reduceByKey等函數,以及一些標識符的使用在這裏可能會報錯,不被識別,原因是在spark1.2.0以及之前的版本中,RDD沒有reduceByKey這些方法。將其隱式轉換成PairRDDFunctions才能訪問,在前面導入語句:import org.apache.spark.SparkContext._
  轉換操作中,sortBy(-_._2)的意思是將新聞主題按主題統計個數並按從大到小排序。這段代碼最終得到的是按照數量進行排序的新聞主題。

6.1.2 基本分詞和詞組過濾

val whiteSpaceSplit = text.flatMap(t => t.split(" ").map(_.toLowerCase))
val nonWordSplit = text.flatMap(t => t.split("""\W+""").map(_.toLowerCase)) //把不是單詞的字符過濾掉
val regex = """[^0-9]*""".r
val filterNumbers = nonWordSplit.filter(token => regex.pattern.matcher(token).matches)
val tokenCounts = filterNumbers.map(t => (t, 1)).reduceByKey(_ + _)
val oreringDesc = Ordering.by[(String, Int), Int](_._2)
println(tokenCounts.top(20)(oreringDesc).mkString("\n"))
val stopwords = Set(
"the","a","an","of","or","in","for","by","on","but","is","not","with","as","was","if","they","are","this","and","it","have","from","at","my","be","that","to"
)//停用詞表
//過濾掉停用詞
val tokenCountsFilteredStopWords = tokenCounts.filter{  case (k,v) => ! stopwords.contains(k) }
//過濾掉僅僅含有一個字符的單詞
val tokenCountsFilteredSize = tokenCountsFilteredStopWords.filter{case (k,v) => k.size >= 2}
//基於頻率去除單詞:去掉在整個文本庫中出現頻率很低的單詞
val oreringAsc = Ordering.by[(String, Int), Int](- _._2)//新建一個排序器,將單詞,次數對按照次數從小到大排序
//將所有出現次數小於2的單詞集合拿到
val rareTokens = tokenCounts.filter{ case (k, v) => v < 2 }.map{ case (k, v) => k }.collect().toSet
//過濾掉所有出現次數小於2的單詞
val tokenCountsFilteredAll = tokenCountsFilteredSize.filter{ case (k, v) => !rareTokens.contains(k) }
println(tokenCountsFilteredAll.top(20)(oreringAsc).mkString(","))
println(tokenCountsFilteredAll.count)//打印不同的單詞

首先執行基本的分詞,然後把不是單詞的字符過濾掉,我們可以使用正則表達式切分原始文檔來移除這些非單詞字符;然後手動去掉停用詞,過濾掉停用詞,過濾掉僅含有一個字符的單詞,去掉整個文本庫中出現頻率很低的單詞,過濾掉所有出現次數小於2的單詞。
  爲什麼知道該這麼操作呢?每一個轉換操作之後,我們都可以打印一下當前數據的形式,查看需要對現在的RDD執行什麼過濾操作。我將程序設置爲本地模式運行後,打印出過濾過程的一系列結果。

6.1.3 訓練TF-IDF模型

val dim = math.pow(2, 18).toInt
val hashingTF = new HashingTF(dim)
//HashingTF的transform函數把每個輸入文檔(即詞項的序列)映射到一個MLlib的Vector對象。
val tf = hashingTF.transform(tokens)
tf.cache()//把數據保持在內存中加速之後的操作
val v = tf.first().asInstanceOf[SV]
println("tf的第一個向量大小:" + v.size)//262144
println("非0項個數:" + v.values.size)//706
println("前10列的下標:" + v.values.take(10).toSeq)
//WrappedArray(1.0, 1.0, 1.0, 1.0, 2.0, 1.0, 1.0, 2.0, 1.0, 1.0)
println("前10列的詞頻:" + v.indices.take(10).toSeq)
//WrappedArray(313, 713, 871, 1202, 1203, 1209, 1795, 1862, 3115, 3166)

HashingTF使用特徵哈希把每個輸入文本的詞映射爲一個詞頻向量的下標,每個詞頻向量的下標是一個哈希值(依次映射到特徵向量的某個維度),詞項的值是本身的TF-IDF權重。可以看到每一個詞頻的稀疏向量的維度是262144(2^18).然而向量中的非0項只有706個。
  下面分析TF-IDF權重,並計算整個文檔的TF-IDF最小和最大權值。

val idf = new IDF().fit(tf)
//transform將詞頻向量轉爲TF_IDF向量
val tfidf = idf.transform(tf)
val v2 = tfidf.first().asInstanceOf[SV]
val minMaxVals = tfidf.map{ v => val sv = v.asInstanceOf[SV]
  (sv.values.min, sv.values.max)
}
val globalMinMax = minMaxVals.reduce{ case ( (min1, max1), (min2, max2)) =>
  (math.min(min1, min2), math.max(max1, max2))
}

6.1.4 樸素貝葉斯算法

val newsgroupMap = news_groups.distinct().collect().zipWithIndex.toMap
val zipped = news_groups.zip(tfidf)
val train = zipped.map{ case (topic, vector) =>
  LabeledPoint(newsgroupMap(topic), vector)
}

從新聞組RDD開始,其中每個元素是一個話題,使用zipWithIndex,給每個類賦予一個數字下標.使用zip函數把話題和由TF-IDF向量組成的tiidf RDD組合,其中每個label是一個類下標,特徵就是IF-IDF向量。

//加載測試集數據
val testRDD = sc.wholeTextFiles(args(1))
val testLabes = testRDD.map{  case (file, text) =>
  val topic = file.split("/").takeRight(2).head
  newsgroupMap(topic) }//根據類別得到此類別的標號
val testTf = testRDD.map{ case (file, text) => hashingTF.transform(tokenize(text)) }
val testTfIdf = idf.transform(testTf)//將MLlib的Vector對象轉化爲tf-idf向量
val zippedTest = testLabes.zip(testTfIdf)//將類型和向量打包
//將類型和向量打包轉換爲LabeledPoint對象
val test = zippedTest.map{  case (topic, vector) => LabeledPoint(topic, vector) }
val predictionAndLabel = test.map(p => (model.predict(p.features), p.label))
//使用模型預測
val accuracy = 1.0 * predictionAndLabel.filter(x => x._1 == x._2).count() / test.count()
val metrics = new MulticlassMetrics(predictionAndLabels = predictionAndLabel)
//計算加權F指標,是一個綜合了準確率和召回率的指標(這裏類似於ROC曲線下的面積,當接近1時有較好的表現),並通過類之間加權平均整合
println("準確率=" + accuracy)

將每個文件的內容進行分詞處理,HashingTF的transform函數把每個輸入文檔(即詞項的序列)映射到一個MLlib的Vector對象。得到結果如下所示:

因數據集太少(訓練集200,測試集6),只是爲了節約時間而拋出結果,所以結果不理想。

6.2 Job運行分析

提交任務到集羣:

打開http://master:4040查看Job運行狀態如下。4040界面詳細給出了每一個Job的運行情況,正在運行的以及運行完成,這些運行在不同集羣的Job組成Spark任務。

打開http://master:8080查看spark的WEB集成界面,在這裏顯示了Spark任務的情況,正在運行的任務,已經完成的任務,以及worker的ID和資源狀態一目瞭然。

下面是Spark Job的狀態、存儲等界面,也是詳細給出了各部分的運行時狀態。

等到任務運行完畢後,4040端口隨着SparkContext的停止而停止,任務的狀態也由Runing轉化成爲了Completed。

任務最終完成的結果。

七、部分博客參考

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章