Je suis un Apache Spark apprenant et je suis tombé sur une action RDD
aggregate
dont je n'ai aucune idée de son fonctionnement. Quelqu'un peut-il épeler et expliquer en détail étape par étape comment sommes-nous arrivés au résultat ci-dessous pour le code ici
RDD input = {1,2,3,3}
RDD Aggregate function :
rdd.aggregate((0, 0))
((x, y) =>
(x._1 + y, x._2 + 1),
(x, y) =>
(x._1 + y._1, x._2 + y._2))
output : {9,4}
Merci
Si vous n'êtes pas sûr de ce qui se passe, il est préférable de suivre les types. En omettant ClassTag
implicite par souci de concision, nous commençons par quelque chose comme ça
abstract class RDD[T] extends Serializable with Logging
def aggregate[U](zeroValue: U)(seqOp: (U, T) ⇒ U, combOp: (U, U) ⇒ U): U
Si vous ignorez tous les paramètres supplémentaires, vous verrez que aggregate
est une fonction qui mappe de RDD[T]
À U
. Cela signifie que le type des valeurs dans l'entrée RDD
ne doit pas nécessairement être le même que le type de la valeur de sortie. C'est donc clairement différent de par exemple reduce
:
def reduce(func: (T, T) ⇒ T): T
ou fold
:
def fold(zeroValue: T)(op: (T, T) => T): T
Comme fold
, aggregate
nécessite un zeroValue
. Comment le choisir? Il doit s'agir d'un élément d'identité (neutre) par rapport à combOp
.
Vous devez également fournir deux fonctions:
seqOp
qui mappe de (U, T)
à U
combOp
qui mappe de (U, U)
à U
Sur la base de ces signatures, vous devriez déjà voir que seul seqOp
peut accéder aux données brutes. Il prend une valeur de type U
une autre de type T
et renvoie une valeur de type U
. Dans votre cas, c'est une fonction avec la signature suivante
((Int, Int), Int) => (Int, Int)
À ce stade, vous pensez probablement qu'il est utilisé pour une sorte d'opération de type pli.
La deuxième fonction prend deux arguments de type U
et retourne une valeur de type U
. Comme indiqué précédemment, il doit être clair qu'il ne touche pas aux données d'origine et ne peut fonctionner que sur les valeurs déjà traitées par le seqOp
. Dans votre cas, cette fonction a une signature comme suit:
((Int, Int), (Int, Int)) => (Int, Int)
Alors, comment pouvons-nous réunir tout cela?
Tout d'abord, chaque partition est agrégée à l'aide de la norme Iterator.aggregate
avec zeroValue
, seqOp
et combOp
passée comme z
, seqop
et combop
respectivement. Puisque InterruptibleIterator
utilisé en interne ne remplace pas aggregate
il doit être exécuté comme un simple foldLeft(zeroValue)(seqOp)
Les résultats partiels suivants collectés à partir de chaque partition sont agrégés à l'aide de combOp
Supposons que l'entrée RDD possède trois partitions avec la distribution de valeurs suivante:
Iterator(1, 2)
Iterator(2, 3)
Iterator()
Vous pouvez vous attendre à ce que l'exécution, en ignorant l'ordre absolu, soit équivalente à quelque chose comme ceci:
val seqOp = (x: (Int, Int), y: Int) => (x._1 + y, x._2 + 1)
val combOp = (x: (Int, Int), y: (Int, Int)) => (x._1 + y._1, x._2 + y._2)
Seq(Iterator(1, 2), Iterator(3, 3), Iterator())
.map(_.foldLeft((0, 0))(seqOp))
.reduce(combOp)
foldLeft
pour une seule partition peut ressembler à ceci:
Iterator(1, 2).foldLeft((0, 0))(seqOp)
Iterator(2).foldLeft((1, 1))(seqOp)
(3, 2)
et sur toutes les partitions
Seq((3,2), (6,2), (0,0))
qui combiné vous donnera le résultat observé:
(3 + 6 + 0, 2 + 2 + 0)
(9, 4)
En général, c'est un modèle courant que vous trouverez partout Spark où vous passez une valeur neutre, une fonction utilisée pour traiter les valeurs par partition et une fonction utilisée pour fusionner des agrégats partiels de différentes partitions. les exemples comprennent:
aggregateByKey
Aggregators
on Spark Datasets
.Voici ma compréhension pour votre référence:
Imaginez que vous avez deux nœuds, l'un prend l'entrée des deux premiers éléments de la liste {1,2} et l'autre prend {3, 3}. (La partition ici est uniquement pour plus de commodité)
Au premier nœud: "(x, y> (x._1 + y, x._2 + 1)) ==", le premier x est (0,0) comme indiqué, et y est votre premier élément 1, et vous aurez la sortie (0 + 1, 0 + 1), puis vient votre deuxième élément y = 2, et la sortie (1 + 2, 1 + 1), qui est (3, 2)
Au deuxième nœud, la même procédure se déroule en parallèle, et vous aurez (6, 2).
"(x, y> (x._1 + y._1, x._2 + y._2)) ==", vous indique de fusionner deux nœuds, et vous obtiendrez (9,4 )
une chose à noter est que (0,0) est en fait ajouté à la longueur du résultat (rdd) +1 fois.
" scala> rdd.aggregate ((1,1)) ((x, y) => (x._1 + y, x._2 + 1), (x, y) = > (x._1 + y._1, x._2 + y._2)) res1: (Int, Int) = (14,9) "