Je suis nouveau sur Spark et Scala. Je ne comprenais pas comment la fonction reductionByKey fonctionne dans Spark. Supposons que nous ayons le code suivant:
val lines = sc.textFile("data.txt")
val pairs = lines.map(s => (s, 1))
val counts = pairs.reduceByKey((a, b) => a + b)
La fonction map est claire: s est la clé et pointe sur la droite de data.txt
et 1 est la valeur.
Cependant, je ne comprenais pas comment la réductionByKey fonctionne en interne? Est-ce que "a" pointe vers la clé? Sinon, est-ce que "a" pointe sur "s"? Alors que représente a + b? comment sont-ils remplis?
Décomposons les méthodes et les types discrets. Cela expose généralement les subtilités des nouveaux développeurs:
pairs.reduceByKey((a, b) => a + b)
devient
pairs.reduceByKey((a: Int, b: Int) => a + b)
et renommer les variables le rend un peu plus explicite
pairs.reduceByKey((accumulatedValue: Int, currentValue: Int) => accumulatedValue + currentValue)
Donc, nous pouvons maintenant voir que nous prenons simplement une valeur accumulée pour la clé donnée et la sommons avec la valeur suivante de cette clé. MAINTENANT, passons à autre chose pour que nous puissions comprendre la partie clé. Alors, visualisons la méthode plutôt comme ceci:
pairs.reduce((accumulatedValue: List[(String, Int)], currentValue: (String, Int)) => {
//Turn the accumulated value into a true key->value mapping
val accumAsMap = accumulatedValue.toMap
//Try to get the key's current value if we've already encountered it
accumAsMap.get(currentValue._1) match {
//If we have encountered it, then add the new value to the existing value and overwrite the old
case Some(value : Int) => (accumAsMap + (currentValue._1 -> (value + currentValue._2))).toList
//If we have NOT encountered it, then simply add it to the list
case None => currentValue :: accumulatedValue
}
})
Ainsi, vous pouvez voir que la réduction ByKey permet de rechercher la clé et de la suivre afin que vous n'ayez pas à vous soucier de la gestion de cette partie.
plus profond, plus vrai si vous voulez
Cela étant dit, il s’agit d’une version simplifiée de ce qui se passe car certaines optimisations sont effectuées ici. Cette opération étant associative, le moteur spark effectuera ces réductions localement en premier (souvent appelé réduction au niveau de la carte)), puis de nouveau au niveau du pilote. Cela évite le trafic réseau; les données et l'exécution de l'opération, il peut réduire le plus petit possible et ensuite envoyer cette réduction sur le fil.
Une des exigences de la fonction reduceByKey
est qu’elle doit être associative. Pour construire une certaine intuition sur le fonctionnement de reduceByKey
, voyons d'abord comment une fonction associative associative nous aide dans un calcul parallèle:
Comme nous pouvons le constater, nous pouvons décomposer une collection originale en plusieurs morceaux et, en appliquant la fonction associative, nous pouvons accumuler un total. Le cas séquentiel est trivial, nous y sommes habitués: 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10.
L'associativité nous permet d'utiliser la même fonction de manière séquentielle et parallèle. reduceByKey
utilise cette propriété pour calculer un résultat à partir d'un RDD, qui est une collection distribuée composée de partitions.
Prenons l'exemple suivant:
// collection of the form ("key",1),("key,2),...,("key",20) split among 4 partitions
val rdd =sparkContext.parallelize(( (1 to 20).map(x=>("key",x))), 4)
rdd.reduceByKey(_ + _)
rdd.collect()
> Array[(String, Int)] = Array((key,210))
Dans spark, les données sont réparties dans des partitions. Pour l'illustration suivante, (4) partitions sont à gauche, délimitées par des lignes fines. Premièrement, nous appliquons la fonction localement à chaque partition, séquentiellement dans la partition, mais nous exécutons les 4 partitions en parallèle. Ensuite, les résultats de chaque calcul local sont agrégés en appliquant la même fonction encore et aboutissent finalement à un résultat.
reduceByKey
est une spécialisation de aggregateByKey
aggregateByKey
prend deux fonctions: une appliquée à chaque partition (séquentiellement) et une appliquée aux résultats de chaque partition (en parallèle). ) reduceByKey
utilise la même fonction associative dans les deux cas: effectuer un calcul séquentiel sur chaque partition, puis combiner ces résultats dans un résultat final, comme nous l'avons illustré ici.
Dans votre exemple de
val counts = pairs.reduceByKey((a,b) => a+b)
a
et b
sont tous deux Int
accumulateurs pour _2
des n-uplets dans pairs
. reduceKey
prendra deux n-uplets de même valeur s
et utilisera leur _2
valeurs comme a
et b
, produisant un nouveau Tuple[String,Int]
. Cette opération est répétée jusqu'à ce qu'il n'y ait qu'un seul Tuple pour chaque clé s
.
Contrairement à non -Spark (ou, en réalité, non parallèle) reduceByKey
où le premier élément est toujours l'accumulateur et le second une valeur, reduceByKey
fonctionne dans un environnement distribué. mode, c’est-à-dire que chaque nœud réduira son ensemble de nuplets en une collection de nuplets à clef unique, puis réduira les nuplets de plusieurs nœuds jusqu’à ce qu’il y ait un ensemble final à clef unique de tuples. Cela signifie que lorsque les résultats des nœuds sont réduits, a
et b
représentent des accumulateurs déjà réduits.
La fonction Spark RDD reductionByKey fusionne les valeurs de chaque clé à l’aide d’une fonction de réduction associative.
La fonction reductionByKey ne fonctionne que sur les RDD et il s’agit d’une opération de transformation qui signifie qu’elle est évaluée paresseusement. Et une fonction associative est passée en tant que paramètre, qui est appliqué au RDD source et crée un nouveau RDD en conséquence.
Donc, dans votre exemple, rdd pairs a un ensemble de plusieurs éléments appariés tels que (s1,1), (s2,1), etc. Et variable à la valeur par défaut 0 et additionne l'élément pour chaque clé et renvoie le résultat rdd compte avec le total des comptes appariés avec la clé.