Come vengono suddivise le fasi in attività in Spark?


143

Supponiamo per quanto segue che sia in esecuzione un solo processo Spark in ogni momento.

Quello che ottengo finora

Ecco cosa capisco cosa succede in Spark:

  1. Quando SparkContextviene creato un, ogni nodo di lavoro avvia un esecutore. Gli esecutori sono processi separati (JVM), che si ricollegano al programma del driver. Ogni esecutore ha il barattolo del programma del driver. Abbandonare un driver, spegne gli esecutori. Ogni esecutore può contenere alcune partizioni.
  2. Quando viene eseguito un lavoro, viene creato un piano di esecuzione in base al grafico della discendenza.
  3. Il lavoro di esecuzione è suddiviso in fasi, in cui le fasi contengono altrettante trasformazioni e azioni vicine (nel grafico del lignaggio), ma nessuna mescolanza. Quindi le fasi sono separate da shuffle.

immagine 1

lo capisco

  • Un'attività è un comando inviato dal driver a un esecutore serializzando l'oggetto Function.
  • L'esecutore deserializza (con il barattolo del driver) il comando (attività) e lo esegue su una partizione.

ma

Domande)

Come divido il palcoscenico in quei compiti?

In particolare:

  1. I compiti sono determinati dalle trasformazioni e dalle azioni o possono esserci più trasformazioni / azioni in un compito?
  2. Le attività sono determinate dalla partizione (ad es. Un'attività per fase per partizione).
  3. Le attività sono determinate dai nodi (ad es. Un'attività per fase per nodo)?

Cosa penso (solo una risposta parziale, anche se giusta)

In https://0x0fff.com/spark-architecture-shuffle , lo shuffle è spiegato con l'immagine

inserisci qui la descrizione dell'immagine

e ho l'impressione che la regola sia

ogni fase è suddivisa in # numero di partizioni, senza tenere conto del numero di nodi

Per la mia prima immagine direi che avrei 3 attività sulla mappa e 3 attività di riduzione.

Per l'immagine da 0x0fff, direi che ci sono 8 attività sulla mappa e 3 attività di riduzione (supponendo che ci siano solo tre file arancione e tre file verde scuro).

Domande aperte in ogni caso

È corretto? Ma anche se ciò è corretto, le mie domande precedenti non hanno tutte una risposta, perché è ancora aperta, se più operazioni (ad esempio più mappe) sono all'interno di un'attività o sono separate in una attività per operazione.

Quello che dicono gli altri

Che cos'è un'attività in Spark? In che modo il lavoratore Spark esegue il file jar? e In che modo lo schedulatore Apache Spark suddivide i file in attività? sono simili, ma non pensavo che la mia domanda avesse una risposta chiara lì.

Risposte:


52

Hai un bel contorno qui. Per rispondere alle tue domande

  • Una distinta task fa bisogno di essere lanciato per ogni partizione di dati per ciascuna stage. Considerare che ogni partizione risiederà probabilmente in posizioni fisiche distinte, ad esempio blocchi in HDFS o directory / volumi per un file system locale.

Si noti che la presentazione di Stages è guidata dal DAG Scheduler. Ciò significa che le fasi che non sono interdipendenti possono essere inviate al cluster per l'esecuzione in parallelo: ciò massimizza la capacità di parallelizzazione sul cluster. Quindi, se le operazioni nel nostro flusso di dati possono avvenire contemporaneamente, ci aspettiamo di vedere più fasi avviate.

Possiamo vederlo in azione nel seguente esempio di giocattolo in cui eseguiamo i seguenti tipi di operazioni:

  • carica due origini dati
  • eseguire alcune operazioni sulla mappa su entrambe le origini dati separatamente
  • Unisciti a loro
  • eseguire alcune operazioni di mappa e filtro sul risultato
  • salva il risultato

Quindi, con quante tappe finiremo?

  • 1 fase ciascuno per il caricamento in parallelo delle due origini dati = 2 fasi
  • Una terza fase che rappresenta joinciò dipende dalle altre due fasi
  • Nota: tutte le operazioni successive che lavorano sui dati uniti possono essere eseguite nella stessa fase perché devono avvenire in sequenza. Non vi è alcun vantaggio nell'avvio di fasi aggiuntive poiché non possono iniziare a lavorare fino al completamento dell'operazione precedente.

Ecco quel programma di giocattoli

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")

Ed ecco il DAG del risultato

inserisci qui la descrizione dell'immagine

Ora: quante attività ? Il numero di compiti dovrebbe essere uguale a

Somma di ( Stage* #Partitions in the stage)


2
Grazie! Per favore, elabora la tua risposta riguardo al mio testo: 1) La mia definizione degli stadi non è completa? Sembra che mi sia mancato il requisito che uno stage non può contenere operazioni che potrebbero essere in parallelo. O la mia descrizione lo implica già rigorosamente? 2) Il numero di attività che devono essere eseguite per il lavoro è determinato dal numero di partizioni, ma non dal numero di processori o nodi, mentre il numero di attività che possono essere eseguite contemporaneamente dipende dal numero di processori, giusto? 3) Un'attività può contenere più operazioni?
Make42,

