簡體   English   中英

如何在火花中對每個執行器執行一次操作

[英]How to perform one operation on each executor once in spark

我有一個存儲在 S3 中的 weka 模型,其大小約為 400MB。 現在,我有一組記錄,我想在這些記錄上運行模型並執行預測。

為了執行預測,我嘗試過的是,

  1. 在驅動程序上下載並加載模型作為靜態對象,將其廣播給所有執行程序。 對預測 RDD 執行映射操作。 ----> 不工作,如在 Weka 中執行預測,模型對象需要修改並且廣播需要只讀副本。

  2. 將模型作為靜態對象下載並加載到驅動程序上,並在每個映射操作中將其發送到執行程序。 -----> 工作(效率不高,因為在每個地圖操作中,我傳遞了 400MB 對象)

  3. 在驅動程序上下載模型並將其加載到每個執行器上並將其緩存在那里。 (不知道怎么弄)

有人知道如何在每個執行器上加載模型一次並緩存它,以便我不會再次加載其他記錄嗎?

您有兩個選擇:

1. 創建一個用惰性 val 表示數據的單例對象:

    object WekaModel {
        lazy val data = {
            // initialize data here. This will only happen once per JVM process
        }
    }       

然后,您可以在map函數中使用惰性 val。 lazy val確保每個工作 JVM 初始化他們自己的數據實例。 不會對data執行序列化或廣播。

    elementsRDD.map { element =>
        // use WekaModel.data here
    }

優勢

  • 效率更高,因為它允許您為每個 JVM 實例初始化一次數據。 例如,當需要初始化數據庫連接池時,這種方法是一個不錯的選擇。

缺點

  • 對初始化的控制較少。 例如,如果您需要運行時參數,則初始化您的對象會比較棘手。
  • 如果需要,您無法真正釋放或釋放對象。 通常,這是可以接受的,因為操作系統會在進程退出時釋放資源。

2. 在 RDD 上使用mapPartition (或foreachPartition )方法,而不僅僅是map

這允許您初始化整個分區所需的任何內容。

    elementsRDD.mapPartition { elements =>
        val model = new WekaModel()

        elements.map { element =>
            // use model and element. there is a single instance of model per partition.
        }
    }

優點

  • 在對象的初始化和取消初始化方面提供更大的靈活性。

缺點

  • 每個分區都會創建並初始化對象的一個​​新實例。 根據每個 JVM 實例有多少個分區,這可能是也可能不是問題。

這是比惰性初始化程序對我更有效的方法。 我創建了一個初始化為空的對象級指針,並讓每個執行程序對其進行初始化。 在初始化塊中,您可以擁有一次性代碼。 請注意,每個處理批次將重置局部變量,但不會重置對象級變量。

object Thing1 {
  var bigObject : BigObject = null

  def main(args: Array[String]) : Unit = {
    val sc = <spark/scala magic here>
    sc.textFile(infile).map(line => {
      if (bigObject == null) {
         // this takes a minute but runs just once
         bigObject = new BigObject(parameters)  
      }
      bigObject.transform(line)
    })
  }
}

這種方法為每個執行器創建一個大對象,而不是其他方法的每個分區創建一個大對象。

如果將var bigObject : BigObject = null放在 main 函數命名空間中,它的行為會有所不同。 在這種情況下,它會在每個分區(即批處理)的開頭運行 bigObject 構造函數。 如果您有內存泄漏,那么這最終會殺死執行程序。 垃圾收集還需要做更多的工作。

這是我們通常做的

  1. 定義一個做這些事情的單例客戶端,以確保每個執行程序中只有一個客戶端

  2. 有一個 getorcreate 方法來創建或獲取客戶端信息,通常讓你有一個想要為多個不同模型服務的公共服務平台,然后我們可以使用像 concurrentmap 來確保線程安全和計算不存在

  3. getorcreate 方法將在 RDD 級別內調用,如轉換或 foreachpartition,因此請確保 init 發生在執行程序級別

您可以通過使用惰性 val 廣播 case 對象來實現此目的,如下所示:

case object localSlowTwo {lazy val value: Int = {Thread.sleep(1000); 2}}
val broadcastSlowTwo = sc.broadcast(localSlowTwo)
(1 to 1000).toDS.repartition(100).map(_ * broadcastSlowTwo.value.value).collect

三個線程的三個執行程序的事件時間線如下所示:

第 1 階段的活動時間表

從同一個 spark-shell 會話再次運行最后一行不再初始化:

第 3 階段的活動時間表

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM