Spark性能調優 - 關鍵性能考量

一、RDD並行度

默認情況下,Spark會對RDD自動分配合適的並行度,但這並不總是有效的。
Spark提供兩種方法對操作的並行度進行調優:

  • 第一種方法是在數據混洗操作時,使用參數的方式爲混洗後的RDD指定並行度

  • 第二種方法是對於任何已有的RDD,可以進行重新分區來獲取更多或者更少的分區數。

    重新分區操作通過 repartition() 實現,該操作會把RDD隨機打亂並分成設定的分區數目。
    如果確定要減少RDD分區,可使用 coalesce() 操作。由於沒有打亂數據,該操作比 repartition() 更高效。

    舉個例子,假設我們讀取大量數據,然後馬上進行filter()操作篩選掉數據集中絕大部分數據。默認情況下,filter()返回的RDD的分區數和其父節點一樣,這樣可能會產生很多空的分區或者只有很少數據的分區。在這種情況下,可通過合併得到分區更少的RDD來提高應用性能。如下:

    例:在PySpark shell中合併分區過多的RDD
    # RDD輸入
    >>> input = sc.textFile("/data/*.log")
    >>> input.getNumPartitions()
    35000
    # 排除掉大部分數據的篩選方法
    >>> lines = input.filter(lambda line: line.startswith("2019-01-01"))
    >>> lines.getNumPartitions()
    4
    # 可以在合併之後的RDD進行後續分析
    >>> lines.count()
    

二、數據序列化格式

當Spark需要通過網絡傳輸數據,或將數據溢寫到磁盤上時,Spark需要把數據序列化爲二進制格式。序列化會在數據進行混洗操作時發生,此時可能需要通過網絡傳輸大量數據。默認情況下,Spark會使用Java內建的序列化庫,第三方庫Kryo有更好性能。

使用Kryo序列化工具:

  • 需設置 spark.serializerorg.apache.spark.serializer.KryoSerializer .
  • 向Kryo註冊需要序列化的類,以獲得最佳性能。若想強制要求註冊,可設置 spark.kryo.registrationRequired => true
#使用Kryo序列化工具並註冊所需類
val conf = new SparkConf()
conf.set("spark.serializer", "org.apache.spark.serializer.KryoSerializer")
// 嚴格要求註冊類
conf.set("spark.kryo.registrationRequired", "true")
conf.registerKryoClasses(Array(classOf[MyClass], classOf[MyOtherClass]))

不論是選用Kryo還是Java序列化,如果代碼中引用到了一個沒有擴展Java的Serializable接口的類,你都會遇到NotSerializableException。
很多JVM都支持通過設置選項 -Dsun.io.serialization.extended DebugInfo=true 來幫助調試,

我們可通過設置 spark-submit 的:
–driver-java-options 以及 --executor-java-options 來打開JVM設置選項

三、內存管理

在各個執行器進程中,內存有如下所列幾種用途

  • RDD存儲
    當調用RDD的persist() 或 cache() 方法時,這個RDD的分區會被存儲到緩存區中。Spark會根據 spark.storage.memoryFraction 限制用來緩存的內存佔整個JVM堆空間的比例大小。如果超出限制,舊的分區數據會被移出內存。

  • 數據混洗與聚合的緩存區
    當進行數據混洗操作是,Spark會創建出溢寫中間緩存區:

    • 存儲聚合操作的中間結果
    • 數據混洗操作中直接輸出的部分緩存數據

    spark.shuffle.memoryFraction 可用於限定緩存區內存佔總內存的比例

  • 用戶代碼
    Spark可執行用戶的任意合法代碼,用戶的函數可自行申請大量內存。

在默認情況下,Spark會使用60%的空間來存儲RDD,20%存儲數據混洗操作產生的數據,剩下20%留給用戶程序。用戶可自行調節這些選項來追求更好的性能表現。

四、硬件供給

提供Spark的硬件資源會顯著影響應用完成時間。影響集羣規模的主要參數包括:

  • 分配給每個執行器節點的內存大小
  • 每個執行器節點佔用核心數
  • 執行器節點總數
  • 用於存儲臨時數據的本地磁盤數

1. 執行器節點內存

通過設置 spark.executor.memory 或者 spark-submit 的 --executor-memory 標記設置 執行器節點內存

切記:“越多越好”原則在設置執行器節點內存時並不一定適用。使用巨大的堆空間可能會導致垃圾回收長時間暫停,從而嚴重影響Spark作業吞吐量。

緩解 長時間垃圾回收暫停 辦法:

  1. 使用較小內存(比如不超過64GB)的執行器實例可緩解長時間垃圾回收暫停。
  2. 使用序列化格式存儲大對象數據

2. 執行器節點佔用核心數及節點總數

執行器節點數目以及每個執行器進程的核心數的配置選項則取決於各種部署模式

  1. 在YARN模式下,可以通過 spark.executor.cores 或 --executor-cores 標記來設置 執行器節點核心數
    可通過設置 --num-executors 設置 執行器節點總數
  2. 在Mesos和獨立模式中, Spark會從調度器提供的資源中獲取儘可能多的核心用於 執行器節點
    也可通過 spark.cores.max 限制一個應用中所有執行器節點所使用的核心總數。

3. 本地磁盤數

本地磁盤可存儲數據混洗操作的中間數據,以及溢寫到磁盤中的RDD分區數據。因此大量使用本地磁盤可以幫助提升Spark應用性能。

  1. Yarn模式下
    Spark 本地磁盤配置項會直接從YARN的配置中讀取
  2. 獨立模式下
    spark-env.sh 中設置環境變量 SPARK_LOCAL_DIRS
  3. Mesos或其它模式
    spark.local.dir 選項可重載集羣默認存儲位置

參考

  1. 《快速大數據分析》
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章