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