1. 程式人生 > >Spark官方調優文件翻譯(轉載)

Spark官方調優文件翻譯(轉載)

Spark調優

由於大部分Spark計算都是在記憶體中完成的,所以Spark程式的瓶頸可能由叢集中任意一種資源導致,如:CPU、網路頻寬、或者記憶體等。最常見的情況是,資料能裝進記憶體,而瓶頸是網路頻寬;當然,有時候我們也需要做一些優化調整來減少記憶體佔用,例如將RDD以序列化格式儲存(storing RDDs in serialized form)。本文將主要涵蓋兩個主題:1.資料序列化(這對於優化網路效能極為重要);2.減少記憶體佔用以及記憶體調優。同時,我們也會提及其他幾個比較小的主題。

資料序列化

序列化在任何一種分散式應用效能優化時都扮演幾位重要的角色。如果序列化格式序列化過程緩慢,或者需要佔用位元組很多,都會大大拖慢整體的計算效率。通常,序列化都是Spark應用優化時首先需要關注的地方。Spark著眼於要達到便利性(允許你在計算過程中使用任何Java型別)和效能的一個平衡。Spark主要提供了兩個序列化庫:

  • Java serialization: 預設情況,Spark使用Java自帶的ObjectOutputStream 框架來序列化物件,這樣任何實現了 java.io.Externalizable 來控制序列化效能。Java序列化很靈活但效能較差,同時序列化後佔用的位元組數也較多。
  • Kryo serialization: Spark還可以使用Kryo 庫(版本2)提供更高效的序列化格式。Kryo的序列化速度和位元組佔用都比Java序列化好很多(通常是10倍左右),但Kryo不支援所有實現了Serializable 介面的型別,它需要你在程式中 register 需要序列化的型別,以得到最佳效能。

要切換到使用 Kryo,你可以在 SparkConf 初始化的時候呼叫 conf.set(“spark.serializer”, “org.apache.spark.serializer.KryoSerializer”)。這個設定不僅控制各個worker節點之間的混洗資料序列化格式,同時還控制RDD存到磁碟上的序列化格式。目前,Kryo不是預設的序列化格式,因為它需要你在使用前註冊需要序列化的型別,不過我們還是建議在對網路敏感的應用場景下使用Kryo。

Spark對一些常用的Scala核心型別(包括在Twitter chill 庫的AllScalaRegistrar中)自動使用Kryo序列化格式。

如果你的自定義型別需要使用Kryo序列化,可以用 registerKryoClasses 方法先註冊:

val conf = new SparkConf().setMaster(...).setAppName(...)
conf.registerKryoClasses(Array(classOf[MyClass1], classOf[MyClass2]))
val sc = new SparkContext(conf)

Kryo的文件(Kryo documentation )中有詳細描述了更多的高階選項,如:自定義序列化程式碼等。

如果你的物件很大,你可能需要增大 spark.kryoserializer.buffer 配置項(config)。其值至少需要大於最大物件的序列化長度。

最後,如果你不註冊需要序列化的自定義型別,Kryo也能工作,不過每一個物件例項的序列化結果都會包含一份完整的類名,這有點浪費空間。

記憶體調優

記憶體佔用調優主要需要考慮3點:1.資料佔用的總記憶體(你多半會希望整個資料集都能裝進記憶體吧);2.訪問資料集中每個物件的開銷;3.垃圾回收的開銷(如果你的資料集中物件週轉速度很快的話)。

一般,Java物件的訪問時很快的,但同時Java物件會比原始資料(僅包含各個欄位值)佔用的空間多2~5倍。主要原因有:

  • 每個Java物件都有一個物件頭(object header),物件頭大約佔用16位元組,其中包含像其對應class的指標這樣的資訊。對於一些包含較少資料的物件(比如只包含一個Int欄位),這個物件頭可能比物件資料本身還大。
  • Java字串(String)有大約40子節點額外開銷(Java String以Char資料的形式儲存原始資料,所以需要一些額外的欄位,如陣列長度等),並且每個字元都以兩位元組的UTF-16編碼在內部儲存。因此,10個字元的String很容易就佔了60位元組。
  • 一些常見的集合類,如 HashMap、LinkedList,使用的是連結串列類資料結構,因此它們對每項資料都有一個包裝器。這些包裝器物件不僅其自身就有“物件頭”,同時還有指向下一個包裝器物件的連結串列指標(通常為8位元組)。
  • 原始型別的集合通常也是以“裝箱”的形式包裝成物件(如:java.lang.Integer)。

