[英]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
。
我有哪些選擇? 我能想到的三個解決方案是:
將參考數據查找移動到forEachPartition
或forEachRdd
以便它完全駐留在工作人員上。 然而,參考數據存在於 REST API 中,所以我還需要以某種方式存儲一個計時器/計數器以停止對流中每個元素的遠程訪問。
每次 refdata 更改時,使用新的廣播變量重新啟動 Spark 上下文。
將參考數據轉換為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]]
}
}
每次您需要調用更新函數來獲取新的廣播變量。
幾乎每個處理流應用程序的人都需要一種方法來將(過濾、查找等)參考數據(來自數據庫、文件等)編織到流數據中。 我們有整個兩部分的部分解決方案
查找要在流操作中使用的參考數據
在大多數情況下,這可以正常工作,但以下情況除外
更新參考數據
盡管在這些線程中提出了建議,但沒有明確的方法可以實現這一點,即:殺死先前的廣播變量並創建新的變量。 多個未知數,例如這些操作之間的預期結果。
這是一個如此普遍的需求,如果有一種方法可以將信息發送到廣播變量通知更新,它會有所幫助。 這樣,就可以使“CacheLookup”中的本地緩存無效
問題的第二部分仍然沒有解決。 如果有任何可行的方法,我會感興趣
不確定您是否已經嘗試過,但我認為可以在不關閉SparkContext
情況下實現對廣播變量的更新。 通過使用unpersist()
方法,廣播變量的副本在每個執行程序上被刪除,並且需要重新廣播的變量才能再次訪問。 對於您的用例,當您想要更新廣播時,您可以:
等待您的執行者完成當前的一系列數據
取消持久化廣播變量
更新廣播變量
重新廣播以將新的參考數據發送給執行者
我從這篇文章中大量借鑒,但最后回復的人聲稱已在本地工作。 重要的是要注意,您可能希望在非持久性上將阻塞設置為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.