Distribuited / Mobile Code Run time System



Mobile Code System Architecture

Reti di Calcolatori L.S. 2005/2006

Pierfrancesco Felicioni 171856

Abstract

Ultimamente nella programmazione distribuita è sempre più utilizzato il concetto di computazione mobile (Mobile Computation) e Codice Mobile (Mobile Code).

Usualmente ci si riferisce al codice mobile (weak mobility) come a del software in grado di viaggiare su una rete eterogenea, attraversando domini di protezione e che può essere eseguito automaticamente all’arrivo a destinazione.

Invece per Mobile Computation (strong mobility) si intende una computazione che inizia su qualche nodo lungo una rete, e che può continuare la sua esecuzione su qualche altro nodo. La Mobile Computation richiede mobilità di codice, di controllo, di dati e di link.

I vantaggi del codice mobile sono:

1. Efficienza: quando sono necessarie ripetute iterazioni con un sito remoto, può essere efficiente spedire il processo direttamente sul sito remoto in modo che vi interagisca localmente. Questo soprattutto quando la latenza della rete è alta e l’interazione consiste in piccoli messaggi.

2. Mancanza di dati: un client potrebbe non avere le risorse (i dati) necessarie per portare a termine l’operazione. Invece di richiederle al server, invia direttamente l’operazione da eseguire e riceve la risposta.

3. Load Balancing: permette di migrare un task dopo una prima esecuzione in modo da bilanciare il carico a run time (nel caso di strong mobility) .

Introduzione

L’applicazione è stata progettata utilizzando il linguaggio di programmazione Java. I motivi che hanno portato a questa scelta derivano dalle questioni principali che riguardano il codice mobile:

1. Code on Demand

2. Codice portabile

3. Marshalling / Unmarshalling dei dati trasmessi (“serialization”)

4. Registrazione dei server e invocazione

5. Garbage collection

6. Macchine eterogenee

1. Java permette la mobilità di codice (Code on Demand) via Applet / Class Loaders, in questo modo il server può scaricare direttamente dal client il codice da eseguire, al momento del bisogno.

2. Il codice è portabile perché semi compilato ed interpretato dalla JVM.

3. La serializzazione permette il marshalling / unmarshalling dei dati trasmessi, nel nostro caso le classi che vogliamo siano eseguite sul server devono implementare l’interfaccia “Serializable”.

4. La registrazione dei server avviene mediante una infrastruttura creata appositamente che sfrutta il supporto RMI di Java.

5. Il Garbage collection viene gestito direttamente da Java RMI, gli oggetti remoti non più referenziati dal client vengono automaticamente cancellati.

6. L’interprete di Java permette l’esecuzione su macchine eterogenee.

Un semplice esempio di implementazione di una weak code mobility

In questo caso è stata realizzata una infrastruttura che supporta l’esecuzione di un task generico su un server e restituisce il risultato al client.

Un esempio molto semplice è mostrato in figura 1, dove un unico server si mette in attesa di task da eseguire così che il client può sfruttare i vantaggi di macchine specializzate e ad alte performance.

[pic]

Figura 1

Il task è definito da una semplice interfaccia:

[pic]

Quando viene invocato il metodo run, il server calcola il risultato e restituisce la risposta al client. Questa interfaccia è molto generica in modo tale che ogni task computazionale possa essere implementato sotto tale interfaccia.

Un task computazionale “MyTask” inviato dal client al server, dovrà implementare l’interfaccia ITask e l’interfaccia Seralizable:

[pic]

L’interfaccia remota del server ”IServer” dovrà dichiarare il metodo compute:

[pic]

Questa interfaccia permette al client tramite il metodo compute di inviare un task da eseguire al server e di ricevere una risposta.

Il “Server” dovrà implementare l’interfaccia “IServer”:

[pic]

Il metodo compute è molto semplice, infatti all’interno del metodo viene eseguito il task e restituito il risultato al client.

Architetture possibili:

1. Client/Server: tipica architettura in cui un client dialoga direttamente con un entità centrale il server, quest’ ultimo si occupa di eseguire le varie operazioni e restituire il risultato al client. Un problema di questo modello consiste nella debolezza della struttura centralizzata.