本節只是Spark記憶體管理的一個概要,下面我們會更詳細地討論各種Spark記憶體調優的具體策略。特別地,我們會討論如何評估資料的記憶體使用量,以及如何改進 – 要麼改變你的資料結構,要麼以某種序列化格式儲存資料。最後,我們還會討論如何調整Spark的快取大小,以及如何調優Java的垃圾回收器。

記憶體管理概覽

Spark中記憶體主要用於兩類目的:執行計算和資料儲存。執行計算的記憶體主要用於混洗(Shuffle)、關聯(join)、排序(sort)以及聚合(aggregation),而資料儲存的記憶體主要用於快取和叢集內部資料傳播。Spark中執行計算和資料儲存都是共享同一個記憶體區域(M)。如果執行計算沒有佔用記憶體,那麼資料儲存可以申請佔用所有可用的記憶體,反之亦然。執行計算可能會搶佔資料儲存使用的記憶體,並將儲存於記憶體的資料逐出記憶體,直到資料儲存佔用的記憶體比例降低到一個指定的比例(R)。換句話說,R是M基礎上的一個子區域,這個區域的記憶體資料永遠不會被逐出記憶體。然而,資料儲存不會搶佔執行計算的記憶體(否則實現太複雜了)。

這樣設計主要有這麼幾個需要考慮的點。首先,不需要快取資料的應用可以把整個空間用來執行計算,從而避免頻繁地把資料吐到磁碟上。其次,需要快取資料的應用能夠有一個數據儲存比例(R)的最低保證,也避免這部分快取資料被全部逐出記憶體。最後,這個實現方式能夠在預設情況下,為大多數使用場景提供合理的效能,而不需要專家級使用者來設定記憶體使用如何劃分。

雖然有兩個記憶體劃分相關的配置引數,但一般來說,使用者不需要設定,因為預設值已經能夠適用於絕大部分的使用場景:

  • spark.memory.fraction 表示上面M的大小,其值為相對於JVM堆記憶體的比例(預設0.75)。剩餘的25%是為其他使用者資料結構、Spark內部元資料以及避免OOM錯誤的安全預留空間(大量稀疏資料和異常大的資料記錄)。
  • spark.memory.storageFraction 表示上面R的大小,其值為相對於M的一個比例(預設0.5)。R是M中專門用於快取資料塊,且這部分資料塊永遠不會因執行計算任務而逐出記憶體。

評估記憶體消耗

確定一個數據集佔用記憶體總量最好的辦法就是,建立一個RDD,並快取到記憶體中,然後再到web UI上”Storage”頁面檢視。頁面上會展示這個RDD總共佔用了多少記憶體。

要評估一個特定物件的記憶體佔用量,可以用 SizeEstimator.estimate 方法。這個方法對試驗哪種資料結構能夠裁剪記憶體佔用量比較有用,同時,也可以幫助使用者瞭解廣播變數在每個執行器堆上佔用的記憶體量。

資料結構調優

減少記憶體消耗的首要方法就是避免過多的Java封裝(減少物件頭和額外輔助欄位),比如基於指標的資料結構和包裝物件等。以下有幾條建議:

  1. 設計資料結構的時候,優先使用物件陣列和原生型別,減少對複雜集合型別(如:HashMap)的使用。fastutil 提供了一些很方便的原聲型別集合,同時相容Java標準庫。
  2. 儘可能避免巢狀大量的小物件和指標。
  3. 對應鍵值應儘量使用數值型或列舉型,而不是字串型。
  4. 如果記憶體小於32GB,可以設定JVM標誌引數 -XX:+UseCompressdOops 將指標設為4位元組而不是8位元組。你可以在  

序列化RDD儲存

