翻譯:Hadoop權(quán)威指南之Spark-1

本文翻譯自O(shè)'Reilly出版Tom White所著《Hadoop: The Definitive Guide》第4版第19章,向作者致敬。該書英文第4版已于2015年4月出版,至今已近15個月,而市面上中文第3版還在大行其道。Spark一章是第4版新增的內(nèi)容,筆者在學(xué)習(xí)過程中順便翻譯記錄,由于筆者也在學(xué)習(xí),并無實(shí)戰(zhàn)經(jīng)驗,難免翻譯不妥或出錯,歡迎方家來信斧正。翻譯純屬興趣,不做商業(yè)用途。秦銘,Email地址qinm08@gmail.com

本文原始地址


Apache Spark 是一個大規(guī)模數(shù)據(jù)處理的集群計算框架。和本書中討論的大多數(shù)其他處理框架不同,Spark不使用MapReduce作為執(zhí)行引擎,作為替代,Spark使用自己的分布式運(yùn)行環(huán)境(distributed runtime)來執(zhí)行集群上的工作。然而,Spark與MapReduce在API和runtime方面有許多相似,本章中我們將會看到。Spark和Hadoop緊密集成:它可以運(yùn)行在YARN上,處理Hadoop的文件格式,后端存儲采用HDFS。

Spark最著名的是它擁有把大量的工作數(shù)據(jù)集保持在內(nèi)存中的能力。這種能力使得Spark勝過對應(yīng)的MapReduce工作流(某些情況下差別顯著),在MapReduce中數(shù)據(jù)集總是要從磁盤加載。兩種類型的應(yīng)用從Spark這種處理模型中受益巨大:1)迭代算法,一個函數(shù)在某數(shù)據(jù)集上反復(fù)執(zhí)行直到滿足退出條件。2)交互式分析,用戶在某數(shù)據(jù)集上執(zhí)行一系列的特定查詢。

即使你不需要內(nèi)存緩存,Spark依然有充滿魅力的理由:它的DAG引擎和用戶體驗。與MapReduce不同,Spark的DAG引擎能夠處理任意的多個操作組成的管道(pipelines of operators)并翻譯為單個Job。

Spark的用戶體驗也是首屈一指的(second to none),它有豐富的API用來執(zhí)行很多常見的數(shù)據(jù)處理任務(wù),比如join。行文之時,Spark提供三種語言的API:Scala,Java和Python。本章中的大多數(shù)例子將采用Scala API,但翻譯為別的語言也是容易的。Spark還帶有一個基于Scala或Python的REPL(read-eval-print loop)環(huán)境,可以快速簡便的查看數(shù)據(jù)集。

Spark是個構(gòu)建分析工具的好平臺,為達(dá)此目的,Apache Spark項目包含了眾多的模塊:機(jī)器學(xué)習(xí)(MLlib),圖形處理(GraphX),流式處理(Spark Streaming),還有SQL(Spark SQL)。本章內(nèi)容不涉及這些模塊,感興趣的讀者可以訪問 Apache Spark 網(wǎng)站

Installing Spark

下載頁面 下載Spark二進(jìn)制分發(fā)包的穩(wěn)定版本(選擇和你正在使用的Hadoop版本相匹配的)。在合適的地方解壓這個tar包。

% tar xzf spark-x.y.z-bin-distro.tgz

把Spark加入到PATH環(huán)境變量中

% export SPARK_HOME=~/sw/spark-x.y.z-bin-distro
% export PATH=$PATH:$SPARK_HOME/bin

我們現(xiàn)在可以運(yùn)行Spark的例子了。

An Example

為了介紹Spark,我們使用spark-shell來運(yùn)行一個交互式會話,這是帶有Spark附加組件的Scala REPL,用下面的命令啟動shell:

% spark-shell
Spark context available as sc.
scala>

從控制臺的輸出,我們可以看到shell創(chuàng)建了一個Scala變量,sc,用來存儲SparkContext實(shí)例。這是Spark的入口,我們可以這樣加載一個文本文件:

scala> val lines = sc.textFile("input/ncdc/micro-tab/sample.txt")
lines: org.apache.spark.rdd.RDD[String] = MappedRDD[1] at textFile at <console>:12

lines變量是對一個彈性數(shù)據(jù)集(RDD)的引用,RDD是Spark的核心抽象:分區(qū)在集群中多臺機(jī)器上的只讀的對象集合。在典型的Spark程序中,一個或多個RDD被加載進(jìn)來作為輸入,經(jīng)過一系列的轉(zhuǎn)變(transformation),成為一組目標(biāo)RDD,可以對其執(zhí)行action(比如計算結(jié)果或者寫入持久化存儲) 。“彈性數(shù)據(jù)集”中的“彈性”是指,Spark會通過從源RDD中重新計算的方式,來自動重建一個丟失的分區(qū)。

加載RDD和執(zhí)行transformation不會觸發(fā)數(shù)據(jù)處理,僅僅是創(chuàng)建一個執(zhí)行計算的計劃。當(dāng)action(比如 foreach())執(zhí)行的時候,才會觸發(fā)計算。

我們要做的第一個transformation,是把lines拆分為fields:

scala> val records = lines.map(_.split("\t"))
records: org.apache.spark.rdd.RDD[Array[String]] = MappedRDD[2] at map at <console>:14

這里使用了RDD的map()方法,對RDD中的每一個元素,執(zhí)行一個函數(shù)。本例中,我們把每一行(字符串String)拆分為 Scala 的字符串?dāng)?shù)組(Array of Strings)。

接下來,我們使用過濾器(filter)來去掉可能存在的壞記錄:

scala> val filtered = records.filter(rec => (rec(1) != "9999" && rec(2).matches("[01459]")))
filtered: org.apache.spark.rdd.RDD[Array[String]] = FilteredRDD[3] at filter at <console>:16

RDD的filter方法接收一個返回布爾值的函數(shù)作為參數(shù)。這個函數(shù)過濾掉那些溫度缺失(由9999表示)或者質(zhì)量不好的記錄。

為了找到每一年的最高氣溫,我們需要在year字段上執(zhí)行分組操作,這樣才能處理每一年的所有溫度值。Spark提供reduceByKey()方法來做這件事情,但它需要一個鍵值對RDD,因此我們需要通過另一個map來把現(xiàn)有的RDD轉(zhuǎn)變?yōu)檎_的形式:

scala> val tuples = filtered.map(rec => (rec(0).toInt, rec(1).toInt))
tuples: org.apache.spark.rdd.RDD[(Int, Int)] = MappedRDD[4] at map at <console>:18

現(xiàn)在可以執(zhí)行聚合了。reduceByKey()方法的參數(shù)是一個函數(shù),這個函數(shù)接受兩個數(shù)值并聯(lián)合為一個單獨(dú)的數(shù)值。這里我們使用Java的Math.max函數(shù):

scala> val maxTemps = tuples.reduceByKey((a, b) => Math.max(a, b))
maxTemps: org.apache.spark.rdd.RDD[(Int, Int)] = MapPartitionsRDD[7] at reduceByKey at <console>:21

現(xiàn)在可以展示maxTemps的內(nèi)容了,調(diào)用foreach()方法并傳入println(),把每個元素打印到控制臺:

scala> maxTemps.foreach(println(_))
(1950,22)
(1949,111)

這個foreach()方法,與標(biāo)準(zhǔn)Scala集合(比如List)中的等價物相同,對RDD中的每個元素應(yīng)用一個函數(shù)(此函數(shù)具有副作用)。正是這個操作,促使Spark運(yùn)行一個job來計算RDD中的數(shù)據(jù),使之能夠跑步通過println()方法:-)

或者,也可以把RDD保存到文件系統(tǒng):

scala> maxTemps.saveAsTextFile("output")

這樣會創(chuàng)建一個output目錄,包含分區(qū)文件:

% cat output/part-*
(1950,22)
(1949,111)

這個saveAsTextFile()方法也會觸發(fā)一個Spark job。主要的區(qū)別是沒有返回值,而是把RDD的計算結(jié)果及其分區(qū)文件寫入output目錄中。

Spark Applications, Jobs, Stages, Tasks

示例中我們看到,和MapReduce一樣,Spark也有job的概念。然而,Spark的job比MapReduce的job更通用,因為它是由任意的stage的有向無環(huán)圖(DAG)組成。每個stage大致等同于MapReduce中的map或者reduce階段。

Stages被Spark 運(yùn)行時拆分為tasks,并行地運(yùn)行在RDD的分區(qū)之上,就像MapReduce的task一樣。

一個Job總是運(yùn)行于一個application的上下文中,由SparkContext實(shí)例表示,application的作用是分組RDD和共享變量。一個application可以運(yùn)行多個job,串行或者并行,并且提供一種機(jī)制,使得一個job可以訪問同一application中的前一個job緩存的RDD。一個交互式的Spark會話,比如spark-shell會話,就是一個application的實(shí)例。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容