Possono essere implementati meccanismi di recovery efficienti che consentano di superare il collo di bottiglia rappresentato dal master, mediante replicazione (slaves).

2. Decentralizzata: in cui non esiste un entità centrale, le entità possono svolgere compiti diversi o lo stesso compito. Servono in questo caso meccanismi più complessi di coordinamento tra i vari server.

L’architettura che si è deciso di implementare è la prima per i motivi sopra elencati.

Il sistema permette di creare gruppi formati da un master e zero o più slave interconnessi. Ogni gruppo gestisce la faul- tollerance e il recovery, implementando il modello a copie attive.

Modalità di funzionamento del sistema

Un client inizialmente chiede al master di un gruppo di registrarsi. La registrazione in questo caso è fondamentale, infatti il master deve sapere a quale client restituire la risposta (in call back) associata ad una richiesta fatta precedentemente.

Una volta registrato, il client riceve un id univoco, mediante il quale viene identificato dal master ad ogni richiesta. Il client può invocare il server in due modalità: sincrona e asincrona, vediamole in dettaglio. La modalità sincrona permette al client di ricevere le risposte nella stessa sequenza con cui sono state inviate le richieste al master.

Nella modalità asincrona il client oltre ad inviare ad ogni chiamata il proprio identificativo, deve anche inviare un id associato alla richiesta, poiché la sequenzializzazione delle risposte non è garantita. Nella modalità sincrona il client deve bloccarsi in attesa della risposta da parte del server. Riportiamo il procedimento di registrazione in figura 2. Mentre in figura 3 sono rappresentati i messaggi scambiati in modalità sincrona e asincrona tra il client e il master del gruppo sul quale il client si è registrato.

[pic]Figura 2 (Registrazione)

[pic]Figura 3[1]

Fault - Tolerance

Per ovviare ad un crash del master è stato adottato il modello a copie attive. Sia il master che gli slave si controllano a vicenda ed eseguono le stesse operazioni. Sono possibili i seguenti casi:

1. Caduta di uno slave durante una richiesta da parte di un client:

Nel normale funzionamento il master dopo aver ricevuto un task da parte di un client registrato, lo invia a tutti gli slave a lui connessi. Il master prima di inviare una risposta al client verifica che tutti gli slave abbiano eseguito e restituito il risultato del task.

Se il numero di risposte relative ad una richiesta è inferiore al numero di slave, il master verifica che non ci sia qualche slave caduto, se si, lo deregistra e lo notifica agli altri slave. Infine invia la risposta al client, cancellando la richiesta. Un problema che potrebbe verificarsi in questo caso, è che uno slave risponda al master e poi cada. Il master non sapendo a quale slave corrisponda una risposta, ed avendo già un numero di risposte sufficienti, risponderebbe al client senza attendere la risposta di alcuni slave. Anche se questo inconveniente si verifica, il client riceverebbe un' unica risposta. Infatti il master prima di inviare una risposta al client, controlla che la richiesta sia attiva, se si, restituisce il risultato al client, eliminando la richiesta.

2. Caduta del master durante una richiesta da parte di un client:

Il master all’arrivo di un nuovo slave, provvede a notificare ai server registrati presso di lui che si è aggiunto un componente al gruppo. Ogni server che ha ricevuto la notifica, avverte il nuovo slave della propria presenza. Questo meccanismo fa sì che gli slave di un gruppo si conoscano a vicenda.

Alla ricezione di una nuova richiesta, ogni slave controlla che il master sia ancora vivo, se il master è caduto, gli slave si coordinano per fornire una risposta. In particolare lo slave con porta più bassa invia il risultato al client, e diventa il nuovo master.

In figura 4 è stato rappresentato questo caso.

[pic]

Figura 4 (caso 2)

3. Caduta del master (idle):

Il master potrebbe cadere anche nel caso in cui sia idle. Per gestire questa situazione gli slave controllano periodicamente il proprio master, se è caduto, lo slave con porta più bassa diventa il nuovo master.

4. Gestione risposte incoerenti:

Prima di fornire una risposta al client per una determinata richiesta, il master deve decidere quale risultato fornire: il suo o quello ricevuto dagli slave. Nel caso in cui le risposte date dagli slave siano incoerenti il risultato da restituire viene scelto in modo probabilistico: viene scelta la risposta data dal maggior numero di server facenti parte del gruppo. Nel caso in cui i responsi sono tutti diversi fra loro, il risultato della risposta inviata al client sarà nullo.