如果經過上面的調整後,儲存的資料物件還是太大,那麼你可以試試將這些物件以序列化格式儲存,所需要做的只是通過 RDD persistence API 設定好儲存級別,如:MEMORY_ONLY_SER。Spark會將RDD的每個分割槽以一個巨大的位元組陣列形式儲存起來。以序列化格式儲存的唯一缺點就是訪問資料會變慢一點,因為Spark需要反序列化每個被訪問的物件。如果你需要序列化快取資料,我們強烈建議你使用Kryo(using Kryo),和Java序列化相比,Kryo能大大減少序列化物件佔用的空間(當然也比原始Java物件小很多)。

垃圾回收調優

JVM的垃圾回收在某些情況下可能會造成瓶頸,比如,你的RDD儲存經常需要“換入換出”(新RDD搶佔了老RDD記憶體,不過如果你的程式沒有這種情況的話那JVM垃圾回收一般不是問題,比如,你的RDD只是載入一次,後續只是在這一個RDD上做操作)。當Java需要把老物件逐出記憶體的時候,JVM需要跟蹤所有的Java物件,並找出那些物件已經沒有用了。概括起來就是,垃圾回收的開銷和物件個數成正比,所以減少物件的個數(比如用 Int陣列取代 LinkedList),就能大大減少垃圾回收的開銷。當然,一個更好的方法就如前面所說的,以序列化形式儲存資料,這時每個RDD分割槽都只包含有一個物件了(一個巨大的位元組陣列)。在嘗試其他技術方案前,首先可以試試用序列化RDD的方式(serialized caching)評估一下GC是不是一個瓶頸。

如果你的作業中各個任務需要的工作記憶體和節點上儲存的RDD快取佔用的記憶體產生衝突,那麼GC很可能會出現問題。下面我們將討論一下如何控制好RDD快取使用的記憶體空間,以減少這種衝突。

衡量GC的影響

GC調優的第一步是統計一下,垃圾回收啟動的頻率以及GC所使用的總時間。給JVM設定一下這幾個引數(參考Spark配置指南 –  configuration guide,檢視Spark作業中的Java選項引數):-verbose:gc -XX:+PrintGCDetails,就可以在後續Spark作業的worker日誌中看到每次GC花費的時間。注意,這些日誌是在叢集worker節點上(在各節點的工作目錄下stdout檔案中),而不是你的驅動器所在節點。

高階GC調優

為了進一步調優GC,我們就需要對JVM記憶體管理有一個基本的瞭解:

  • Java堆記憶體可分配的空間有兩個區域:新生代(Young generation)和老生代(Old generation)。新生代用以儲存生存週期短的物件,而老生代則是儲存生存週期長的物件。
  • 新生代區域被進一步劃分為三個子區域:Eden,Survivor1,Survivor2。
  • 簡要描述一下垃圾回收的過程:如果Eden區滿了,則啟動一輪minor GC回收Eden中的物件,生存下來(沒有被回收掉)的Eden中的物件和Survivor1區中的物件一併複製到Survivor2中。兩個Survivor區域是互相切換使用的(就是說,下次從Eden和Survivor2中複製到Survivor1中)。如果某個物件的年齡(每次GC所有生存下來的物件長一歲)超過某個閾值,或者Survivor2(下次是Survivor1)區域滿了,則將物件移到老生代(Old區)。最終如果老生代也滿了,就會啟動full GC。