1
4) Cosa intendevi con la tua ultima frase? Dopotutto, le partizioni numeriche possono variare da uno stadio all'altro. Intendevi che è così che hai configurato il tuo lavoro per tutte le fasi?
Make42,

@ Make42 Naturalmente il numero di partizioni può variare da uno stadio all'altro: hai ragione. Era mia intenzione dire sum(..)di tener conto di quella variazione.
javadba,

wow, la tua risposta è stata del tutto ok ma sfortunatamente, l'ultima frase è sicuramente un concetto sbagliato. Ciò non significa che i numeri di partizione in uno stadio siano uguali al numero di processori, tuttavia, è possibile impostare il numero di partizioni per un RDD in base al numero di core presentati sulla macchina.
epcpu,

@epcpu È stato un caso speciale, ma sono d'accordo che sarebbe fuorviante, quindi lo sto rimuovendo.
javadba,

26

Questo potrebbe aiutarti a capire meglio diversi pezzi:

  • Fase: è una raccolta di attività. Stesso processo eseguito su diversi sottoinsiemi di dati (partizioni).
  • Attività: rappresenta un'unità di lavoro su una partizione di un set di dati distribuito. Quindi in ogni fase, numero di attività = numero di partizioni, o come hai detto "un'attività per fase per partizione".
  • Ogni esecutore gira su un contenitore di filo e ogni contenitore risiede su un nodo.
  • Ogni fase utilizza più esecutori, a ciascun esecutore sono assegnati più vcore.
  • Ogni vcore può eseguire esattamente un'attività alla volta
  • Pertanto, in qualsiasi fase, è possibile eseguire più attività in parallelo. numero di attività in esecuzione = numero di vcores in uso.

2
Questa è una lettura davvero utile su spark architecture: 0x0fff.com/spark-architecture
pedram bashiri,

Non ho ottenuto il tuo numero di punto 3. Per quanto ne so, ogni nodo può avere più esecutori, quindi secondo il punto 3: dovrebbe esserci un solo esecutore per nodo. Puoi chiarire questo punto?
Rituparno Behera,

@RituparnoBehera ogni nodo può avere contenitori multipli e quindi più esecutori Spark. Dai un'occhiata a questo link. docs.cloudera.com/runtime/7.0.2/running-spark-applications/…
pedram bashiri

15

Se capisco correttamente ci sono 2 cose (correlate) che ti confondono:

1) Cosa determina il contenuto di un'attività?

2) Cosa determina il numero di attività da eseguire?

Il motore Spark "incolla" insieme semplici operazioni su rdds consecutivi, ad esempio:

rdd1 = sc.textFile( ... )
rdd2 = rdd1.filter( ... )
rdd3 = rdd2.map( ... )
rdd3RowCount = rdd3.count

quindi quando rdd3 viene (pigramente) calcolato, spark genererà un'attività per partizione di rdd1 e ogni attività eseguirà sia il filtro che la mappa per riga per ottenere rdd3.

Il numero di attività è determinato dal numero di partizioni. Ogni RDD ha un numero definito di partizioni. Per un RDD di origine che viene letto da HDFS (usando sc.textFile (...) ad esempio) il numero di partizioni è il numero di divisioni generate dal formato di input. Alcune operazioni su RDD (s) possono comportare un RDD con un diverso numero di partizioni:

rdd2 = rdd1.repartition( 1000 ) will result in rdd2 having 1000 partitions ( regardless of how many partitions rdd1 had ).

Un altro esempio sono i join:

rdd3 = rdd1.join( rdd2  , numPartitions = 1000 ) will result in rdd3 having 1000 partitions ( regardless of partitions number of rdd1 and rdd2 ).

Le operazioni (la maggior parte) che modificano il numero di partizioni comportano uno shuffle, ad esempio quando facciamo:

rdd2 = rdd1.repartition( 1000 ) 

ciò che realmente accade è che l'attività su ciascuna partizione di rdd1 deve produrre un output finale che può essere letto dallo stadio seguente in modo che rdd2 abbia esattamente 1000 partizioni (Come fanno? Hash o Sort ). Le attività su questo lato sono talvolta denominate "Attività mappa (lato)". Un'attività che verrà eseguita successivamente su rdd2 agirà su una partizione (di rdd2!) E dovrebbe capire come leggere / combinare gli output sul lato della mappa relativi a quella partizione. Le attività su questo lato vengono talvolta definite "Attività di riduzione (laterali)".

Le 2 domande sono correlate: il numero di compiti in uno stage è il numero di partizioni (comune ai rdds consecutivi "incollati" insieme) e il numero di partizioni di un rdd può cambiare tra gli stadi (specificando il numero di partizioni per alcuni shuffle causando l'operazione per esempio).

Una volta iniziata l'esecuzione di uno stage, i suoi compiti possono occupare gli slot degli incarichi. Il numero di slot per attività simultanee è numExecutors * ExecutorCores. In generale, questi possono essere occupati da compiti provenienti da diverse fasi non dipendenti.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.