J'ai, je crois, un cas d'utilisation relativement courant pour spark streaming:
J'ai un flux d'objets que je voudrais filtrer en fonction de certaines données de référence
Au départ, je pensais que ce serait une chose très simple à réaliser en utilisant une variable de diffusion :
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;
});
}
Cependant, bien que rarement, mes données de référence changeront périodiquement
J'avais l'impression que je pouvais modifier et rediffuser ma variable sur le pilote et elle serait propagée à chacun des travailleurs, cependant le Broadcast
l'objet n'est pas Serializable
et doit être final
.
Quelles alternatives ai-je? Les trois solutions auxquelles je peux penser sont:
Déplacez la recherche de données de référence dans un forEachPartition
ou forEachRdd
afin qu'elle réside entièrement sur les travailleurs. Cependant, les données de référence se trouvent derrière une API REST, donc je devrais également stocker une minuterie/un compteur pour empêcher l'accès à la télécommande pour chaque élément du flux.
Redémarrez le contexte Spark chaque fois que les refdata changent, avec une nouvelle variable de diffusion.
Convertissez les données de référence en [~ # ~] rdd [~ # ~] , puis join
les flux de cette manière que je diffuse maintenant Pair<MyObject, RefData>
, bien que cela envoie les données de référence avec chaque objet.
Prolonger la réponse Par @Rohan Aletty. Voici un exemple de code d'un BroadcastWrapper qui actualise la variable de diffusion en fonction de certains ttl
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;
}
}
Votre code ressemblerait à:
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;
});
}
Cela a également fonctionné pour moi sur le multi-cluster. J'espère que cela t'aides
Presque tous ceux qui traitent des applications de streaming ont besoin d'un moyen de tisser (filtrer, rechercher, etc.) des données de référence (à partir de bases de données, de fichiers, etc.) dans les données de streaming. Nous avons une solution partielle des deux parties entières
Rechercher des données de référence à utiliser dans les opérations de streaming
Pour la plupart, cela fonctionne bien, sauf pour les éléments suivants
Mettre à jour les données de référence
Il n'y a aucun moyen définitif d'y parvenir malgré les suggestions de ces discussions, à savoir: tuer la variable de diffusion précédente et en créer une nouvelle. Plusieurs inconnues comme ce à quoi s'attendre entre ces opérations.
C'est un tel besoin commun, cela aurait aidé s'il y avait un moyen d'envoyer des informations pour diffuser la mise à jour des informations variables. Avec cela, il est possible d'invalider les caches locaux dans "CacheLookup"
La deuxième partie du problème n'est toujours pas résolue. Je serais intéressé s'il existe une approche viable à ce sujet
Je ne sais pas si vous avez déjà essayé cela, mais je pense qu'une mise à jour d'une variable de diffusion peut être effectuée sans arrêter le SparkContext
. Grâce à l'utilisation de la méthode unpersist()
, les copies de la variable de diffusion sont supprimées sur chaque exécuteur et devraient être la variable devrait être rediffusée pour être à nouveau accessibles. Pour votre cas d'utilisation, lorsque vous souhaitez mettre à jour votre diffusion, vous pouvez:
Attendez que vos exécuteurs exécutent une série de données en cours
Annuler la résistance de la variable de diffusion
Mettre à jour la variable de diffusion
Rediffusion pour envoyer les nouvelles données de référence aux exécuteurs
Je m'inspire assez de ce post mais la personne qui a fait la dernière réponse a affirmé l'avoir fait fonctionner localement. Il est important de noter que vous souhaiterez probablement définir le blocage sur true
sur la dissociation afin que vous puissiez être sûr que les exécuteurs sont débarrassés des anciennes données (de sorte que les valeurs périmées ne seront pas lues à la prochaine itération) .
Récemment rencontré un problème avec cela. Pensé que cela pourrait être utile pour scala utilisateurs ..
La façon dont Scala fait BroadCastWrapper
est comme dans l'exemple ci-dessous.
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]]
}
}
Chaque fois que vous devez appeler la fonction de mise à jour pour obtenir une nouvelle variable de diffusion.