Spark GC調優的目標就是確保老生代(Old generation )只儲存長生命週期RDD,而同時新生代(Young generation )的空間又能足夠儲存短生命週期的物件。這樣就能在任務執行期間,避免啟動full GC。以下是GC調優的主要步驟:

  • 從GC的統計日誌中觀察GC是否啟動太多。如果某個任務結束前,多次啟動了full GC,則意味著用以執行該任務的記憶體不夠。
  • 如果GC統計資訊中顯示,老生代記憶體空間已經接近存滿,可以通過降低 spark.memory.storageFraction 來減少RDD快取佔用的記憶體;減少快取物件總比任務執行緩慢要強!
  • 如果major GC比較少,但minor GC很多的話,可以多分配一些Eden記憶體。你可以把Eden的大小設為高於各個任務執行所需的工作記憶體。如果要把Eden大小設為E,則可以這樣設定新生代區域大小:-Xmn=4/3*E。(放大4/3倍,主要是為了給Survivor區域保留空間)
  • 舉例來說,如果你的任務會從HDFS上讀取資料,那麼單個任務的記憶體需求可以用其所讀取的HDFS資料塊的大小來評估。需要特別注意的是,解壓後的HDFS塊是解壓前的2~3倍大。所以如果我們希望保留3~4個任務並行的工作記憶體,並且HDFS塊大小為64MB,那麼可以評估Eden的大小應該設為 4*3*64MB。
  • 最後,再觀察一下垃圾回收的啟動頻率和總耗時有沒有什麼變化。

我們的很多經驗表明,GC調優的效果和你的程式程式碼以及可用的總記憶體相關。網上還有不少調優的選項說明(many more tuning options),但總體來說,就是控制好full GC的啟動頻率,就能有效減少垃圾回收開銷。

其他注意事項

並行度

一般來說叢集並不會滿負荷運轉,除非你吧每個操作的並行度都設得足夠大。Spark會自動根據對應的輸入檔案大小來設定“map”類運算元的並行度(當然你可以通過一個SparkContext.textFile等函式的可選引數來控制並行度),而對於想 groupByKey 或reduceByKey這類 “reduce” 運算元,會使用其各父RDD分割槽數的最大值。你可以將並行度作為構建RDD第二個引數(參考spark.PairRDDFunctions ),或者設定 spark.default.parallelism 這個預設值。一般來說,評估並行度的時候,我們建議2~3個任務共享一個CPU。

Reduce任務的記憶體佔用

如果RDD比記憶體要大,有時候你可能收到一個OutOfMemoryError,但其實這是因為你的任務集中的某個任務太大了,如reduce任務groupByKey。Spark的混洗(Shuffle)運算元(sortByKey,groupByKey,reduceByKey,join等)會在每個任務中構建一個雜湊表,以便在任務中對資料分組,這個雜湊表有時會很大。最簡單的修復辦法就是增大並行度,以減小單個任務的輸入集。Spark對於200ms以內的短任務支援非常好,因為Spark可以跨任務複用執行器JVM,任務的啟動開銷很小,因此把並行度增加到比叢集中總CPU核數還多是沒有任何問題的。

廣播大變數

使用SparkContext中的廣播變數相關功能(broadcast functionality)能大大減少每個任務本身序列化的大小,以及叢集中啟動作業的開銷。如果你的Spark任務正在使用驅動器(driver)程式中定義的巨大物件(比如:靜態查詢表),請考慮使用廣播變數替代之。Spark會在master上將各個任務的序列化後大小打印出來,所以你可以檢查一下各個任務是否過大;通常來說,大於20KB的任務就值得優化一下。

資料本地性

資料本地性對Spark作業往往會有較大的影響。如果程式碼和其所操作的資料在統一節點上,那麼計算速度肯定會更快一些。但如果二者不在一起,那必然需要挪動其中之一。一般來說,挪動序列化好的程式碼肯定比挪動一大堆資料要快。Spark就是基於這個一般性原則來構建資料本地性的排程。

資料本地性是指程式碼和其所處理的資料的距離。基於資料當前的位置,資料本地性可以劃分成以下幾個層次(按從近到遠排序):

  • PROCESS_LOCAL 資料和執行的程式碼處於同一個JVM程序內。
  • NODE_LOCAL 資料和程式碼處於同一節點。例如,資料處於HDFS上某個節點,而對應的執行器(executor)也在同一個機器節點上。這會比PROCESS_LOCAL稍微慢一些,因為資料需要跨程序傳遞。
  • NO_PREF 資料在任何地方處理都一樣,沒有本地性偏好。
  • RACK_LOCAL 資料和程式碼處於同一個機架上的不同機器。這時,資料和程式碼處於不同機器上,需要通過網路傳遞,但還是在同一個機架上,一般也就通過一個交換機傳輸即可。
  • ANY 資料在網路中其他未知,即資料和程式碼不在同一個機架上。

