spark核心概念
Application | 基於Spark的用戶程序。由羣集上的 a driver program and executors 組成。=1 driver +多個executors |
Application jar | 一個包含用戶的Spark應用程序的jar。在某些情況下,用戶將希望創建一個包含其應用程序及其依賴項的“超級jar”。用戶的jar絕不能包含Hadoop或Spark庫,但是這些庫將在運行時添加。 |
Driver program | 該進程運行應用程序的main()函數並創建SparkContext |
Cluster manager |
用於獲取羣集上資源的外部服務(例如,standalone manager, Mesos, YARN) 像啓動時設置的 spark-submit --master local[2]/spark://xxx:7077/yarn |
Deploy mode | 部署模式,決定了你的driver跑在哪裏。–deploy-mode “client” (本地)or “cluster”(集羣) |
Worker node |
可以在集羣中運行應用程序代碼的任何節點 (一個運行我們應用程序代碼的節點 ,對於YARN來說就是nodemanager) |
Executor |
爲worker node上的應用程序啓動的進程,該進程運行任務並將數據跨任務存儲在內存或磁盤存儲中。每個Application都有自己的Executor(執行者)。
|
Task | (任務)一種工作單元,將發送給Executor上執行 |
Job | (作業)這是一個並行的計算,一個計算裏面包含了多個task,只要遇到一個action就是一個Job。 |
Stage |
每個job都被分爲一些smaller sets of tasks,這些任務集稱爲相互依賴的Stage(階段)(類似於MapReduce中的map和reduce階段);您會在driver的日誌中看到該術語。 一個stage的邊界往往是從某個地方取數據開始,到shuffle的結束 |
spark運行架構及注意事項
Spark application是運行在集羣上的一組獨立的進程,通過主程序(叫做driver program)裏的sparkcontext對象協調。
具體來說,爲了在集羣上運行,SparkContext可以連接到集中Cluster manager (spark自己獨立的集羣管理器,或者是Mesos,或者是YARN),CM去申請資源,一旦申請並連接成功,spark會在集羣上面executor,這些executor進程能夠進行計算並存儲我們的數據。然後,它(Driver programm)將你的應用程序的代碼傳到executor上面去。最終,SparkContext將你的tasks發送到executors上面去執行。
關於這種架構有幾點有用的注意事項:
1、每一個application有它自己的executor進程,獨立於其他application,這些進程存在於整個作業的生命週期並以多線程的形式運行我們的tasks(一個executor能夠運行多個task)。這樣可以在調度方(driver)(每個驅動程序調度自己的任務)和執行方(executor)(在不同JVM中運行的不同application中的tasks)之間隔離應用程序。然而,這意味着不同application之間無法分享數據,除非你將數據寫在一個外部存儲系統(alluxio框架,一個分佈式內存的數據框架)
2、Spark與底層集羣管理器(CM)無關。只要它可以獲取執行executor進程,並且這些進程相互通信,即使在支持其他應用程序的集羣管理器(例如Mesos / YARN)上運行它也相對容易。
3、drvier program必須在其生命週期內監聽並接受來自其executor的傳入連接。因此,驅動程序必須是來自工作節點的網絡可尋址的。
4、因爲driver在集羣上調度任務,所以它應該靠近work節點運行,最好是在同一局域網上運行。如果您想遠程向羣集發送請求,最好向driver打開RPC並讓它從附近提交操作,而不是遠離工作節點運行驅動程序。
spark和hadoop重要概念區分
hadoop:
1)一個mr程序=一個job
2)一個job=1/n個task(map/reduce)
3)一個task對應於一個進程
4)task運行時開啓進程,task執行完畢後摧毀進程,對於多個task來說,開銷是比較大的(即使你能通JVM共享)
spark:
1)application=driver(main方法中創建sparkcontext)+executors
2)一個job=一個action
3)一個job=1/n個stage
4)一個stage=1/n個task
5)一個task對應一個線程,多個task可以以並行的方式運行在一個executor進程中
spark cache詳解
cache的作用:如果一個RDD在後續的計算中可能會被使用到,那麼建議cache(加入緩存之後就去讀緩存,不去讀磁盤了)
rdd.cache():StorageLevel-----》BlockManager
cache和transformation一樣是lazy的,只有遇到action纔會執行
rdd.py
def cache(self): """ Persist this RDD with the default storage level (C{MEMORY_ONLY}). """ self.is_cached = True self.persist(StorageLevel.MEMORY_ONLY) return self def persist(self, storageLevel=StorageLevel.MEMORY_ONLY): """ Set this RDD's storage level to persist its values across operations after the first time it is computed. This can only be used to assign a new storage level if the RDD does not have a storage level set yet. If no storage level is specified defaults to (C{MEMORY_ONLY}). >>> rdd = sc.parallelize(["b", "a", "c"]) >>> rdd.persist().is_cached True """ self.is_cached = True javaStorageLevel = self.ctx._getJavaStorageLevel(storageLevel) self._jrdd.persist(javaStorageLevel) return selfdef unpersist(self): """ Mark the RDD as non-persistent, and remove all blocks for it from memory and disk. """ self.is_cached = False self._jrdd.unpersist() return self
cache底層調用的是persist方法,傳入的方法是StorageLevel.MEMORY_ONLY(只內存),但其實persist默認調用的也是StorageLevel.MEMORY_ONLY,所以cache()=persist()
在Python中,存儲的對象將始終使用Pickle庫進行序列化,因此,是否選擇序列化級別都無關緊要。Python中的可用存儲級別包括MEMORY_ONLY
,MEMORY_ONLY_2
, MEMORY_AND_DISK
,MEMORY_AND_DISK_2
,DISK_ONLY
,和DISK_ONLY_2
。
選擇哪個存儲級別?
Spark的存儲級別旨在在內存使用率和CPU效率之間提供不同的權衡。我們建議通過以下過程選擇一個:
-
如果您的RDD與默認的存儲級別(
MEMORY_ONLY
)相稱,請保持這種狀態。這是CPU效率最高的選項,允許RDD上的操作儘可能快地運行。 -
如果不是,請嘗試使用
MEMORY_ONLY_SER
並選擇一個快速的序列化庫,以使對象的空間效率更高,但仍可以快速訪問。(Java和Scala) -
除非用於計算數據集的函數很昂貴,否則它們會過濾到磁盤上,否則它們會過濾大量數據。否則,重新計算分區可能與從磁盤讀取分區一樣快。(儘量不要選擇磁盤)
-
如果要快速容錯,請使用複製的存儲級別(例如,如果使用Spark來處理來自Web應用程序的請求)。所有存儲級別都通過重新計算丟失的數據來提供完全的容錯能力,但是複製的存儲級別使您可以繼續在RDD上運行任務,而不必等待重新計算丟失的分區。
Spark自動監視每個節點上的緩存使用情況,並以最近最少使用(LRU)的方式丟棄舊的數據分區。如果要手動刪除RDD而不是等待它脫離緩存,請使用該RDD.unpersist()
方法。(unpersist()是立即執行的)
spark lineage詳解
RDD數據集通過所謂的血統關係(Lineage)記住了它是如何從其它RDD中演變過來的。相比其它系統的細顆粒度的內存數據更新級別的備份或者LOG機制,RDD的Lineage記錄的是粗顆粒度的特定數據轉換(Transformation)操作(filter, map, join etc.)行爲。當這個RDD的部分分區數據丟失時,它可以通過Lineage獲取足夠的信息來重新運算和恢復丟失的數據分區。
spark dependency詳解
窄依賴:一個父RDD的partition最多會被子RDD的partition使用一次(pipline-able)
寬依賴:一個父RDD的partition會被子RDD的partition使用多次,有shuffle
寬依賴對於窄依賴容錯機制,數據重算的時間會長一些
spark優化
監聽,事後查看
$SPARK_HOME/
conf/spark-env.shexport SPARK_HISTORY_OPTS=-Dspark.history.fs.logDirectory=hdfs://pyspark-1.bigload.com:8020/directory
$SPARK_HOME/conf/spark-defaults.conf
spark.eventLog.enabled true
spark.eventLog.dir hdfs://pyspark-1.bigload.com:8020/directory
./sbin/start-history-server.sh
http://<server-url>:18080
默認情況下,這將創建一個Web界面,列出未完成和已完成的應用程序和嘗試。
序列化
序列化的作用:將對象序列化爲慢速格式或佔用大量字節的格式將大大減慢計算速度。
序列化的選擇:
Spark旨在在便利性(允許您在操作中使用任何Java類型)和性能之間取得平衡。它提供了兩個序列化庫:
- Java序列化:默認情況下,Spark使用Java的
ObjectOutputStream
框架對對象進行序列化,並且可以與您創建的任何實現的類一起使用java.io.Serializable
。您還可以通過擴展來更緊密地控制序列化的性能java.io.Externalizable
。Java序列化很靈活,但是通常很慢,並且導致許多類的序列化格式很大。 - Kryo序列化:Spark還可以使用Kryo庫(版本4)更快地序列化對象。與Java序列化(通常多達10倍)相比,Kryo顯着更快,更緊湊,但是Kryo不支持所有
Serializable
類型,並且要求您預先註冊要在程序中使用的類,以實現最佳性能。最後,如果您不註冊自定義類,Kryo仍然可以工作,但是必須將完整的類名與每個對象一起存儲,這很浪費。
如果對象很大,則可能還需要增加spark.kryoserializer.buffer
config。該值必須足夠大以容納要序列化的最大對象。 -
要使用Kryo註冊您自己的自定義類,請使用
registerKryoClasses
方法。val conf = new SparkConf().setMaster(...).setAppName(...) conf.registerKryoClasses(Array(classOf[MyClass1], classOf[MyClass2])) val sc = new SparkContext(conf)
百度了一圈也沒找到pyspark kryo序列化的文章,目前的想法是用
pickle來做序列化
內存管理
Spark中的內存使用情況大體上屬於以下兩類之一:執行和存儲。執行內存是指用於洗牌,聯接,排序和聚合中的計算的內存,而存儲內存是指用於在集羣中緩存和傳播內部數據的內存。在Spark中,執行和存儲共享一個統一的區域(M)。當不使用執行內存時,存儲可以獲取所有可用內存,反之亦然。如果有必要,執行可能會驅逐存儲,但只有在總存儲內存使用率下降到某個閾值(R)以下時,該存儲纔會退出。換句話說,R
描述了一個子區域,在該子區域M
中永遠不會清除緩存的塊。由於實現的複雜性,存儲可能無法退出執行。
這種設計確保了幾種理想的性能。首先,不使用緩存的應用程序可以將整個空間用於執行,從而避免了不必要的磁盤溢出。其次,確實使用緩存的應用程序可以保留最小的存儲空間(R),以免其數據塊被逐出。最後,這種方法可爲各種工作負載提供合理的即用即用性能,而無需用戶瞭解如何在內部劃分內存。
儘管有兩種相關的配置,但典型用戶無需調整它們,因爲默認值適用於大多數工作負載:
spark.memory.fraction
表示的大小M
爲(JVM堆空間-300MB)的一部分(默認值爲0.6)。其餘的空間(40%)保留用於用戶數據結構,Spark中的內部元數據,並在記錄稀疏和異常大的情況下防止OOM錯誤。spark.memory.storageFraction
將的大小表示R
爲的一部分M
(默認爲0.5)。R
是M
其中的緩存塊不受執行影響而退出的存儲空間。
spark.memory.fraction
應該設置的值,以便在JVM的舊版本或“長期使用的”一代中舒適地適應此堆空間量。
廣播變量
廣播變量使程序員可以在每臺計算機上保留一個只讀變量,而不用隨任務一起發送它的副本。例如,可以使用它們以高效的方式爲每個節點提供大型輸入數據集的副本。Spark還嘗試使用有效的廣播算法分配廣播變量,以降低通信成本。
spark action是通過一組階段執行的,這些階段由分佈式“隨機”操作分隔。Spark自動廣播每個階段中任務所需的通用數據。在運行每個任務之前,以這種方式廣播的數據以序列化形式緩存並反序列化。這意味着僅當跨多個階段的任務需要相同數據或以反序列化形式緩存數據非常重要時,顯式創建廣播變量纔有用。
v
通過調用從變量創建廣播變量SparkContext.broadcast(v)
。broadcast變量是的包裝v
,可以通過調用value
方法訪問其值。下面的代碼顯示了這一點:
>>> broadcastVar = sc.broadcast([1, 2, 3])
<pyspark.broadcast.Broadcast object at 0x102789f10>
>>> broadcastVar.value
[1, 2, 3]
廣播大變量
使用中 可用的廣播功能SparkContext
可以極大地減少每個序列化任務的大小,以及在羣集上啓動作業的成本。如果您的任務使用驅動程序中的任何大對象(例如,靜態查找表),請考慮將其轉換爲廣播變量。Spark在主服務器上打印每個任務的序列化大小,因此您可以查看它來確定任務是否太大;通常,大約20 KB以上的任務可能值得優化。
數據本地性操作
數據局部性可能會對Spark作業的性能產生重大影響。如果數據和對其進行操作的代碼在一起,則計算速度往往會很快。但是,如果代碼和數據是分開的,那麼一個必須移到另一個。通常,從一個地方到另一個地方傳送序列化代碼要比塊數據更快,因爲代碼大小比數據小得多。Spark圍繞此數據本地性一般原則構建調度。
數據局部性是數據與處理它的代碼之間的接近程度。根據數據的當前位置,可分爲多個級別。從最遠到最遠的順序:
PROCESS_LOCAL
數據與正在運行的代碼位於同一JVM中。這是最好的位置NODE_LOCAL
數據在同一節點上。示例可能在同一節點上的HDFS中,或者在同一節點上的另一執行程序中。這比PROCESS_LOCAL
由於數據必須在進程之間傳輸而要慢一些NO_PREF
可以從任何地方快速訪問數據,並且不受位置限制RACK_LOCAL
數據在同一服務器機架上。數據位於同一機架上的其他服務器上,因此通常需要通過單個交換機通過網絡發送ANY
數據在網絡上的其他位置,而不是在同一機架中
Spark傾向於在最佳位置級別安排所有任務,但這並不總是可能的。在任何空閒執行器上沒有未處理數據的情況下,Spark會切換到較低的本地級別。有兩種選擇:a)等待忙碌的CPU釋放以在同一服務器上的數據上啓動任務,或b)立即在需要將數據移動到更遠的地方啓動新任務。
Spark通常要做的是稍等一下,以期釋放繁忙的CPU。一旦超時到期,它將開始將數據從很遠的地方移到空閒的CPU中。每個級別之間的回退等待超時可以單獨配置,也可以一起配置在一個參數中。有關詳細信息,請參見配置頁面spark.locality
上的 參數。如果您的任務很長並且位置不佳,則應該增加這些設置,但是默認設置通常效果很好。