web-dev-qa-db-fra.com

Comment fonctionne la fonction d'agrégation Spark - aggregByKey?

Disons que j'ai un système de distribution sur 3 nœuds et que mes données sont réparties entre ces nœuds. par exemple, j'ai un fichier test.csv qui existe sur les 3 nœuds et il contient 2 colonnes de:

**row   | id,  c.**
---------------
row1  | k1 , c1  
row2  | k1 , c2  
row3  | k1 , c3  
row4  | k2 , c4  
row5  | k2 , c5  
row6  | k2 , c6  
row7  | k3 , c7  
row8  | k3 , c8  
row9  | k3 , c9  
row10 | k4 , c10   
row11 | k4 , c11  
row12 | k4 , c12 

Ensuite, j'utilise SparkContext.textFile pour lire le fichier en tant que rdd et ainsi. Pour autant que je sache, chaque spark nœud de travail lira la partie du fichier. Donc maintenant, disons que chaque nœud stockera:

  • nœud 1: ligne 1 ~ 4
  • nœud 2: ligne 5 ~ 8
  • nœud 3: ligne 9 ~ 12

Ma question est que disons que je veux faire le calcul sur ces données, et il y a une étape que je dois regrouper la clé, donc la paire valeur-clé serait [k1 [{k1 c1} {k1 c2} {k1 c3}]].. Et ainsi de suite.

Il existe une fonction appelée groupByKey() qui est très coûteuse à utiliser, et aggregateByKey() est recommandé d'utiliser. Je me demande donc comment fonctionne groupByKey() et aggregateByKey() sous le capot? Quelqu'un peut-il utiliser l'exemple que j'ai fourni ci-dessus pour expliquer s'il vous plaît? Après avoir mélangé où résident les lignes sur chaque nœud?

40
EdwinGuo

aggregateByKey() est presque identique à reduceByKey() (les deux appelant combineByKey() en arrière-plan), sauf que vous donnez une valeur de départ pour aggregateByKey(). La plupart des gens connaissent reduceByKey(), je vais donc l'utiliser dans l'explication.

La raison pour laquelle reduceByKey() est tellement meilleure est qu'elle utilise une fonction MapReduce appelée un combinateur. N'importe quelle fonction comme + Ou * Peut être utilisée de cette façon car l'ordre des éléments sur lesquels elle est appelée n'a pas d'importance. Cela permet à Spark de commencer à "réduire" les valeurs avec la même clé même si elles ne sont pas encore toutes dans la même partition.

D'un autre côté, groupByKey() vous offre plus de polyvalence puisque vous écrivez une fonction qui prend un Iterable, ce qui signifie que vous pouvez même extraire tous les éléments dans un tableau. Cependant, il est inefficace car pour que cela fonctionne, l'ensemble complet de paires (K,V,) Doit être dans une partition.

L'étape qui déplace les données lors d'une opération de type réduit est généralement appelée shuffle, au niveau le plus simple, les données sont partitionnées sur chaque nœud (souvent avec un partitionneur de hachage), puis triées sur chaque nœud.

50
aaronman

gregByKey () est assez différent de ReduceByKey. Ce qui se passe, c'est que ReduceByKey est en quelque sorte un cas particulier d'agrégatByKey.

gregByKey () combinera les valeurs pour une clé particulière, et le résultat d'une telle combinaison peut être n'importe quel objet que vous spécifiez. Vous devez spécifier comment les valeurs sont combinées ("ajoutées") à l'intérieur d'une partition (qui est exécutée dans le même nœud) et comment vous combinez le résultat de différentes partitions (qui peuvent être dans différents nœuds). ReduceByKey est un cas particulier, dans le sens où le résultat de la combinaison (par exemple une somme) est du même type que les valeurs, et que l'opération lorsqu'elle est combinée à partir de différentes partitions est également la même que l'opération lors de la combinaison de valeurs à l'intérieur d'un cloison.

Un exemple: imaginez que vous avez une liste de paires. Vous le parallélisez:

val pairs = sc.parallelize(Array(("a", 3), ("a", 1), ("b", 7), ("a", 5)))

Maintenant, vous voulez les "combiner" en produisant une somme. Dans ce cas, ReduceByKey et cumulByKey sont les mêmes:

val resReduce = pairs.reduceByKey(_ + _) //the same operation for everything
resReduce.collect
res3: Array[(String, Int)] = Array((b,7), (a,9))

//0 is initial value, _+_ inside partition, _+_ between partitions
val resAgg = pairs.aggregateByKey(0)(_+_,_+_)
resAgg.collect
res4: Array[(String, Int)] = Array((b,7), (a,9))

Maintenant, imaginez que vous voulez que l'agrégation soit un ensemble de valeurs, c'est-à-dire un type différent de celui des valeurs, qui sont des entiers (la somme des entiers est également des entiers):

import scala.collection.mutable.HashSet
//the initial value is a void Set. Adding an element to a set is the first
//_+_ Join two sets is the  _++_
val sets = pairs.aggregateByKey(new HashSet[Int])(_+_, _++_)
sets.collect
res5: Array[(String, scala.collection.mutable.HashSet[Int])]  =Array((b,Set(7)), (a,Set(1, 5, 3)))
64
Antoni