Spark傾向於讓所有任務都具有最佳的資料本地性,但這並非總是可行的。某些情況下,可能會出現一些空閒的執行器(executor)沒有待處理的資料,那麼Spark可能就會犧牲一些資料本地性。有兩種可能的選項:a)等待已經有任務的CPU,待其釋放後立即在同一臺機器上啟動一個任務;b)立即在其他節點上啟動新任務,並把所需要的資料複製過去。

而通常,Spark會等待一小會,看看是否有CPU會被釋放出來。一旦等待超時,則立即在其他節點上啟動並將所需的資料複製過去。資料本地性各個級別之間的回落超時可以單獨配置,也可以在統一引數內一起設定;詳細請參考 configuration page 中的 spark.locality 相關引數。如果你的任務執行時間比較長並且資料本地性很差,你就應該試試調大這幾個引數,不過預設值一般都能適用於大多數場景了。

總結

本文是一個簡短的Spark調優指南,列舉了Spark應用調優一些比較重要的考慮點 – 最重要的就是,資料序列化和記憶體調優。對於絕大多數應用來說,用Kryo格式序列化資料能夠解決大多數的效能問題。如果您有其他關於效能調優最佳實踐的問題,歡迎郵件諮詢(Spark mailing list )。

相關推薦

Spark官方調翻譯轉載