Nella modalità sincrona il caso 2 è stato gestito in modo tale che se cade il master, il client riceva una eccezione.

Efficienza

L’efficienza è stata ottenuta mediante un bilanciamento del carico, per implementarla erano possibili due soluzioni:

1. Modello decentrato in cui i vari gruppi di master/slave si conoscono a vicenda. In questo caso ogni gruppo, periodicamente deve controllare la presenza di altri gruppi ed aggiornare la sua lista. Inoltre ad ogni modifica dei componenti di un gruppo, è necessario inviare a tutti i gruppi conosciuti la lista aggiornata. Questa soluzione comporta un alto traffico di rete, con tutte le possibili conseguenze.

2. Modello centralizzato in cui ogni master di un determinato gruppo deve registrarsi presso un Proxy (entità centrale), che mantiene una lista sempre aggiornata dei gruppi presenti. In questo caso il collo di bottiglia è rappresentato dal Proxy. Il pro è costituito dal fatto che il numero di messaggi scambiati per aggiornare la lista dei gruppi è inferiore.

[pic]

Figura 5

Tra le due si è scelto di implementare la seconda soluzione per i motivi sopra citati. Il load balancing è implementato sia sul Master che sul proxy.

➢ Proxy Load Balancing

Ogni client per registrarsi ad un gruppo passa attraverso un proxy, quest’ultimo decide su quale gruppo dirigere il client. La decisione viene fatta in basa a due parametri:

• Numero di richieste che sta gestendo un gruppo

• Numero di client connessi al gruppo

Il primo parametro considerato è il numero di richieste, se sono uguali per tutti i gruppi (cosa che si verifica raramente), il proxy sceglie in base al gruppo che ha un minor numero di client connessi.

Questo meccanismo di load balancing non è efficace a posteriori. Consideriamo il caso in cui tutti i gruppi siano idle (stesso numero di richieste pari a zero), in questo caso il proxy sceglierebbe per ogni nuovo client un gruppo diverso (basandosi sul numero di client connessi ad un gruppo), cercando di bilanciare a priori il carico. Supponiamo ora che in una fase successiva i client connessi ad un gruppo inizino ad inviare un numero esorbitante di richieste, mentre sull’altro gruppo questa situazione non si presenti. In questo caso il bilanciamento del carico non avrebbe esordito l’effetto voluto.

➢ Master Load Balancing

Per ovviare alla situazione descritta in precedenza ogni master è dotato di un meccanismo di load balancing interno. In pratica se il numero di richieste da gestire supera una certa soglia, inizia a chiedere al proxy di poter smistare le richieste su un altro gruppo.

Il proxy a questo punto controlla che ci sia un altro gruppo che abbia un numero di richieste inferiori, se si, chiede al master del gruppo scelto di registrare il client relativo a quella richiesta e di gestirla. In caso contrario finché il master continua ad essere sovraccarico (soglia al limite) rifiuta ulteriori registrazioni di nuovi client, continuando a gestire le richieste dei client già registrati. E’ possibile osservare i messaggi scambiati in Figura 5.

[pic]

Figura 5

Master Rielection

Il proxy controlla ad intervalli regolari, che il master di ogni gruppo, sia ancora presente.

Nel caso in cui un master non risponde più, rielegge un nuovo master, e lo notifica agli slave del gruppo. Ogni master è anche controllato dagli stessi slave, se il proxy cade insieme al master, gli slave si coordinano autonomamente ed eleggono un nuovo master. Nella figura 6 è stato rappresentato il caso peggiore, in cui sia un master di un gruppo che il proxy cadano contemporaneamente.

Master Rielection: [pic]

Figura 6[2]

Trasparenza del servizio

Il client conosce solo l’indirizzo del proxy, sarà quest’ultimo ad indirizzarlo verso il master più opportuno. Nel caso in cui il master cada, lo slave che diventa il nuovo master si occupa di aggiornare i riferimenti dei client che erano già connessi. Inoltre la consegna delle risposte da parte del nuovo master, viene effettuata comunque, mediante call back.

