Supposons pour les suivants qu'un seul travail Spark est en cours d'exécution à tout moment.
Voici ce que je comprends de ce qui se passe dans Spark:
SparkContext
est créé, chaque nœud de travail démarre un exécuteur. Les exécuteurs sont des processus distincts (JVM), qui se reconnectent au programme du pilote. Chaque exécuteur a le pot du programme du pilote. Quitter un pilote, ferme les exécuteurs. Chaque exécuteur peut contenir des partitions.Je comprends que
- Une tâche est une commande envoyée par le pilote à un exécuteur en sérialisant l'objet Function.
- L'exécuteur désérialise (avec le pilote jar) la commande (tâche) et l'exécute sur une partition.
mais
Comment diviser la scène en tâches?
Plus précisément:
Dans https://0x0fff.com/spark-architecture-shuffle , la lecture aléatoire est expliquée avec l'image
et j'ai l'impression que la règle est
chaque étape est divisée en # tâches de nombre de partitions, sans tenir compte du nombre de nœuds
Pour ma première image, je dirais que j'aurais 3 tâches de carte et 3 tâches de réduction.
Pour l'image à partir de 0x0fff, je dirais qu'il y a 8 tâches de carte et 3 tâches de réduction (en supposant qu'il n'y ait que trois fichiers orange et trois fichiers vert foncé).
Est-ce exact? Mais même si cela est correct, toutes les questions ci-dessus ne répondent pas à toutes les questions, car il est toujours ouvert, que plusieurs opérations (par exemple plusieurs cartes) relèvent d'une tâche ou soient séparées en une tâche par opération.
Qu'est-ce qu'une tâche dans Spark? Comment le travailleur Spark exécute-t-il le fichier jar? et Comment le planificateur Apache Spark _ scindé tâches? sont similaires, mais je ne pensais pas avoir répondu clairement à ma question.
Vous avez un joli joli contour ici. Pour répondre à tes questions
task
ne distinct doit être lancé pour chaque partition de données pour chaque stage
. Considérez que chaque partition réside probablement dans des emplacements physiques distincts - p. Ex. blocs dans HDFS ou répertoires/volumes pour un système de fichiers local.Notez que la soumission de Stage
s est gérée par le DAG Scheduler
. Cela signifie que les étapes non interdépendantes peuvent être soumises au cluster pour une exécution en parallèle: cela maximise la capacité de parallélisation sur le cluster. Donc, si des opérations dans notre flux de données peuvent se produire simultanément, nous nous attendons à voir plusieurs étapes lancées.
Nous pouvons le voir en action dans l'exemple de jouet suivant dans lequel nous effectuons les types d'opérations suivants:
Alors combien d’étapes finirons-nous?
join
qui est dépendante sur les deux autres étapesVoici ce programme de jouet
val sfi = sc.textFile("/data/blah/input").map{ x => val xi = x.toInt; (xi,xi*xi) }
val sp = sc.parallelize{ (0 until 1000).map{ x => (x,x * x+1) }}
val spj = sfi.join(sp)
val sm = spj.mapPartitions{ iter => iter.map{ case (k,(v1,v2)) => (k, v1+v2) }}
val sf = sm.filter{ case (k,v) => v % 10 == 0 }
sf.saveAsTextFile("/data/blah/out")
Et voici le DAG du résultat
Maintenant: combien de tâches ? Le nombre de tâches doit être égal à
Somme de (Stage
* #Partitions in the stage
)
Cela pourrait vous aider à mieux comprendre différentes pièces:
Si je comprends bien, il y a 2 choses (liées) qui vous déroutent:
1) Qu'est-ce qui détermine le contenu d'une tâche?
2) Qu'est-ce qui détermine le nombre de tâches à exécuter?
Le moteur de Spark "colle" ensemble des opérations simples sur des disques consécutifs, par exemple:
rdd1 = sc.textFile( ... )
rdd2 = rdd1.filter( ... )
rdd3 = rdd2.map( ... )
rdd3RowCount = rdd3.count
ainsi, lorsque rdd3 est calculé (paresseusement), spark générera une tâche par partition de rdd1 et chaque tâche exécutera à la fois le filtre et la carte par ligne pour générer rdd3.
Le nombre de tâches est déterminé par le nombre de partitions. Chaque RDD a un nombre défini de partitions. Pour un RDD source lu à partir de HDFS (à l'aide de sc.textFile (...), par exemple), le nombre de partitions est le nombre de divisions générées par le format d'entrée. Certaines opérations sur un ou plusieurs RDD peuvent aboutir à un RDD avec un nombre de partitions différent:
rdd2 = rdd1.repartition( 1000 ) will result in rdd2 having 1000 partitions ( regardless of how many partitions rdd1 had ).
Un autre exemple est joint:
rdd3 = rdd1.join( rdd2 , numPartitions = 1000 ) will result in rdd3 having 1000 partitions ( regardless of partitions number of rdd1 and rdd2 ).
(La plupart) des opérations qui changent le nombre de partitions impliquent un remaniement, quand on fait par exemple:
rdd2 = rdd1.repartition( 1000 )
ce qui se passe réellement, c’est que la tâche sur chaque partition de rdd1 doit produire une sortie qui peut être lue à l’étape suivante de manière à ce que rdd2 ait exactement 1000 partitions (comment elles le font? Hash ou - Trier ). Les tâches de ce côté sont parfois appelées "tâches de carte (côté)". Une tâche qui sera exécutée ultérieurement sur rdd2 agira sur une partition (de rdd2!) Et devra trouver comment lire/combiner les sorties côté carte correspondant à cette partition. Les tâches de ce côté sont parfois appelées "tâches réduites".
Les 2 questions sont liées: le nombre de tâches dans une étape est le nombre de partitions (communes aux disques consécutifs "collés") et le nombre de partitions d’un RDD peut changer entre les étapes (en spécifiant le nombre de partitions mélange aléatoire provoquant une opération par exemple).
Une fois que l'exécution d'une étape commence, ses tâches peuvent occuper des emplacements de tâches. Le nombre d'emplacements de tâches simultanés est numExecutors * ExecutorCores. En général, ceux-ci peuvent être occupés par des tâches provenant de différentes étapes non dépendantes.