web-dev-qa-db-fra.com

Comment puis-je mettre à jour une variable de diffusion dans spark streaming?

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:

  1. 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.

  2. Redémarrez le contexte Spark chaque fois que les refdata changent, avec une nouvelle variable de diffusion.

  3. 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.

28
Andrew Stubbs

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

23
Aastha

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

  1. Rechercher des données de référence à utiliser dans les opérations de streaming

    • créer un objet CacheLookup avec le cache TTL souhaité
    • envelopper cela en diffusion
    • utiliser CacheLookup dans le cadre de la logique de streaming

Pour la plupart, cela fonctionne bien, sauf pour les éléments suivants

  1. 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

6
Ravi Reddy

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:

  1. Attendez que vos exécuteurs exécutent une série de données en cours

  2. Annuler la résistance de la variable de diffusion

  3. Mettre à jour la variable de diffusion

  4. 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) .

3
Rohan Aletty

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.

3
Ram Ghadiyaram