簡體   English   中英

如何更新火花流中的廣播變量?

[英]How can I update a broadcast variable in spark streaming?

我相信,我有一個相對常見的 Spark 流用例:

我有一個對象流,我想根據一些參考數據進行過濾

最初,我認為使用廣播變量實現這將是一件非常簡單的事情:

public void startSparkEngine {
    Broadcast<ReferenceData> refdataBroadcast
      = sparkContext.broadcast(getRefData());

    final JavaDStream<MyObject> filteredStream = objectStream.filter(obj -> {
        final ReferenceData refData = refdataBroadcast.getValue();
        return obj.getField().equals(refData.getField());
    }

    filteredStream.foreachRDD(rdd -> {
        rdd.foreach(obj -> {
            // Final processing of filtered objects
        });
        return null;
    });
}

但是,盡管很少,我的參考數據會定期更改

我的印象是我可以在驅動程序上修改和重新廣播我的變量,它會傳播給每個工作人員,但是Broadcast對象不是Serializable並且需要是final

我有哪些選擇? 我能想到的三個解決方案是:

  1. 將參考數據查找移動到forEachPartitionforEachRdd以便它完全駐留在工作人員上。 然而,參考數據存在於 REST API 中,所以我還需要以某種方式存儲一個計時器/計數器以停止對流中每個元素的遠程訪問。

  2. 每次 refdata 更改時,使用新的廣播變量重新啟動 Spark 上下文。

  3. 將參考數據轉換為RDD ,然后以我現在正在流式傳輸Pair<MyObject, RefData>的方式join流,盡管這將隨每個對象一起Pair<MyObject, RefData>參考數據。

擴展答案@Rohan Aletty。 這是一個基於某些 ttl 刷新廣播變量的 BroadcastWrapper 示例代碼

public class BroadcastWrapper {

    private Broadcast<ReferenceData> broadcastVar;
    private Date lastUpdatedAt = Calendar.getInstance().getTime();

    private static BroadcastWrapper obj = new BroadcastWrapper();

    private BroadcastWrapper(){}

    public static BroadcastWrapper getInstance() {
        return obj;
    }

    public JavaSparkContext getSparkContext(SparkContext sc) {
       JavaSparkContext jsc = JavaSparkContext.fromSparkContext(sc);
       return jsc;
    }

    public Broadcast<ReferenceData> updateAndGet(SparkContext sparkContext){
        Date currentDate = Calendar.getInstance().getTime();
        long diff = currentDate.getTime()-lastUpdatedAt.getTime();
        if (var == null || diff > 60000) { //Lets say we want to refresh every 1 min = 60000 ms
            if (var != null)
               var.unpersist();
            lastUpdatedAt = new Date(System.currentTimeMillis());

            //Your logic to refresh
            ReferenceData data = getRefData();

            var = getSparkContext(sparkContext).broadcast(data);
       }
       return var;
   }
}

您的代碼如下所示:

public void startSparkEngine() {

    final JavaDStream<MyObject> filteredStream = objectStream.transform(stream -> {
        Broadcast<ReferenceData> refdataBroadcast = BroadcastWrapper.getInstance().updateAndGet(stream.context());

        stream.filter(obj -> obj.getField().equals(refdataBroadcast.getValue().getField()));
    });

    filteredStream.foreachRDD(rdd -> {
        rdd.foreach(obj -> {
        // Final processing of filtered objects
        });
        return null;
    });
}

這對我來說也適用於多集群。 希望這可以幫助

最近遇到了這個問題。 認為它可能對 Scala 用戶有幫助..

Scala 執行BroadCastWrapper方式如下例所示。

import java.io.{ ObjectInputStream, ObjectOutputStream }
import org.apache.spark.broadcast.Broadcast
import org.apache.spark.streaming.StreamingContext
import scala.reflect.ClassTag

/* wrapper lets us update brodcast variables within DStreams' foreachRDD
 without running into serialization issues */
case class BroadcastWrapper[T: ClassTag](
 @transient private val ssc: StreamingContext,
  @transient private val _v: T) {

  @transient private var v = ssc.sparkContext.broadcast(_v)

  def update(newValue: T, blocking: Boolean = false): Unit = {

    v.unpersist(blocking)
    v = ssc.sparkContext.broadcast(newValue)
  }

  def value: T = v.value

  private def writeObject(out: ObjectOutputStream): Unit = {
    out.writeObject(v)
  }

  private def readObject(in: ObjectInputStream): Unit = {
    v = in.readObject().asInstanceOf[Broadcast[T]]
  }
}

每次您需要調用更新函數來獲取新的廣播變量。

幾乎每個處理流應用程序的人都需要一種方法來將(過濾、查找等)參考數據(來自數據庫、文件等)編織到流數據中。 我們有整個兩部分的部分解決方案

  1. 查找要在流操作中使用的參考數據

    • 使用所需的緩存 TTL 創建 CacheLookup 對象
    • 將其包裝在廣播中
    • 使用 CacheLookup 作為流邏輯的一部分

在大多數情況下,這可以正常工作,但以下情況除外

  1. 更新參考數據

    盡管在這些線程中提出了建議,但沒有明確的方法可以實現這一點,即:殺死先前的廣播變量並創建新的變量。 多個未知數,例如這些操作之間的預期結果。

這是一個如此普遍的需求,如果有一種方法可以將信息發送到廣播變量通知更新,它會有所幫助。 這樣,就可以使“CacheLookup”中的本地緩存無效

問題的第二部分仍然沒有解決。 如果有任何可行的方法,我會感興趣

不確定您是否已經嘗試過,但我認為可以在不關閉SparkContext情況下實現對廣播變量的更新。 通過使用unpersist()方法,廣播變量的副本在每個執行程序上被刪除,並且需要重新廣播的變量才能再次訪問。 對於您的用例,當您想要更新廣播時,您可以:

  1. 等待您的執行者完成當前的一系列數據

  2. 取消持久化廣播變量

  3. 更新廣播變量

  4. 重新廣播以將新的參考數據發送給執行者

我從這篇文章中大量借鑒,但最后回復的人聲稱已在本地工作。 重要的是要注意,您可能希望在非持久性上將阻塞設置為true ,以便您可以確保執行程序清除舊數據(因此在下一次迭代中不會再次讀取陳舊值)。

最簡單的實現方法,下面的代碼讀取每個批次的維度數據文件夾,但請記住,新的維度數據值(在我的情況下為國家/地區名稱)必須是一個新文件。

package com.databroccoli.streaming.dimensionupateinstreaming

import org.apache.log4j.{Level, Logger}
import org.apache.spark.sql.{DataFrame, ForeachWriter, Row, SparkSession}
import org.apache.spark.sql.functions.{broadcast, expr}
import org.apache.spark.sql.types.{StringType, StructField, StructType, TimestampType}

object RefreshDimensionInStreaming {

  def main(args: Array[String]) = {

    @transient lazy val logger: Logger = Logger.getLogger(getClass.getName)

    Logger.getLogger("akka").setLevel(Level.WARN)
    Logger.getLogger("org").setLevel(Level.ERROR)
    Logger.getLogger("com.amazonaws").setLevel(Level.ERROR)
    Logger.getLogger("com.amazon.ws").setLevel(Level.ERROR)
    Logger.getLogger("io.netty").setLevel(Level.ERROR)

    val spark = SparkSession
      .builder()
      .master("local")
      .getOrCreate()

    val schemaUntyped1 = StructType(
      Array(
        StructField("id", StringType),
        StructField("customrid", StringType),
        StructField("customername", StringType),
        StructField("countrycode", StringType),
        StructField("timestamp_column_fin_1", TimestampType)
      ))

    val schemaUntyped2 = StructType(
      Array(
        StructField("id", StringType),
        StructField("countrycode", StringType),
        StructField("countryname", StringType),
        StructField("timestamp_column_fin_2", TimestampType)
      ))

    val factDf1 = spark.readStream
      .schema(schemaUntyped1)
      .option("header", "true")
      .csv("src/main/resources/broadcasttest/fact")

    var countryDf: Option[DataFrame] = None: Option[DataFrame]

    def updateDimensionDf() = {
      val dimDf2 = spark.read
        .schema(schemaUntyped2)
        .option("header", "true")
        .csv("src/main/resources/broadcasttest/dimension")

      if (countryDf != None) {
        countryDf.get.unpersist()
      }

      countryDf = Some(
        dimDf2
          .withColumnRenamed("id", "id_2")
          .withColumnRenamed("countrycode", "countrycode_2"))

      countryDf.get.show()
    }

    factDf1.writeStream
      .outputMode("append")
      .foreachBatch { (batchDF: DataFrame, batchId: Long) =>
        batchDF.show(10)

        updateDimensionDf()

        batchDF
          .join(
            countryDf.get,
            expr(
              """
      countrycode_2 = countrycode 
      """
            ),
            "leftOuter"
          )
          .show

      }
      .start()
      .awaitTermination()

  }

}

我以不同的方式做到了。

我創建了一個廣播變量並每 5 分鍾在驅動程序的不同線程中更新它。

  var broadcastValue: Broadcast[Set[String]] = spark.sparkContext.broadcast(calculateValue())

  def runScheduledThreadToUpdateBroadcastVariable(): Unit = {
    val updateTask = new Runnable {
      def run() = {
        broadcastValue.unpersist(blocking = false)
        broadcastValue = spark.sparkContext.broadcast(calculateValue())
      }
    }

    val executor = new ScheduledThreadPoolExecutor(1)
    executor.scheduleAtFixedRate(updateTask, 1, 5, TimeUnit.MINUTES)
  }

暫無
暫無

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

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