Spark:為大數(shù)據(jù)處理點(diǎn)亮一盞明燈
譯文Apache Spark項(xiàng)目于2009年誕生于伯克利大學(xué)的AMPLab實(shí)驗(yàn)室,當(dāng)初的目的在于將內(nèi)存內(nèi)分析機(jī)制引入大規(guī)模數(shù)據(jù)集當(dāng)中。在那個時候,Hadoop MapReduce的關(guān)注重點(diǎn)仍然放在那些本質(zhì)上無法迭代的大規(guī)模數(shù)據(jù)管道身上。想在2009年以MapReduce為基礎(chǔ)構(gòu)建起分析模型實(shí)在是件費(fèi)心費(fèi)力而又進(jìn)展緩慢的工作,因此AMPLab設(shè)計(jì)出Spark來幫助開發(fā)人員對大規(guī)模數(shù)據(jù)集執(zhí)行交互分析、從而運(yùn)行各類迭代工作負(fù)載——也就是對內(nèi)存中的同一套或者多套數(shù)據(jù)集進(jìn)行反復(fù)處理,其中最典型的就是機(jī)器學(xué)習(xí)算法。
Spark的意義并不在于取代Hadoop。正相反,它為那些高度迭代的工作負(fù)載提供了一套備用處理引擎。通過顯著降低面向磁盤的寫入強(qiáng)度,Spark任務(wù)通常能夠在運(yùn)行速度方面高出Hadoop MapReduce幾個數(shù)量級。作為“寄生”在Hadoop集群當(dāng)中的得力助手,Spark利用Hadoop數(shù)據(jù)層(HDFS、HBase等等)作為數(shù)據(jù)管道終端,從而實(shí)現(xiàn)原始數(shù)據(jù)讀取以及最終結(jié)果存儲。
編寫Spark應(yīng)用程序
作為由Scala語言編寫的項(xiàng)目,Spark能夠?yàn)閿?shù)據(jù)處理流程提供一套統(tǒng)一化抽象層,這使其成為開發(fā)數(shù)據(jù)應(yīng)用程序的絕佳環(huán)境。Spark在大多數(shù)情況下允許開發(fā)人員選擇Scala、Java以及Python語言用于應(yīng)用程序構(gòu)建,當(dāng)然對于那些最為前沿的層面、只有Scala能夠?qū)崿F(xiàn)大家的一切構(gòu)想。
Spark當(dāng)中的突出特性之一在于利用Scala或者Python控制臺進(jìn)行交互式工作。這意味著大家可以在嘗試代碼運(yùn)行時,立即查看到其實(shí)際執(zhí)行結(jié)果。這一特性非常適合調(diào)試工作——大家能夠在無需進(jìn)行編譯的前提下變更其中的數(shù)值并再次處理——以及數(shù)據(jù)探索——這是一套典型的處理流程,由大量檢查-顯示-更新要素所構(gòu)成。
Spark的核心數(shù)據(jù)結(jié)構(gòu)是一套彈性分布式數(shù)據(jù)(簡稱RDD)集。在Spark當(dāng)中,驅(qū)動程序被編寫為一系列RDD轉(zhuǎn)換機(jī)制,并附帶與之相關(guān)的操作環(huán)節(jié)。顧名思義,所謂轉(zhuǎn)換是指通過變更現(xiàn)有數(shù)據(jù)——例如根據(jù)某些特定指標(biāo)對數(shù)據(jù)進(jìn)行過濾——根據(jù)其創(chuàng)建出新的RDD。操作則隨RDD自身同步執(zhí)行。具體而言,操作內(nèi)容可以是計(jì)算某種數(shù)據(jù)類型的實(shí)例數(shù)量或者將RDD保存在單一文件當(dāng)中。
Spark的另一大優(yōu)勢在于允許使用者輕松將一套RDD共享給其它Spark項(xiàng)目。由于RDD的使用貫穿于整套Spark堆棧當(dāng)中,因此大家能夠隨意將SQL、機(jī)器學(xué)習(xí)、流以及圖形等元素?fù)诫s在同一個程序之內(nèi)。
熟悉各類其它函數(shù)型編程語言——例如LISP、Haskell或者F#——的開發(fā)人員會發(fā)現(xiàn),除了API之外、自己能夠非常輕松地掌握Spark編程方式。歸功于Scala語言的出色收集系統(tǒng),利用Spark Scala API編寫的應(yīng)用程序能夠以干凈而且簡潔的面貌呈現(xiàn)在開發(fā)者面前。在對Spark編程工作進(jìn)行調(diào)整時,我們主要需要考慮這套系統(tǒng)的分布式特性并了解何時需要對對象以及函數(shù)進(jìn)行排序。
擁有其它程序語言,例如Java,知識背景的程序員則往往沒辦法快速適應(yīng)Spark項(xiàng)目的函數(shù)編程范式。有鑒于此,企業(yè)可能會發(fā)現(xiàn)找到一位能夠切實(shí)上手Spark(從這個角度講,Hadoop也包含其中)的Scala與函數(shù)編程人員實(shí)在不是件容易的事。
由于Spark的RDD能夠?qū)崿F(xiàn)跨系統(tǒng)共享,因此大家能夠隨意將SQL、機(jī)器學(xué)習(xí)、流以及圖形等元素?fù)诫s在同一個程序之內(nèi)。
#p#
彈性分布式數(shù)據(jù)集
對于RDD的使用貫穿于整套堆棧當(dāng)中,而這也成為Spark如此強(qiáng)大的根基之一。無論是從概念層面還是實(shí)施層面,RDD都顯得非常簡單; RDD類當(dāng)中的大部分方法都在20行以內(nèi)。而從核心角度看,RDD屬于一套分布式記錄集合,由某種形式的持久性存儲作為依托并配備一系列轉(zhuǎn)換機(jī)制。
RDD是不可變更的。我們無法對RDD進(jìn)行修改,但卻能夠輕松利用不同數(shù)值創(chuàng)建新的RDD。這種不可變性算得上是分布式數(shù)據(jù)集的一大重要特性; 這意味著我們用不著擔(dān)心其它線程或者進(jìn)程在我們不知不覺中對RDD數(shù)值作出了變更——而這正是多線程編程領(lǐng)域的一個老大難問題。這同時意味著我們能夠?qū)DD分發(fā)到整個集群當(dāng)中加以執(zhí)行,而不必?fù)?dān)心該如何在各節(jié)點(diǎn)之間對RDD內(nèi)容變更進(jìn)行同步。
RDD不可變性在Spark應(yīng)用程序的容錯機(jī)制當(dāng)中同樣扮演著重要角色。由于每個RDD都保留有計(jì)算至當(dāng)前數(shù)值的全部歷史記錄、而且其它進(jìn)程無法對其作出變更,因此在某個節(jié)點(diǎn)丟失時對RDD進(jìn)行重新計(jì)算就變得非常輕松——只需要返回原本的持久性數(shù)據(jù)分區(qū),再根據(jù)不同節(jié)點(diǎn)重新推導(dǎo)計(jì)算即可。(Hadoop當(dāng)中的大多數(shù)分區(qū)都具備跨節(jié)點(diǎn)持久性。)
RDD能夠通過多種數(shù)據(jù)分區(qū)類型加以構(gòu)成。在大多數(shù)情況下,RDD數(shù)據(jù)來自HDFS,也就是所謂“分區(qū)”的書面含義。不過RDD也可以由來自其它持久性存儲機(jī)制的數(shù)據(jù)所構(gòu)成,其中包括HBase、Cassandra、SQL數(shù)據(jù)庫(通過JDBC)、Hive ORC(即經(jīng)過優(yōu)化的行列)文件乃至其它能夠與Hadoop InputFormat API相對接的存儲系統(tǒng)。無論RDD的實(shí)際來源如何,其運(yùn)作機(jī)制都是完全相同的。
Spark轉(zhuǎn)換機(jī)制的最后一項(xiàng)備注是:此類流程非常懶惰,也就是說直到某項(xiàng)操作要求將一條結(jié)果返回至驅(qū)動程序,否則此前整個過程不涉及任何計(jì)算環(huán)節(jié)。這樣的特性在與Scala shell進(jìn)行交互時顯得意義重大。這是因?yàn)镽DD在逐步轉(zhuǎn)換的過程當(dāng)中不會帶來任何資源成本——直到需要執(zhí)行實(shí)際操作。到這個時候,所有數(shù)值才需要進(jìn)行計(jì)算,并將結(jié)果返回給用戶。除此之外,由于RDD能夠利用內(nèi)存充當(dāng)緩存機(jī)制,因此頻繁使用計(jì)算結(jié)果也不會造成反復(fù)計(jì)算或者由此引發(fā)的資源消耗。
Spark轉(zhuǎn)換機(jī)制非常懶惰,也就是說直到某項(xiàng)操作要求將一條結(jié)果返回至用戶處,否則此前整個過程不涉及任何計(jì)算環(huán)節(jié)。
執(zhí)行Spark應(yīng)用程序
為了將一項(xiàng)Spark任務(wù)提交至集群,開發(fā)人員需要執(zhí)行驅(qū)動程序并將其與集群管理器(也被稱為cluster master)相對接。集群管理器會為該驅(qū)動程序提供一套持久性接口,這樣同一款應(yīng)用程序即可在任何受支持集群類型之上實(shí)現(xiàn)正常運(yùn)行。
Spark項(xiàng)目目前支持專用Spark(獨(dú)立)、Mesos以及YARN集群。運(yùn)行在集群當(dāng)中的每個驅(qū)動程序以各自獨(dú)立的方式負(fù)責(zé)資源分配與任務(wù)調(diào)度工作。盡管以隔離方式進(jìn)行應(yīng)用程序交付,但這種架構(gòu)往往令集群很難高效實(shí)現(xiàn)內(nèi)存管理——也就是對于Spark而言最為寶貴的資源類型。多個高內(nèi)存消耗任務(wù)在同時提交時,往往會瞬間將內(nèi)存吞噬殆盡。盡管獨(dú)立集群管理器能夠?qū)崿F(xiàn)簡單的資源調(diào)度,但卻只能做到跨應(yīng)用程序FIFO(即先入先出)這種簡單的程度,而且無法實(shí)現(xiàn)資源識別。
總體而言,Spark開發(fā)人員必須更傾向于裸機(jī)層面思維,而非利用像Hive或者Pig這樣的高級應(yīng)用程序?qū)?shù)據(jù)分析作為思考出發(fā)點(diǎn)。舉例來說,由于驅(qū)動程序充當(dāng)著調(diào)度任務(wù)的執(zhí)行者,它需要最大程度與這些工作節(jié)點(diǎn)保持緊密距離、從而避免網(wǎng)絡(luò)延遲對執(zhí)行效果造成的負(fù)面影響。
驅(qū)動程序與集群管理器高可用性這兩者都很重要。如果驅(qū)動程序停止工作,任務(wù)也將立即中止。而如果集群管理器出現(xiàn)故障,新的任務(wù)則無法被提交至其中,不過現(xiàn)有任務(wù)仍將繼續(xù)保持執(zhí)行。在Spark 1.1版本當(dāng)中,主高可用性機(jī)制由獨(dú)立Spark集群通過ZooKeeper實(shí)現(xiàn),但驅(qū)動程序卻缺乏與高可用性相關(guān)的保障措施。
將一套Spark集群當(dāng)中的性能最大程度壓榨出來更像是一種魔法甚至妖術(shù),因?yàn)槠渲行枰婕皩︱?qū)動程序、執(zhí)行器、內(nèi)存以及內(nèi)核的自由組合及反復(fù)實(shí)驗(yàn),同時根據(jù)特定集群管理器對CPU及內(nèi)存使用率加以優(yōu)化。目前關(guān)于此類運(yùn)維任務(wù)的指導(dǎo)性文檔還非常稀缺,而且大家可能需要與同事進(jìn)行頻繁溝通并深入閱讀源代碼來實(shí)現(xiàn)這一目標(biāo)。
Spark應(yīng)用程序架構(gòu)。Spark目前可以被部署在Spark獨(dú)立、YARN或者M(jìn)esos集群當(dāng)中。請注意,運(yùn)行在集群當(dāng)中的每一個驅(qū)動程序都會以彼此獨(dú)立的方式進(jìn)行資源分配與任務(wù)調(diào)度。
#p#
監(jiān)控與運(yùn)維
每一款驅(qū)動程序都擁有自己的一套Web UI,通常為端口4040,其中顯示所有實(shí)用性信息——包括當(dāng)前運(yùn)行任務(wù)、調(diào)度程度、執(zhí)行器、階段、內(nèi)存與存儲使用率、RDD等等。這套UI主要充當(dāng)信息交付工具,而非針對Spark應(yīng)用程序或者集群的管理方案。當(dāng)然,這也是調(diào)試以及性能調(diào)整之前的基礎(chǔ)性工具——我們需要了解的、與應(yīng)用程序運(yùn)行密切相關(guān)的幾乎所有信息都能在這里找到。
雖然算是個不錯的開始,但這套Web UI在細(xì)節(jié)方面仍然顯得比較粗糙。舉例來說,要想查看任務(wù)歷史記錄、我們需要導(dǎo)航到一臺獨(dú)立的歷史服務(wù)器,除非大家所使用的是處于獨(dú)立模式下的集群管理器。不過最大的缺點(diǎn)在于,這套Web UI缺少對于運(yùn)維信息的管理與控制能力。啟動與中止節(jié)點(diǎn)運(yùn)行、查看節(jié)點(diǎn)運(yùn)行狀況以及其它一些集群層面的統(tǒng)計(jì)信息在這里一概無法實(shí)現(xiàn)??傮w而言,Spark集群的運(yùn)行仍然停留在命令行操作時代。
Spark的Web UI提供了與當(dāng)前運(yùn)行任務(wù)相關(guān)的豐富信息,但所有指向集群的管理操作則需要完全通過命令行來實(shí)現(xiàn)。
Spark對決Tez
事實(shí)上,Spark與Tez都采用有向無環(huán)圖(簡稱DAG)執(zhí)行方式,這兩套框架之間的關(guān)系就如蘋果與桔子般不分軒輊,而最大的差別在于其受眾以及設(shè)計(jì)思路。即使如此,我發(fā)現(xiàn)很多IT部門仍然沒能分清這兩款框架間的差異所在。
Tez是一款應(yīng)用程序框架,設(shè)計(jì)目的在于幫助開發(fā)人員編寫出更為高效的多級MapReduce任務(wù)。舉例來說,在Hive 0.13版本當(dāng)中,HQL(即Hive查詢語言)由語言編譯器負(fù)責(zé)解析并作為Tez DAG進(jìn)行渲染,即將數(shù)據(jù)流映射至處理節(jié)點(diǎn)處以實(shí)現(xiàn)高效執(zhí)行。Tez DAG由應(yīng)用程序以邊緣到邊緣、頂點(diǎn)到頂點(diǎn)的方式進(jìn)行構(gòu)建。用戶則完全不需要了解Tez DAG的構(gòu)建方式,甚至感受不到它的存在。
Spark與Tez之間的真正差異在于二者實(shí)現(xiàn)方式的不同。在Spark應(yīng)用程序當(dāng)中,同樣的工作節(jié)點(diǎn)通過跨迭代實(shí)現(xiàn)重新使用,這就消除了JVM啟動所帶來的資源成本。Spark工作節(jié)點(diǎn)還能夠?qū)ψ兞窟M(jìn)行緩存處理,從而消除對數(shù)值進(jìn)行跨迭代重新讀取與重新計(jì)算的需要。正是借鑒著以上幾大特征,Spark才能夠在迭代編程當(dāng)中如魚得水、充分發(fā)力。而由此帶來的缺點(diǎn)是,Spark應(yīng)用程序會消耗大量集群資源、特別是在緩存過期的情況下。我們很難在集群運(yùn)行著Spark的時候?qū)Y源進(jìn)行優(yōu)化。
盡管支持多級任務(wù)執(zhí)行機(jī)制,Tez仍然不具備任何形式的緩存處理能力。雖然變量能夠在一定程度上得到緩存處理,從而保證規(guī)劃器在可能的情況下保證調(diào)度任務(wù)從同節(jié)點(diǎn)中的上一級處獲取必要數(shù)值,但Tez當(dāng)中仍然未能提供任何一種經(jīng)過妥善規(guī)劃的跨迭代或者變量廣播機(jī)制。除此之外,Tez任務(wù)還需要反復(fù)啟動JVM,而這會帶來額外的資源開銷。因此,Tez更適合處理那些規(guī)模極為龐大的數(shù)據(jù)集,在這種情況下啟動時間只占整體任務(wù)處理周期的一小部分、幾乎可以忽略不計(jì)。
在大多數(shù)情況下,Hadoop社區(qū)對此都擁有很好的移花接木式解決方案,而且其中最出色的部分機(jī)制已經(jīng)能夠作用于其它項(xiàng)目。舉例來說,YARN-1197將允許Spark執(zhí)行器以動態(tài)方式進(jìn)行規(guī)模調(diào)整,這樣它們就能夠在合適的條件下將資源返還給集群。與之相似,Stinger.next將為Hive等傳統(tǒng)Hadoop應(yīng)用程序帶來由跨查詢緩存提供的巨大優(yōu)勢。
#p#
一整套集成化分析生態(tài)系統(tǒng)
Spark所采用的底層RDD抽象機(jī)制構(gòu)建起整個Spark生態(tài)系統(tǒng)的核心數(shù)據(jù)結(jié)構(gòu)。在機(jī)器學(xué)習(xí)(MLlib)、數(shù)據(jù)查詢(Spark SQL)、圖形分析(GraphX)以及流運(yùn)行(Spark Streaming)等模塊的共同支持下,開發(fā)人員能夠以無縫化方式使用來自任意單一應(yīng)用程序的庫。
舉例來說,開發(fā)人員可以根據(jù)HDFS當(dāng)中的某個文件創(chuàng)建一個RDD,將該RDD轉(zhuǎn)換為SchemaRDD、利用Spark SQL對其進(jìn)行查詢,而后將結(jié)果交付給MLlib庫。最后,結(jié)果RDD可以被插入到Spark Streaming當(dāng)中,從而充當(dāng)消息交付機(jī)制的預(yù)測性模型。如果要在不使用Spark項(xiàng)目的情況下實(shí)現(xiàn)以上目標(biāo),大家需要使用多套庫、對數(shù)據(jù)結(jié)構(gòu)進(jìn)行封包與轉(zhuǎn)換,并投入大量時間與精力對其加以部署??傮w而言,將三到上個在最初設(shè)計(jì)當(dāng)中并未考慮過協(xié)作場景的應(yīng)用程序整合在一起絕對不是正常人的脆弱心靈所能承受的沉重負(fù)擔(dān)。
堆棧集成機(jī)制讓Spark在交互式數(shù)據(jù)探索與同一數(shù)據(jù)集內(nèi)的重復(fù)性函數(shù)應(yīng)用領(lǐng)域擁有著不可替代的重要價值。機(jī)器學(xué)習(xí)正是Spark項(xiàng)目大展拳腳的理想場景,而在不同生態(tài)系統(tǒng)之間以透明方式實(shí)現(xiàn)RDD共享的特性更是大大簡化了現(xiàn)代數(shù)據(jù)分析應(yīng)用程序的編寫與部署流程。
然而,這些優(yōu)勢的實(shí)現(xiàn)并非全無代價。在1.x系列版本當(dāng)中,Spark系統(tǒng)在諸多細(xì)節(jié)上還顯得相當(dāng)粗糙。具體而言,缺乏安全性(Spark無法運(yùn)行在Kerberised集群當(dāng)中,也不具備任務(wù)控制功能)、缺乏企業(yè)級運(yùn)維功能、說明文檔質(zhì)量糟糕,而且嚴(yán)苛的稀缺性技能要求意味著目前Spark仍然只適合早期實(shí)驗(yàn)性部署或者那些有能力滿足大規(guī)模機(jī)器學(xué)習(xí)模型必需條件且愿意為其構(gòu)建支付任何投入的大型企業(yè)。
到底應(yīng)不應(yīng)該部署Spark算是一個“仁者見仁,智者見智”的開放性議題。對于一部分組織而言,Spark這套速度極快的內(nèi)存內(nèi)分析引擎能夠帶來諸多優(yōu)勢,從而輕松為其帶來理想的投資回報(bào)表現(xiàn)。但對于另一些組織來說,那些雖然速度相對較慢但卻更為成熟的工具仍然是其不二之選,畢竟它們擁有適合企業(yè)需求的完善功能而且更容易找到有能力對其進(jìn)行管理與控制的技術(shù)人員。
無論如何,我們都要承認(rèn)Spark的積極意義。Spark項(xiàng)目將一系列創(chuàng)新型思維帶入了大數(shù)據(jù)處理市場,并且表現(xiàn)出極為強(qiáng)勁的發(fā)展勢頭。隨著其逐步成熟,可以肯定Spark將最終成為一支不容忽視的巨大力量。
Apache Spark 1.1.0 / Apache軟件基金會
總結(jié)性描述
作為一套配備精妙A(yù)PI以實(shí)現(xiàn)數(shù)據(jù)處理應(yīng)用程序創(chuàng)建目標(biāo)的高速內(nèi)存內(nèi)分析引擎,Spark在迭代工作負(fù)載這類需要重復(fù)訪問同一套或者多套數(shù)據(jù)集的領(lǐng)域——例如機(jī)器學(xué)習(xí)——表現(xiàn)出無可匹敵的競爭優(yōu)勢。
基于Apache 2.0許可的開源項(xiàng)目
優(yōu)勢
• 精妙且具備一致性保障的API幫助開發(fā)人員順利構(gòu)建起數(shù)據(jù)處理應(yīng)用程序
• 支持Hadoop集群上的交互式查詢與大規(guī)模數(shù)據(jù)集分析任務(wù)
• 在運(yùn)行迭代工作負(fù)載時擁有高出Hadoop幾個數(shù)量級的速度表現(xiàn)
• 能夠以獨(dú)立配置、YARN、Hadoop MapReduce或者M(jìn)esos等方式部署在Hadoop集群當(dāng)中
• RDD(即彈性分布式數(shù)據(jù)集)能夠在不同Spark項(xiàng)目之間順利共享,從而允許用戶將SQL、機(jī)器學(xué)習(xí)、流運(yùn)行以及圖形等元素?fù)诫s在同一程序當(dāng)中
• Web UI提供與Spark集群及當(dāng)前運(yùn)行任務(wù)相關(guān)的各類實(shí)用性信息
缺點(diǎn)
• 安全性不理想
• 說明文檔質(zhì)量糟糕
• 不具備集群資源管理能力
• 學(xué)習(xí)曲線不夠友好