MokaByte
Numero 35 - Novembre 99
|
|||
|
Fabio Cesari e Massimo Campeggi per il corso di Reti di Calcolatori (A.A. 98/99) tenuto dal prof. Antonio Corradi presso la facoltà di ingegneria informatica dell'Università di Bologna. |
del software |
|
|
|
||
DFG è una applicazione distribuita per la creazione di immagini dell'insieme di Mandelbrot. La generazione delle immagini avviene sfruttando le risorse di calcolo di varie workstation, secondo lo schema seguente:
I controlli sulla correttezza dei dati inseriti in una richiesta vengono effettuati dal client (e vengono quindi trascurati sia dal server che dagli slaves). Dopo l'invio di una richiesta di servizio, il client rimane in attesa di una risposta in modo sincrono. Viene però data la possibilità all'utente di interrompere l'operazione in corso in qualunque momento. Prima dell'invio di una richiesta di un calcolo e all'atto della ricezione di una risposta (anche parziale) o al verificarsi di un errore, viene disabilitata la funzione di invio di richieste di interruzione. Dopo l'invio di una richiesta, non è possibile spedirne un'altra fino alla ricezione di un risultato (anche parziale) o al verificarsi di un errore. Nel caso l'utente voglia interrompere il calcolo, viene spedito a DFGServer un messaggio di tipo Messaggio_Interruzione. Non si è ritenuto oppurtuno introdurre un time-out ma si è scelto di lasciare all'utente la possibilità di interrompere il calcolo di un'immagine per due motivi:
la corretta scelta di un time-out è difficile e potrebbe contrastare con i desideri dell'utente. Nella versione definitiva di DFGClient, si è modificata la struttura del codice in modo da poterlo eseguire sia come applet che come applicazione. Quando è usato come applicazione, è necessario specificare l'host di DFGServer sulla linea di comando: java dfg.dfg_client host_DFGServer A causa delle restrizioni imposte dai security manager utilizzati dalle varie versioni di browser e appletviewer provate, a volte l'applet non è in grado di determinare il nome dell'host su cui sta eseguendo. Per questo motivo, si utilizza un text field nel quale l'utente può inserire il nome dell'host locale (ovviamente questo campo viene riempito automaticamente quando DFGClient è utilizzato come applicazione). D'altra parte, questa informazione è utilizzata solo per costruire l'identificatore delle richieste inviate e ha il solo scopo di permetterne il riconoscimento per una eventuale interruzione. Per questo motivo, si può inserire una qualsiasi stringa che, assieme al numero di porta utilizzato per inviare le richieste, costituisca un identificatore unico nel sistema. L'applet è a conoscenza dell'host su cui opera DFGServer in quanto questo coincide con l'host da cui l'applet stesso è stato prelevato. Nel caso l'applet venga caricato da hard disk anzichè tramite un server web (questo accade sulle macchine lia, dove non è installato un server web), questa informazione è però sconosciuta all'applet e va quindi inserita dall'utente ; inoltre, in questa situazione il security manager dell'applet permette solo di collegarsi a localhost, quindi l'applet deve essere in esecuzione sullo stesso nodo sul quale è presente DFGServer. Ovviamente, durante il normale funzionamento (cioè quando l'applet viene scaricato da un server web oppure DFGClient è utilizzato come applicazione), il text field viene riempito automaticamente. L'applet è a conoscenza del numero di porta sul quale DFGServer è in attesa di richieste di servizio (si è scelta la porta 20000). Per poter suddividere il calcolo di una immagine tra gli slaves, DFGServer deve mantenere una struttura dati (chiamata Tabella) in cui memorizzare gli slaves attualmente disponibili, la loro collocazione (nome dell'host e numero di porta) e la loro situazione di carico. Vedere la sezione Bilanciamento del carico per le specifiche relative alle modalità di scelta degli slaves da utilizzare. DFGServer deve essere in grado di servire vari tipi di richieste. Ogni tipo di richiesta corrisponde ad un diverso tipo di Messaggio:
Messaggio_Interruzione: Viene utilizzato dai DFGClient per richiedere l'interruzione del calcolo di una immagine. Il servizio di questo tipo di richiesta viene svolto creando un oggetto GestoreInterruzione che permette di ritrovare gli slaves interessati dall'operazione e inviare loro una richiesta di interruzione. Messaggio_Registrazione: Viene utilizzato dai DFGSlave per registrarsi nella Tabella. Messaggio_Verifica: Viene utilizzato dai DFGSlave per verificare periodicamente la presenza di DFGServer. Bisogna innanzitutto osservare che ogni messaggio di questo tipo contiene un elemento (ident) che lo identifica univocamente e il cui formato è "host_DFGClient : porta_DFGClient". Questa informazione è utilizzata solo per permettere il riconoscimento delle richieste di servizio per una loro eventuale interruzione. Se non ci sono slaves disponibili, DFGServer comunica al client che il servizio non è attivo (chiudendo la connessione con esso). In caso contrario, si procede come segue: Per prima cosa viene creato un oggetto GestoreRichiesta, che viene utilizzato per scandire la Tabella, crearne una copia contenente l'indicazione del numero di righe che ciascuno slave deve calcolare, allocare un'area di memoria per l'immagine, aggiornare la Tabella con la nuova situazione di carico e creare, per ogni slave, un thread Esecutore che si occupi di inviargli la richiesta e ricevere la corrispondente risposta. I thread Esecutore vengono assegnati ad un gruppo di thread il cui nome identifica univocamente il servizio che stanno svolgendo (viene usato ident come nome per il gruppo) e viene dato loro un nome che permetta, in caso di necessità (ved. la gestione di una richiesta di interruzione), di ritrovare lo slave che devono contattare (il formato del loro nome è: "host_DFGSlave : porta_DFGSlave"). La figura seguente mostra un possibile schema dei gruppi di thread e dei thread in essi contenuti durante l'esecuzione di DFGServer:
Nel caso la richiesta vada a buon fine, ogni thread Esecutore ottiene la porzione di immagine che gli era stata commissionata e la memorizza nella posizione corretta dell'area di memoria appositamente predisposta dall'oggetto GestoreRichiesta che lo ha generato. Quando tutti i thread Esecutore hanno terminato il proprio compito, l'immagine ottenuta viene trasmessa al client. Nota: il fatto di non realizzare GestoreRichiesta come thread (come si era fatto nelle versioni preliminari di DFG) ma solamente come oggetto di cui vengono invocati dei metodi introduce una serializzazione delle operazioni svolte da DFGServer, il quale non si mette in attesa di nuovi messaggi prima di aver creato tutti i thread Esecutore e aggiornato la Tabella. Questo permette di risolvere a priori possibili situazioni di corsa critica. Questa soluzione non provoca problemi di ritardo nel servizio, visto che le operazioni svolte tramite GestoreRichiesta sono tutte operazioni locali. Per lo stesso motivo, anche i thread GestoreInterruzione sono stati modificati nello stesso modo. Operazioni
svolte da DFGServer alla ricezione di un messaggio di tipo Messaggio_Interruzione:
Il procedimento
di interruzione di un calcolo può essere realizzato in modi diversi
da quello scelto (ad esempio riducendosi semplicemente a far terminare
i thread Esecutore e GestoreMandelbrot coinvolti) . I pregi del metodo
scelto sono la semplicità dei protocolli di comunicazione e la considerazione
che la ricezione di un risultato (anche se parziale) del calcolo da parte
di DFGClient è importante.
In caso di ricezione di un Messaggio_Interruzione relativo ad un job non presente, non si fa nulla. Questa situazione può verificarsi se il calcolo dell'immagine termina prima che il messaggio di interruzione abbia il tempo di arrivare a DFGServer ed essere eseguito. Se non si trova il gruppo di thread da interrompere (può accadere se durante la spedizione e il trattamento del comando di interruzione il calcolo termina), si prosegue l'esecuzione senza altre conseguenze. Se l'aggiornamento della Tabella al termine di un calcolo è in ritardo rispetto alla ripartizione di un nuovo lavoro, questo viene suddiviso tra gli slaves utilizzando una situazione di carico inesatta. Questo non può però essere considerato un errore perchè, anche se il carico effettivo degli slaves non è quello indicato in Tabella, il lavoro che stavano svolgendo non è ancora terminato del tutto: manca l'aggiornamento della Tabella, che deve essere considerata come l'ultima parte del lavoro. E' possibile, in teoria, che una richiesta di interruzione arrivi agli slaves interessati prima della corrispondente richiesta di calcolo, risultando quindi inefficace. Questo può accadere se i thread Esecutore sono molto più lenti a contattare gli slaves dei corrispondenti thread EsecutoreInterruzione (nonostante questi siano stati creati sicuramente dopo di essi). Anche per prevenire questa remota possibilità si è previsto di poter inviare più messaggi di interruzione durante il calcolo (vedere anche la sezione successiva). DFGSlave:
L'atto di registrazione presso DFGServer avviene spedendo un Messaggio_Registrazione contenente le informazioni necessarie ad inizializzare una riga della Tabella. Ad intervalli di tempo prefissati, DFGSlave si accerta che DFGServer sia attivo spedendogli un Messaggio_Verifica. Ogni DFGSlave presente nel sistema deve essere a conoscenza dell'host su cui opera DFGServer e del numero di porta su cui esso è in attesa di richieste.
Messaggio_Interruzione: Viene utilizzato da DFGServer per richiedere l'interruzione del calcolo relativo ad una particolare immagine. Alla ricezione di una richiesta di questo tipo, DFGSlave crea un thread GestoreMandelbrot che si occupa di servirla; il thread estrae dalla richiesta i parametri, alloca un'area di memoria per l'immagine risultante e chiama la funzione nativa per il calcolo dell'immagine. Al thread viene assegnato come nome l'identificatore (ident) contenuto nel messaggio stesso (nota: il formato di ident non ha importanza, basta che permetta di identificare univocamente il lavoro che sta svolgendo). Questo è necessario per poter servire una eventuale richiesta di terminazione del calcolo proveniente da DFGServer. Il motivo della scelta di avere slaves concorrenti è determinato da due fattori:
se gli slaves fossero sequenziali, nel caso DFGServer ricevesse un numero di richieste di servizio superiore agli slaves disponibili, dovrebbe attendere che almeno uno degli slaves si liberasse prima di poter iniziare a servire tutte le richieste. L'implementazione parallela degli slaves permette invece di poter sempre iniziare il servizio di una nuova richiesta (=>possibile migliore throughput). DFGSlave estrae dal messaggio il parametro ident e scandisce la lista dei thread contenuti nel gruppo main, uccidendo tutti i thread aventi per nome ident (è possibile che ci sia più di un thread chiamato ident se ci sono state ridistribuzioni di carico a seguito della caduta di uno o più slaves). Prima di terminare, ciascuno dei thread interessati spedisce il risultato parziale del calcolo a DFGServer. In questo modo, DFGClient riceve un'immagine incompleta ma che può ugualmente essere utile. Nota: l'invio da parte di DFGClient di una richiesta di interruzione può (in casi molto particolari) aumentare il tempo di generazione di un'immagine. Ad esempio, se il messaggio di interruzione arriva a DFGSlave quando il calcolo è già stato completato ma prima (o durante) la spedizione a DFGServer del risultato, si interrompe la connessione tra DFGSlave e DFGServer e quindi si provoca una ridistribuzione di carico (e i nuovi slaves interessati non verranno interrotti). Per questo motivo si è prevista la possibilità di poter inviare più di una richiesta di interruzione da parte di DFGClient (anche se in pratica queste situazioni non si sono mai verificate). Linea di comando: java dfg.dfg_slave porta_slave host_server carico_di_default
Problemi di bilanciamento
del carico:
Tab.1
Nota: l'implementazione parallela degli slaves rende inutile l'esecuzione di più di una istanza di DFGSlave per ogni host e permette di identificare ogni DFGSlave con l'host su cui opera. Nel sistema realizzato, comunque, viene utilizzata la coppia nomehost : numeroporta per individuare uno slave. Definizione
del parametro "carico":
numero massimo di iterazioni da utilizzare Il parametro "carico" è definito come la somma del costo delle richieste servite da un determinato slave in un certo istante. Al numero ottenuto viene anche sommato un valore di default comunicato dallo slave all'atto della sua registrazione nella Tabella. Quest'ultimo valore deve essere specificato manualmente all'atto della esecuzione dello slave e serve a tenere conto del tipo di macchina su cui lo slave sta eseguendo e delle risorse che sono disponibili su di essa. Non è previsto che questo "handicap" possa essere modificato durante l'esecuzione dello slave. La scelta di questo valore (peraltro opzionale) deve essere effettuata sulla base della conoscenza delle macchine su cui verranno eseguiti gli slaves. Esempio (ved.
tab.1):
Algoritmo per la suddivisione del lavoro tra gli slaves: Definizione
del problema:
Considerazioni
preliminari:
Si sceglie di utilizzare sempre tutti gli slaves disponibili, evitando di suddividere tra più slaves il servizio di richieste con un costo inferiore a 100000 (piccole richieste possono verificarsi nel caso in cui uno slave fallisca e si debba ripartire il suo lavoro tra gli altri). L'algoritmo ideale
dovrebbe far sì che, dopo l'assegnazione del numero di righe da
calcolare ai vari slaves, il carico di ciascuno slave fosse il medesimo.
Algoritmo:
N_RIGHE = parte_intera( ( 1 - Cj / CARICO_TOTALE ) * H / ( N -1 ) ) Ovviamente uno
slave non viene utilizzato se, a causa dell'arrotondamento, si ha per esso
N_RIGHE == 0.
Esempio:
Tab. 2
Dopo la assegnazione, la Tabella viene aggiornata e la nuova situazione è indicata nella tab.3: Tab. 3
DFGClient:
Nel caso di eccezioni durante la spedizione di un Messaggio_Interruzione, si chiude la connessione con DFGServer, si segnala l'errore e si continua l'esecuzione (non bisogna chiudere la connessione su cui DFGClient è in attesa dell'immagine, perchè questa operazione viene svolta dal punto precedente).
In caso di impossibilità nello stabilire la connessione con uno slave o in caso di caduta di uno slave durante il servizio, il thread Esecutore corrispondente ritorna un messaggio di errore che provoca la cancellazione dello slave dalla Tabella e la ridistribuzione del lavoro tra gli altri slaves. Nel caso in cui lo slave cancellato sia l'unico slave presente, si deve comunicare al client che il servizio non è attualmente disponibile. Attenzione: la scelta di cancellare lo slave dalla Tabella non sempre è corretta: se la caduta della connessione con DFGSlave non è dovuta al fatto che lo slave è guasto ma solo ad un errore occasionale, la cancellazione dallo slave dalla Tabella è un'operazione concettualmente sbagliata. D'altra parte, il thread Esecutore non può sapere se lo slave è ancora in grado di svolgere il suo compito e la caduta della connessione con esso lo deve indurre a ritenere che non sia più "affidabile", quindi a cancellarlo dalla Tabella. Una cancellazione errata può portare ad anomalie nel funzionamento del sistema, ma solo se lo slave, al momento della sua cancellazione, sta calcolando altre immagini e se il completamento di tali immagini avviene dopo la spedizione del successivo Messaggio_Verifica a DFGServer. In tal caso, infatti, lo slave viene reinserito in Tabella col suo carico di default, che non corrisponde al suo carico effettivo. Per risolvere questo problema, sarebbe sufficiente far sì che i messaggi di verifica contenessero il reale valore di carico e non il carico di default. In caso di caduta del client o della connessione con esso, DFGServer non può accorgersi di nulla se non quando tenta di spedirgli l'immagine richiesta. Di conseguenza, per evitare uno spreco di risorse, nel caso l'utente voglia interrompere il calcolo di un'immagine non deve limitarsi a far terminare il client, ma deve premere il bottone "stop". Se un EsecutoreInterruzione non riesce a mettersi in contatto con lo slave per il quale è stato creato (ad esempio perchè nel frattempo lo slave è caduto) o se rileva eccezioni durante la spedizione del Messaggio_Interruzione, termina senza altre conseguenze. In caso di ricezione di un Messaggio_Registrazione da uno slave già presente in Tabella (dovuto ad esempio al fatto che lo slave è caduto mentre era inattivo ed è stato successivamente ripristinato), la sua Entry viene sostituita con quella contenuta nel messaggio. In questo modo è possibile modificare il carico di default di uno slave. Dopo che tutti i thread Esecutore sono terminati, bisogna eliminare il gruppo di thread che li conteneva (utilizzando il metodo setDaemon(true)). Questo è necessario per due motivi:
se un client spedisce, in tempi diversi, richieste di immagini utilizzando lo stesso numero di porta (questo può accadere in caso di caduta e successivo ripristino dell'host su cui esso risiede), viene creato un secondo gruppo di thread con lo stesso nome e non si riesce a trattare correttamente un'eventuale richiesta di interruzione.
In caso di caduta della connessione con DFGServer, lo slave si limita a rilevare l'eccezione e a liberare le risorse allocate a quella connessione. In caso di ricezione di un Messaggio_Interruzione relativo ad un GestoreMandelbrot non presente, non si fa nulla. Questa situazione può verificarsi se il calcolo dell'immagine termina prima che il messaggio di interruzione abbia il tempo di arrivare a DFGSlave e di essere eseguito. In caso di fallimento
della procedura di registrazione, si continua ad effettuare periodicamente
la richiesta di registrazione, in attesa che DFGServer venga ripristinato.
In caso di fallimento della procedura di verifica della presenza di DFGServer, si interrompono tutti i thread GestoreMandelbrot attivi e si iniziano ad effettuare periodicamente richieste di registrazione, in attesa che DFGServer venga ripristinato. |
|
||
|
||
MokaByte ricerca
nuovi collaboratori
|
||
|