Spark調優 由於大部分Spark計算都是在記憶體中完成的,所以Spark程式的瓶頸可能由叢集中任意一種資源導致,如:CPU、網路頻寬、或者記憶體等。最常見的情況是,資料能裝進記憶體,而瓶頸是網路頻寬;當然,有時候我們也需要做一些優化調整來減少記憶體佔用,例如將RDD以序列化格式儲存(storing RD

Spark官方調翻譯轉載

區域 ng- 完整 好的 java類型 int 單個 rdd 常見 Spark調優 由於大部分Spark計算都是在內存中完成的,所以Spark程序的瓶頸可能由集群中任意一種資源導致,如:CPU、網絡帶寬、或者內存等。最常見的情況是,數據能裝進內存,而瓶頸是網絡帶寬;當

Hyperledger Fabric 1.3 官方翻譯關鍵概念 (Key Concepts)

身份(Identity) 什麼是身份(What is an Identity)? The different actors in a blockchain network include peers, orderers, client applications,

Hyperledger Fabric 1.3 官方翻譯教程 (Tutorials)

構建你的第一個網路(Building Your First Network) These instructions have been verified to work against the latest stable Docker images and t

PureMVC 官方翻譯

    最近在學習PureMVC框架,感覺最權威的還是閱讀官方文件,順便翻譯了下全當記筆記了。 PureMVC概覽     這篇文件他討論PureMVC框架的類和介面,使用UML來闡述它們的角色、職責和協作。     PureMVC框架有一個非常細小的目標一一就是幫助你把應用程式編碼之間的關聯分離成明確

ECMAScript 2015官方翻譯

宣告:   1.翻譯文章旨在輔助理解,沒有講究語言方面的信達雅,英文原版傳送門:http://www.ecma-international.org/ecma-262/6.0/   2.有的地方翻譯的很拗口,因為官方文件用詞句法都很嚴謹,本人也是

Autofac官方翻譯——Getting Started

立即開始 將Autofac整合到你的應用的基本模式如下: 按照控制反轉(IoC)的思想構建你的應用程式 新增Autofac引用 在application啟動程式碼裡… 建立ContainerBuilder物件 註冊元件 Build容器並且儲存以備用 在程式

Kafka官方翻譯產品概述

流平臺的三要素: 1、提供釋出/訂閱記錄流的能力,類似於訊息佇列; 2、對記錄流的儲存有容錯能力; 3、可以即時處理記錄流。kafka可用於兩大類應用: 1、建立實時流資料管道,在系統或應用之間進行可靠傳輸; 2、建立基於實時流的應用,可以傳輸或處理資料流。先知概念: *k

Django 2.0 之Models(模型) 官方翻譯

以下翻譯是自己學習的時候順便記下的,如果有不對的地方還請指正。 模型是關於你的資料的唯一、確定的資料來源。它包含你所儲存的資料的基本欄位和行為。通常,每個模型對映到一個數據庫表。 基礎知識: 每一個模型都是一個Python類,它是 django.db.models.Mo

Pilosa翻譯入門指南

目錄 開始 Pilosa 簡單專案 建立架構(Create the Schema) 從CVS檔案匯入資料 做一些查詢(Queries) 接下來做什麼? Pilosa支援預設使用JSON的HTTP介面。 任何HTTP工具都可用於與Pilosa伺服器進行互動。 本文件中的示例將使

Pilosa翻譯示例

目錄 傳輸Transportation 簡單說明 Introduction 資料模型 Data Model 對映Mapping 原文地址 傳輸Transportation 簡單說明 Introduction 紐約市釋出了一個非

Vue-Analgtics 使用者翻譯部分

本部落格旨在個人筆記記錄。 一.1.開始 安裝      npm install vue-analytics --save    2.開始使用你的Vue應用程式,在main.js中加入以下程式碼      import Vue from 'vue'      import

V4L2翻譯十三

I/O流 (DMA快取引用) 這是一個實驗性介面,將來可能發生改變 DMABUF框架提供了在多裝置見共享快取的通用方法,支援DMABUF的裝置驅動可以將一個DMA快取以檔案控制代碼的方式輸出到使用者空間(輸出者規則),以檔案控制代碼的方式從使用者空間獲取一個DMA快取

V4L2翻譯

影象裁剪、插入及縮放 一些視訊捕捉裝置可以取一張圖片的小部分,然後對圖片進行任意尺寸的放大或縮小。我們將這些能力稱之為裁剪和縮放。一些視訊使出裝置可以將圖片放大或縮小,然後將其插入到視訊訊號的任意掃描線和橫向偏移中。 應用程式可以使用一下API來選擇視訊訊號中的區域

Theano Tutorial翻譯:詞彙

Apply 應用 應用的意思是對於輸入進行運算然後產生輸出值。就像是數學函式a[符號值] Broadcasting 廣播 廣播是一個允許把不同維度的張量通過一個一個元素進行運算的機制。他通過複製相對小的張量來進行運算。 Constant 常數

V4L2翻譯

裁剪、組合及縮放的實驗性API 實驗性介面將來也許會發生改變 介紹 一些視訊捕捉裝置可以對一張圖片的取樣部分進行任意尺寸的縮小或放大。然後,這些裝置可以講這個圖片插入到更大的圖片中。一些視訊輸出裝置可以對輸入圖片進行部分裁剪,對其進行縮放以及將其插入到視訊訊號的任意

V4L2翻譯

視訊標準 視訊裝置通常支援一種或多種不同的視訊標準或更多的標準衍生體。每一個視訊輸入和輸出支援一組標準。這項設定由VIDIOC_ENUMINPUT和VIDIOC_ENUMOUTPUT ioctl返回的v4l2_input和v4l2_output結構體中的std成員體現。

V4L2翻譯

擴充套件控制 介紹 控制機制原本是用於使用者設定(如亮度,飽和度等)。但無論如何,它恰恰證明了對於複雜驅動他是個非常有用的模型,如每個驅動包含一個大型API的子模組。 MPEG編碼API就是在這樣的設計理念下:MPEG標準十分龐大,當前支援MPEG編碼的硬體只是聲明瞭這個

V4L2翻譯

好久沒更新,有好幾篇翻譯都沉寂在Wiznote中了。趕緊發出來~ YUV格式 YUV是源自於TV廣播的符合視訊訊號,它將亮度資訊(Y)從顏色資訊(U、V或Cb、Cr)。顏色資訊涵蓋了紅色和藍色的顏色差異訊號,這樣藍色部分就可以通過減去亮度資訊進行重建。見“顏色空間”

android開源圖表庫MPAndroidChart翻譯

public interface OnChartGestureListener { /** * Callbacks when a touch-gesture has started on the chart (ACTION_DOWN) * * @param me