Implementazione del sistema

La struttura del sistema a supporto della weak code mobility è stata implementata secondo l’esempio di cui si è parlato in precedenza. Vediamo ora in dettaglio le interfacce più significative:

➢ ITask

[pic]

Ogni task generico inviato dal client al server deve implementare l’interfaccia ITask. Il metodo run viene invocato dal server all’arrivo di un nuovo Task. Il metodo toString invece permette al master di confrontare fra loro i risultati restituiti dagli slave.

➢ IMasterSlave (IServer)

[pic]

Il metodo compute è utilizzato dal client per eseguire una operazione asincrona sul server e ricevere il risultato in call back. Invece il metodo computeSync permette di eseguire operazioni (Task) sul server in modo sincrono. Entrambi i metodi hanno tre parametri: il primo ITask rappresenta un oggetto (definito sul client) che implementa l’interfaccia ITask, il secondo id, è l’identificativo univoco del client restituito dal proxy, mentre il terzo permette al client di identificare a quale richiesta (fatta precedentemente sul server) appartiene una determinata risposta restituita dal server[3].

➢ IClient

[pic]

Anche il client deve implementare una interfaccia remota (IClient). Il metodo newResult viene invocato dal master per restituire al client il risultato (in call back) di una richiesta fatta precedentemente. Mentre il metodo newServer viene invocato dal master per assegnare al client un riferimento del server su cui è stato registrato. Inoltre il metodo newServer può essere invocato in altri casi:

1. Da uno slave in seguito alla caduta del Master di un gruppo.

2. Dal master di un gruppo diverso da quello assegnato inizialmente al client, nel caso in cui il master del gruppo iniziale chieda al proxy di redirigere le richieste su un altro gruppo.

➢ IDemonProxy

[pic]

Il proxy implementa questa interfaccia remota. Il client per registrarsi su un master invoca il metodo connectClientToMaster, passando un riferimento alla sua interfaccia, il suo id restituito in precedenza dal proxy (tramite il metodo getId), il suo ip e la sua porta. Il metodo registerServer viene invocato da un server per chiedere al proxy di registrarsi. Nel caso in cui è l’unico componente del gruppo viene eletto dal proxy come master. Il metodo AmIMaster viene invocato da un server per conoscere la propria identità all’interno del gruppo (Master/Slave). Il metodo redirectRequest permette ad un master sovraccarico di redirigere un client su un altro gruppo, restituisce false nel caso in cui il proxy non sia riuscito nell’intento[4].

Conclusioni e sviluppi futuri

L’applicazione realizzata ha risposto in modo adeguato ai test a cui è stata sottoposta, rispettando l’affidabilità e la robustezza per cui era stata progettata.

L’implementazione del sistema si è rivelata piuttosto complessa, soprattutto nella fase di gestione dei protocolli di recovery. La struttura centralizzata soffre di alcuni problemi tipici di questa architettura: nel nostro caso il collo di bottiglia è rappresentato dal proxy.

Per quanto riguarda gli sviluppi futuri, dovrebbe essere prevista la fault tolerance lato proxy mediante replicazione .

Introdurre l’autenticazione degli utenti fondamentale nel caso di mobilità di codice, per questioni di sicurezza.

Migliorare l’interfaccia grafica lato client, in modo da renderla più completa.

Introdurre il supporto alla strong mobility.

-----------------------

[1] Blu: richieste in modalità asincrona la cui risposta viene restituita in call back

Rosso: richieste in modalità sincrona

[2] Rosso: i segnali che il proxy avrebbe inviato agli slave per notificargli chi è il nuovo master.

Blu: La richiesta che un client registrato stava inoltrando al master.

Verde: Il segnale che è inviato dal nuovo master (tramite rielezione decentralizzata) nel caso in cui il proxy non sia più raggiungibile.

[3] Per un maggiore approfondimento è possibile consultare la documentazione javadoc sul codice fornita con il progetto.

[4] I motivi sono molti: non esistono altri gruppi, non esiste un master meno carico di quello che ha fatto la richiesta, il client è gia stato rediretto in precedenza.

................
................

In order to avoid copyright disputes, this page is only a partial summary.

Google Online Preview   Download