Differences between revisions 1 and 2
Revision 1 as of 2015-01-09 12:17:30
Size: 24700
Editor: lukisi
Comment:
Revision 2 as of 2015-01-14 11:36:06
Size: 25665
Editor: lukisi
Comment:
Deletions are marked like this. Additions are marked like this.
Line 34: Line 34:
Fondamentale è la funzione H~-,,t,,-~. Definiamo la funzione H~-,,t,,-~(x) come l'indirizzo x associato ad un nodo esistente (x ∈ dom(α~-,,t,,-~)) che minimizza la distanza x - x’, in modo più rigoroso minarg,,x∈dom(α,,~-,,t,,-~,,),,dist(x,x’). La funzione ''dist'' rappresenta in modo intuitivo la distanza tra due indirizzi, ma è definita in modo che la funzione H~-,,t,,-~ "cerchi" il primo indirizzo valido "procedendo verso destra" fino al gsize per poi ripartire da 0. Questo comportamento ci ritornerà utile in seguito. Precisamente la funzione ''dist(x,y)'' si calcola così: Fondamentale è la funzione H~-,,t,,-~. Definiamo la funzione H~-,,t,,-~(x̄) come l'indirizzo x associato ad un nodo esistente (x ∈ dom(α~-,,t,,-~)) che minimizza la distanza x̄ - x, in modo più rigoroso minarg,,x∈dom(α,,~-,,t,,-~,,),,dist(x̄,x). La funzione ''dist'' rappresenta in modo intuitivo la distanza tra due indirizzi, ma è definita in modo che la funzione H~-,,t,,-~ "cerchi" il primo indirizzo valido "procedendo verso destra" fino al gsize per poi ripartire da 0. Questo comportamento ci ritornerà utile in seguito. Precisamente la funzione ''dist(x̄,x)'' si calcola così:

 * x̄ è formato da x̄~-,,0,,-~·x̄~-,,1,,-~·...·x̄~-,,l-1,,-~.
Line 37: Line 38:
 * y è formato da y~-,,0,,-~·y~-,,1,,-~·...·y~-,,l-1,,-~.
Line 40: Line 40:
  * se x~-,,j,,-~ == y~-,,j,,-~:   * se x̄~-,,j,,-~ == x~-,,j,,-~:
Line 42: Line 42:
  * altrimenti se y~-,,j,,-~ > x~-,,j,,-~:
   * distanza += y~-,,j,,-~ - x~-,,j,,-~;
  * altrimenti se x~-,,j,,-~ > x̄~-,,j,,-~:
   * distanza += x~-,,j,,-~ - x̄~-,,j,,-~;
Line 45: Line 45:
   * distanza += y~-,,j,,-~ - x~-,,j,,-~ + gsize[j];    * distanza += x~-,,j,,-~ - x̄~-,,j,,-~ + gsize[j];
Line 52: Line 52:
Sia ''n'' un nodo che vuole inviare un messaggio ''m'' all'hash-node della chiave ''k'' per il servizio ''p''. Il nodo ''n'' usa la funzione ''h''~-,,p,,-~ definita da ''p'' per calcolare dalla chiave ''k'' l'indirizzo ''x''. A questo punto dovrebbe calcolare ''x''=H~-,,t,,-~(x). Procede così: Sia ''n'' un nodo che vuole inviare un messaggio ''m'' all'hash-node della chiave ''k'' per il servizio ''p''. Il nodo ''n'' usa la funzione ''h''~-,,p,,-~ definita da ''p'' per calcolare dalla chiave ''k'' l'indirizzo ''x̄''. A questo punto dovrebbe calcolare ''x''=H~-,,t,,-~(x̄). Procede così:
Line 61: Line 61:
 * x è formato da x~-,,0,,-~·x~-,,1,,-~·...·x~-,,l-1,,-~.
 * Il nodo n calcola H~-,,t,,-~(x) secondo le sue conoscenze, cioè trova un livello ''j'' e un identificativo ''y''~-,,j,,-~ tali che:
  * y~-,,j,,-~ ∈ n~-,,j+1,,-~, oppure j == l-1.
  * Il gnodo y~-,,j,,-~ è quello, fra le conoscenze di n, che minimizza la funzione dist(x, y~-,,j,,-~).
  * y~-,,j,,-~ ≠ n~-,,j,,-~.
  * se j == 0 allora n ha trovato con le sue sole conoscenze y~-,,0,,-~ = H~-,,t,,-~(x).
  * come caso estremo n potrebbe trovare che esso stesso è il definitivo H~-,,t,,-~(x). In questo caso l'algoritmo termina e si passa subito all'esecuzione del messaggio m.
  * Se j > 0 allora il nodo n non conosce l'interno del gnodo y~-,,j,,-~. Ma la sua esistenza implica che nella rete esistono uno o più nodi al suo interno e tra questi senz'altro quello che ha l'indirizzo y che minimizza la funzione dist(x,y).
 * Il nodo n e il nodo y (il destinatario del messaggio m) hanno in comune il g-nodo n~-,,j+1,,-~. Tutto il percorso che il messaggio deve fare è all'interno di questo g-nodo; quindi ogni singolo nodo intermedio che riceve il messaggio non necessita, per inoltrarlo, di identificativi a livelli maggiori di j.
 * Il nodo n prepara un messaggio ''m’'' da inoltrare al gnodo y~-,,j,,-~. Questo messaggio contiene:
  * ''n'': la tupla n~-,,0,,-~·n~-,,1,,-~·...·n~-,,j,,-~. ~-Questo dettaglio fa in modo che il coordinatore di un gnodo G sia perfettamente raggiungibile da qualsiasi nodo all'interno di G anche mentre G sta gradualmente migrando.-~
  * ''x'': la tupla x~-,,0,,-~·x~-,,1,,-~·...·x~-,,j-1,,-~.
  * ''lvl, pos'': le coordinate del g-nodo che miriamo a raggiungere, cioè j e y~-,,j,,-~.
 * x̄ è formato da x̄~-,,0,,-~·x̄~-,,1,,-~·...·x̄~-,,l-1,,-~.
 * Il nodo n calcola H~-,,t,,-~(x̄) secondo le sue conoscenze, cioè trova un livello ''j'' e un identificativo ''x''~-,,j,,-~ tali che:
  * x~-,,j,,-~ ∈ n~-,,j+1,,-~, oppure j == l-1.
  * Il gnodo x~-,,j,,-~ è quello, fra le conoscenze di n, che minimizza la funzione dist(x̄, x~-,,j,,-~).
  * x~-,,j,,-~ ≠ n~-,,j,,-~.
  * se j == 0 allora n ha trovato con le sue sole conoscenze x~-,,0,,-~ = H~-,,t,,-~(x̄).
  * come caso estremo n potrebbe trovare che esso stesso è il definitivo H~-,,t,,-~(x̄). In questo caso l'algoritmo termina e si passa subito all'esecuzione del messaggio m.
  * Se j > 0 allora il nodo n non conosce l'interno del gnodo x~-,,j,,-~. Ma la sua esistenza implica che nella rete esistono uno o più nodi al suo interno e tra questi senz'altro quello che ha l'indirizzo x che minimizza la funzione dist(x̄,x).
 * Il nodo n e il nodo x (il destinatario del messaggio m) hanno in comune il g-nodo n~-,,j+1,,-~. Tutto il percorso che il messaggio deve fare è all'interno di questo g-nodo; quindi ogni singolo nodo intermedio che riceve il messaggio non necessita, per inoltrarlo, di identificativi a livelli maggiori di j.
 * Il nodo n prepara un messaggio ''m’'' da inoltrare al gnodo x~-,,j,,-~. Questo messaggio contiene:
  * ''n'': la tupla n~-,,0,,-~·n~-,,1,,-~·...·n~-,,j,,-~. ~-Il fatto che la tupla contiene solo gli identificativi inferiori al g-nodo j+1 fa in modo che il coordinatore di un gnodo G sia perfettamente raggiungibile da qualsiasi nodo all'interno di G anche mentre G sta gradualmente migrando.-~
  * ''x̄'': la tupla x̄~-,,0,,-~·x̄~-,,1,,-~·...·x̄~-,,j-1,,-~.
  * ''lvl, pos'': le coordinate del g-nodo che miriamo a raggiungere, cioè j e x~-,,j,,-~.
Line 76: Line 76:
 * Il nodo n invia il messaggio m’ al suo miglior gateway verso il gnodo y~-,,j,,-~. Questo invio viene fatto in modo asincrono con protocollo reliable (TCP).
 * Il nodo n tiene a mente per un certo periodo l'id del messaggio in attesa di una risposta. Se passa un certo tempo si considera fallito l'invio del messaggio m all'hash-node della chiave k. Cioè è lanciata una eccezione al metodo p2p.
 * Il nodo n invia il messaggio m’ al suo miglior gateway verso il gnodo x~-,,j,,-~. Questo invio viene fatto con protocollo reliable (TCP) senza ricevere una risposta e senza attendere la sua processazione: diciamo in questo senso che il messaggio è inviato in modo asincrono.
 * Il nodo n tiene a mente per un certo periodo l'id del messaggio in attesa di una risposta. Se passa un certo tempo si considera fallito l'invio del messaggio m all'hash-node della chiave k. Più sotto viene descritto cosa dovrà fare n in questo caso.
Line 88: Line 88:
 * Il nodo v calcola H~-,,t,,-~(m’.x) secondo le sue conoscenze relative al suo g-nodo di livello m’.lvl, cioè trova un livello ''k'' e un identificativo ''y''~-,,k,,-~. Sicuramente k < m’.lvl.
 * Se il nodo v trova che esso stesso è H~-,,t,,-~(m’.x) allora il messaggio è giunto alla destinazione finale (vedi sotto il proseguimento dell'algoritmo).
 * Il nodo v calcola H~-,,t,,-~(m’.x̄) secondo le sue conoscenze relative al suo g-nodo di livello m’.lvl, cioè trova un livello ''k'' e un identificativo ''x''~-,,k,,-~. Sicuramente k < m’.lvl.
 * Se il nodo v trova che esso stesso è H~-,,t,,-~(m’.x̄) allora il messaggio è giunto alla destinazione finale (vedi sotto il proseguimento dell'algoritmo).
Line 92: Line 92:
  * pos diventa ''y''~-,,k,,-~.
  * x diventa x~-,,0,,-~·x~-,,1,,-~·...·x~-,,k-1,,-~.
  * pos diventa ''x''~-,,k,,-~.
  * x̄ diventa x̄~-,,0,,-~·x̄~-,,1,,-~·...·x̄~-,,k-1,,-~.
Line 102: Line 102:
  * Altrimenti n comunica a v la chiave k.
  * v verifica di essere esso stesso, in base alle sue conoscenze, il risultato di H~-,,t,,-~ ( h~-,,p,,-~ ( k ) ). Se non è così, v lo comunica a n e chiude la comunicazione. n sarà libero di riprovare da capo.
  * Altrimenti v conferma a n di essere il destinatario corretto; n comunica a v l'intero messaggio m.
  * Altrimenti n comunica a v il messaggio m.
Line 109: Line 107:
Siano ''n'' ed ''m'' due nodi che conoscono una certa chiave ''k'' per un servizio ''p''. Entrambi sono in grado di calcolare ''x'' = H~-,,t,,-~ ( h~-,,p,,-~ ( k ) ) e di contattare x. Sia ''j'' il livello massimo tale che x ∉ n~-,,j,,-~, x ∉ m~-,,j,,-~. Cioè l'interno del g-nodo x~-,,j,,-~ di livello j a cui appartiene il nodo x è sconosciuto per n e per m. Supponiamo ora che per qualche motivo i messaggi instradati dal modulo !PeerServices si perdano all'interno del g-nodo x~-,,ε,,-~ (con ε piccolo a piacere), ε < j. Siano ''n'' ed ''m'' due nodi che conoscono una certa chiave ''k'' per un servizio ''p''. Entrambi sono in grado di calcolare ''x'' = H~-,,t,,-~ ( h~-,,p,,-~ ( k ) ) e di contattare x. Sia ''j_n'' il livello del g-nodo comune a n ed x: cioè x ∈ n~-,,j_n,,-~, x ∉ n~-,,j_n-1,,-~. Sia ''j_m'' il livello del g-nodo comune a m ed x: cioè x ∈ m~-,,j_m,,-~, x ∉ m~-,,j_m-1,,-~. Sia ''j'' il minore tra j_n e j_m: cioè x ∉ n~-,,j-1,,-~, x ∉ m~-,,j-1,,-~. Cioè l'interno del g-nodo x~-,,j-1,,-~ di livello j-1 a cui appartiene il nodo x è sconosciuto per n e per m. Supponiamo ora che per qualche motivo i messaggi instradati dal modulo !PeerServices si perdano all'interno del g-nodo x~-,,ε,,-~ (con ε piccolo a piacere), ε < j-1.
Line 113: Line 111:
Dopo che n vede fallire il suo tentativo di contattare x per salvare un record con chiave k, n può cercare di isolare il g-nodo malfunzionante; cioè n calcola ''x''’ = H~-,,t,,-~ ( h~-,,p,,-~ ( k ), exclude_list=[x] ) TODO Dopo che n vede fallire il suo tentativo di contattare x per salvare un record con chiave k, n deve cercare di isolare il g-nodo malfunzionante. Se lo facesse basandosi solo sulle sue conoscenze potrebbe solo calcolare ''x''’ = H~-,,t,,-~ ( h~-,,p,,-~ ( k ), exclude_list=[x~-,,j_n-1,,-~] ). Ora il nodo m cerca di contattare x per leggere il record con chiave k, vede che il suo tentativo di contattare x fallisce e quindi prova ad isolare il g-nodo malfunzionante. Basandosi solo sulle sue conoscenze calcolerebbe ''x''’’ = H~-,,t,,-~ ( h~-,,p,,-~ ( k ), exclude_list=[x~-,,j_m-1,,-~] ). La conseguenza è che se j_m ≠ j_n allora n ed m contattano nodi diversi.

Questo significa che un meccanismo robusto deve prevedere che il nodo n che cerca di contattare il nodo x ∈ n~-,,j_n,,-~ e non vi riesce deve rilevare tutti gli identificativi del g-nodo x~-,,ε,,-~ (dal livello j_n-1 al livello ε) in cui il messaggio si è perso. Sarà cura di n, prima di effettuare ulteriori tentativi, di memorizzare questa tupla nel formato adeguato per evitare di giungere nuovamente allo stesso g-nodo. Ad esempio il nodo n sa, a seconda del servizio, se deve memorizzare solo la tupla interna al g-nodo in cui è avvenuta la ricerca (n~-,,j_n,,-~) oppure aggiungere i suoi identificativi fino ad un certo livello (ad esempio se stava contattando il Coordinator di un certo g-nodo) oppure fino all'ultimo livello.

==== Modifiche agli algoritmi ====
Modifichiamo l'algoritmo di instradamento considerando che si deve comunicare al nodo originante del messaggio m’ quali g-nodi sono stati raggiunti correttamente.

Modifichiamo inoltre l'algoritmo di calcolo di H~-,,t,,-~ considerando che deve essere possibile escludere un set di determinati g-nodi. Questi elementi da escludere possono essere, a seconda del momento come vedremo subito, espressi sotto forma di tuple globali (da l-1 a ε) oppure di tuple interne ad un g-nodo a cui il nodo appartiene.

Sia ''e_list'' il set di g-nodi da escludere. In questo set ogni g-nodo è espresso sotto forma di tupla globale. Il nodo n istanzia questo set inizialmente vuoto quando inizia il suo tentativo di contattare l'hash-node x̄.

Per prima cosa il nodo n sceglie dal set e_list quei g-nodi che sono nella sua mappa, cioè quelli che hanno il g-nodo direttamente superiore in comune con il nodo stesso, e li mette nel set ''my_e_list''. Il nodo n calcola H~-,,t,,-~(x̄, exclude_list=my_e_list) come prima, aggiungendo il vincolo di esclusione di quei g-nodi. Trova un livello ''j'' e un identificativo ''x''~-,,j,,-~ ∈ n~-,,j+1,,-~. Ora il nodo n sceglie dal set e_list quei g-nodi che sono interni al g-nodo x~-,,j,,-~ (il g-nodo x~-,,j,,-~ sicuramente non era nel set e_list) e li aggiunge al messaggio m’ come membro m’.e_list; in questa lista ogni g-nodo è espresso sotto forma di tupla interna a x~-,,j,,-~ (da j-1 a ε) dove j è il livello del g-nodo obiettivo.

Come avveniva in precedenza, n invia m’ al miglior gateway e questo lo inoltra fino a raggiungere un nodo v dentro il g-nodo obiettivo. Quando v deve calcolare H~-,,t,,-~(m’.x̄) prima sceglie dal set m’.e_list quei g-nodi che sono nella sua mappa e li mette nel set ''my_e_list''. Poi calcola H~-,,t,,-~(m’.x̄, exclude_list=my_e_list) e può trovarsi in una tra 3 diverse situazioni:

 1. v è la destinazione finale di m’.
 1. v individua un g-nodo interno che diventa il nuovo obiettivo di m’, cioè trova un livello ''k'' e un identificativo ''x''~-,,k,,-~.
 1. v trova che a causa dei vincoli nessun g-nodo al suo interno è più valido come obiettivo per m’.

Nel caso 1 il nodo v si connette via TCP ad n attraverso la tupla m’.n e ne esegue la richiesta, come descritto prima.

Nel caso 2 il nodo v sceglie dal set m’.e_list quei g-nodi che sono interni al g-nodo x~-,,k,,-~ (il g-nodo x~-,,k,,-~ sicuramente non era nel set m’.e_list), li esprime sotto forma di tuple interne a x~-,,k,,-~ (da k-1 a ε) e mette questa lista al posto del vecchio contenuto di m’.e_list; effettua anche le altre modifiche a m’ e lo inoltra verso il suo nuovo obiettivo; subito dopo v si connette via TCP ad n attraverso la tupla m’.n e gli comunica (senza necessitare alcuna risposta) l'identificativo m’.id_msg e la tupla che identifica all'interno di m’.n~-,,j+1,,-~ il g-nodo nuovo obiettivo.

Nel caso 3 il nodo v si connette via TCP ad n attraverso la tupla m’.n e gli comunica (senza necessitare alcuna risposta) il fallimento indicando l'identificativo m’.id_msg e la tupla che identifica all'interno di m’.n~-,,j+1,,-~ il g-nodo di v, cioè la tupla che nei suoi prossimi tentativi n dovrà escludere.

----
Con questi accorgimenti quando il nodo n invia il messaggio m’ al g-nodo x~-,,j,,-~ = H~-,,t,,-~ ( h~-,,p,,-~ ( ''k'' ) ) e si mette in attesa delle risposte possono verificarsi questi casi:

 1. Il nodo n riceve (oltre ad alcuni messaggi intermedi) per tempo una risposta da parte di x. Il messaggio m gli viene consegnato.
 1. Il nodo n riceve un messaggio da parte di un g-nodo x~-,,ε,,-~ che gli comunica che al suo interno non ci sono obiettivi validi.
 1. Il nodo n riceve alcuni messaggi (0 o più) che gli segnalano dei g-nodi x~-,,k,,-~ con k < j come nuove destinazioni di m’ e poi più niente fino allo scadere del tempo massimo. In questo caso viene scelto come valore ε il più piccolo tra i messaggi ricevuti, oppure se sono stati ricevuti 0 messaggi lo stesso valore j.

Nei casi 2 e 3 abbiamo un tentativo fallito e un valore x~-,,ε,,-~ da escludere nelle future ricerche. Il valore x~-,,ε,,-~ è espresso sotto forma di tupla interna a x~-,,j,,-~, quindi il nodo n è in grado di completare la tupla per renderla globale e poi la inserisce nel set e_list. Il nodo n effettuerà quindi altri tentativi escludendo i g-nodi inadatti come è stato descritto sopra.

L'unico errore (eccezione) definitivo ammissibile per una richiesta ad un servizio peer-to-peer sarà quindi il "nessun nodo nella rete partecipa al servizio" in caso di servizi opzionali. Nei servizi obbligatori invece come minimo il nodo n risponde a se stesso.
Line 116: Line 148:
Quando un nodo ''v'' riceve la richiesta di memorizzare (o aggiornare, o rinfrescare) un record con chiave ''k'' nella sua porzione del database distribuito si occupa di replicare questo dato su un numero ''q'' di nodi replica. L'obiettivo è fare sì che se il nodo muore o si sconnette dalla rete, alla prossima richiesta di lettura del dato venga comunque data la risposta corretta. Quindi v deve scegliere i nodi che saranno contattati per la chiave k quando lui non parteciperà più.

=== Scelta dei g-nodi di replica ===
Ovviamente il nodo v verifica di essere il risultato di H~-,,t,,-~ ( h ( k ) ).

Poi v calcola quale sarebbe stato il secondo miglior candidato, in sua assenza. Anche in questo caso quello che può computare il nodo v è un g-nodo ''u'' di livello ''j'', con la funzione H~-,,t,,-~ ( h ( k ), exclude_list=[v] ) ~-^1^-~ . Riguardo al g-nodo u, v sa che al suo interno esiste almeno un nodo ("partecipante") ma non sa quali e quanti.

Quindi v invia al g-nodo u la richiesta di replicare il record in un massimo di q nodi. Vedremo di seguito come viene comunicato questo messaggio da v ad u, come u lo esegue, come u risponde a v indicando quante repliche ha potuto salvare. Per ora assumiamo che v riceve la risposta da parte del g-nodo u, che gli comunica di aver effettuato ''w'' repliche (w <= q) oppure che passato un certo tempo massimo (che dipende dalla dimensione di u, p. es. 2 secondi per il numero di nodi stimati nel g-nodo u) il nodo v consideri il g-nodo u non in grado di soddisfare alcuna replica.

Il nodo v sottrae da q il numero w (oppure 0 se u non ha risposto) e se q > 0 reitera le operazioni partendo dal calcolo di H~-,,t,,-~ ( h ( k ), exclude_list=[v, u] ). Le operazioni si considerano concluse quando q diventa 0 oppure quando H~-,,t,,-~ ( h ( k ), exclude_list) non è in grado di restituire altri g-nodi, vale a dire se nella rete ci sono meno di q partecipanti al servizio.
Quando un nodo ''v'' riceve la richiesta di memorizzare (o aggiornare, o rinfrescare) un record con chiave ''k'' e valore ''val'' nella sua porzione del database distribuito del servizio ''p'' si occupa di replicare questo dato su un numero ''q'' di nodi replica. L'obiettivo è fare sì che se il nodo muore o si sconnette dalla rete, alla prossima richiesta di lettura del dato venga comunque data la risposta corretta. Quindi v deve scegliere i nodi che saranno contattati per la chiave k quando lui non parteciperà più.

Grazie all'introduzione del meccanismo di fault tolerance descritto sopra, scegliere e contattare tali nodi diventa un esercizio banale. Di seguito l'algoritmo:

 * Il nodo v calcola ''x̄'' = h~-,,p,,-~ ( k ).
 * ''lista_repliche'' = []
 * ''lista_esclusioni'' = [v]
 * Mentre lista_repliche.size < q:
  * Calcola e contatta ''x'' = H~-,,t,,-~ ( x̄, exclude_list=lista_esclusioni ) ~-^1^-~ . Il risultato di questa operazione può essere di 3 tipi: 1) viene contattato un nodo x; 2) viene individuato un g-nodo x~-,,j,,-~ dove cercare x, ma il contatto non riesce e viene individuato un g-nodo x~-,,ε,,-~ da escludere dalle prossime ricerche; 3) si rileva che nessun altro nodo può soddisfare la richiesta.
  * caso 1:
   * Il nodo n comunica ad x il messaggio m che consiste in "replica il dato k,val nella tua cache per p"
   * lista_repliche.add(x)
   * lista_esclusioni.add(x)
  * caso 2:
   * lista_esclusioni.add(x~-,,ε,,-~)
  * caso 3:
   * break.
Line 129: Line 168:
 1. Nella funzione che cerca i candidati per le repliche è possibile che la ricerca venga ristretta ad un certo g-nodo; questo è a discrezione del servizio: ad esempio il Coordinator di un g-nodo non sarà mai contattato all'esterno del g-nodo stesso, quindi sarebbe inutile replicare i record di sua pertinenza su nodi esterni.

=== Salvataggio delle repliche ===
Vediamo come fa il nodo generico ''v'' a richiedere il salvataggio di max. ''q'' repliche di un record ''r'' del servizio ''pid'' all'interno del g-nodo ''u''.

Sia l'indice ''j'' tale che il g-nodo v~-,,j+1,,-~ è il minimo g-nodo comune tra v ed u. Similmente a quanto visto prima, la richiesta che viene inviata al g-nodo u fa un percorso interno a v~-,,j+1,,-~ ed è costituita da un messaggio ''m''’ che contiene la tupla ''v'': v~-,,0,,-~·v~-,,1,,-~·...·v~-,,j,,-~, le coordinate ''lvl'' e ''pos'' del g-nodo che si vuole raggiungere, un identificativo ''id_msg'' generato per il messaggio. Quando il messaggio viene ricevuto da un nodo ''u0'' appartenente ad u questo realizza una connessione TCP con il nodo v tramite percorso interno al g-nodo v~-,,j+1,,-~.

Una volta realizzata la connessione TCP tra v e u0 il dialogo consiste in:

 * u0 comunica a v l'identificativo m’.id_msg;
 * se v aveva rinunciato ad attendere la risposta a questo messaggio lo comunica a u0 e qui si interrompe.
 * v comunica a u0:
  * il record ''r'' da memorizzare,
  * il numero ''q'' di repliche richieste,
  * il ''pid'' del servizio,
  * la tupla ''x'': x~-,,0,,-~·x~-,,1,,-~·...·x~-,,j-1,,-~,
 * u0 esegue le operazioni descritte di seguito per memorizzare fino a q repliche; poi risponde a v con il numero di repliche che è riuscito a mettere all'interno del g-nodo u. La connessione TCP viene chiusa.

Per memorizzare le repliche dentro il g-nodo u, u0 procede così:

 * exclude_list = []
 * mentre q > 0:
  * Dalla tupla m.x calcola H~-,,t,,-~ (x, exclude_list). Ottiene un g-nodo ''z''.
  * Se z è lo stesso u0:
   * esegue un salvataggio nel nodo stesso.
   * decrementa q di 1.
   * aggiunge u0 a exclude_list.
  * Altrimenti:
   * Si applica la ricorsione di questo stesso algoritmo: ''u0'' richiede il salvataggio di max. ''q'' repliche del record ''r'' del servizio ''pid'' all'interno del g-nodo ''z''. La risposta ottenuta è il numero di salvataggi effettuati ''s''.
   * decrementa q di s.
   * aggiunge z a exclude_list.
 1. Con la dicitura "contatta ''x'' = H~-,,t,,-~ ( x̄, exclude_list=lista_esclusioni )" si intende la sequenza di operazioni descritte negli algoritmi modificati con il meccanismo di fault tolerance. Quindi in realtà il nodo v quando esegue la funzione H~-,,t,,-~ mette nella exclude_list solo una parte degli elementi presenti in lista_esclusioni, cioè i g-nodi visibili nella sua mappa; poi a seconda di quale g-nodo x~-,,j,,-~ è stato eletto il nodo v prende un sottoinsieme degli altri elementi presenti in lista_esclusioni e li inserisce nel messaggio m’ con cui cerca di contattare x.
Line 164: Line 173:
Nell'analisi dell'algoritmo delineato di seguito si tenga conto che queste operazioni non sono strettamente necessarie al funzionamento del servizio. Se queste non vengono effettuate con precisione l'eventuale inconsistenza nel database distribuito sarà comunque temporanea. E' quindi di proposito che si è cercato un trade-off tra la rigorosità del risultato e la leggerezza dell'implementazione. Ad esempio l'inoltro dei messaggi verso i nodi viene svolto dagli hop intermedi senza garantire il successo né l'identificazione del punto esatto in cui si può essere perduto il messaggio.
Line 168: Line 175:
Abbiamo visto che la funzione H~-,,t,,-~ cerca l'indirizzo che minimizza la funzione dist e che la funzione dist introduce una sorta di "direzione a destra" nella ricerca dell'indirizzo. Definiamo ora la funzione ''H_rev''~-,,t,,-~(x) = minarg,,x∈dom(α,,~-,,t,,-~,,),,''dist_rev''(x,x’) dove la funzione dist_rev implementa la "direzione a sinistra" nella ricerca dell'indirizzo. Per farlo basta definire ''dist_rev''(x,x’) = dist(x’,x). Abbiamo visto che la funzione H~-,,t,,-~ cerca l'indirizzo che minimizza la funzione dist e che la funzione dist introduce una sorta di "direzione a destra" nella ricerca dell'indirizzo. Definiamo ora la funzione ''H_rev''~-,,t,,-~(x̄) = minarg,,x∈dom(α,,~-,,t,,-~,,),,''dist_rev''(x̄,x) dove la funzione dist_rev implementa la "direzione a sinistra" nella ricerca dell'indirizzo. Per farlo basta definire ''dist_rev''(x̄,x) = dist(x,x̄). Inoltre va aggiunto al messaggio m’ un campo booleano ''reverse'' che impostato a True indica al nodo ''v'' di proseguire la ricerca usando H_rev.
Line 174: Line 181:
  * exclude_list = [n~-,,j,,-~]
  * dx_done = false
  * Mentre not dx_done:
   * Il nodo n calcola H~-,,t,,-~ ( n, exclude_list ) circoscritto al g-nodo n~-,,j+1,,-~ ed ottiene il g-nodo ''gn_dx'', che potrebbe essere null.
   * Se gn_dx è null:
    * dx_done = true
   * Altrimenti:
    * Il nodo n contatta (di seguito è descritto come questo avviene) il nodo ''n_dx'' appartenente a gn_dx più prossimo a destra rispetto a n~-,,0,,-~·...·n~-,,j-1,,-~. Se j == 0 allora n_dx = gn_dx. Contattatolo gli chiede la sua cache per il servizio p.~-^1^-~
    * Se n non riesce a contattare n_dx:
     * Aggiunge gn_dx a exclude_list.
    * Altrimenti:
     * dx_done = true
     * Aggiunge gn_dx a exclude_list.
     * sx_done = false
     * Mentre not sx_done:
      * Il nodo n calcola H_rev~-,,t,,-~ ( n, exclude_list ) circoscritto al g-nodo n~-,,j+1,,-~ ed ottiene il g-nodo ''gn_sx'', che potrebbe essere null.
      * Se gn_sx è null:
       * sx_done = true
      * Altrimenti:
       * Il nodo n contatta il nodo ''n_sx'' appartenente a gn_sx più prossimo a sinistra rispetto a n~-,,0,,-~·...·n~-,,j-1,,-~. Se j == 0 allora n_sx = gn_sx. Contattatolo gli chiede la sua cache per il servizio p.
       * Se n non riesce a contattare n_sx:
        * Aggiunge gn_sx a exclude_list.
       * Altrimenti:
        * sx_done = true
  * ''lista_esclusioni'' = [n~-,,j,,-~]
  * ''sx_to_be_done'' = True
  * Mentre True:
   * Il nodo n calcola e contatta ''x'' = H~-,,t,,-~ ( n, exclude_list=lista_esclusioni ) circoscritto al g-nodo n~-,,j+1,,-~ ~-^1^-~ . Il risultato di questa operazione può essere di 3 tipi: 1) viene contattato un nodo x; 2) viene individuato un g-nodo x~-,,j,,-~ dove cercare x, ma il contatto non riesce e viene individuato un g-nodo x~-,,ε,,-~ da escludere dalle prossime ricerche; 3) si rileva che nessun altro nodo può soddisfare la richiesta.
   * caso 1:
    * Il nodo n comunica ad x il messaggio m, cioè gli chiede la sua cache per il servizio p.~-^2^-~
    * lista_esclusioni.add(x)
    * break.
   * caso 2:
    * lista_esclusioni.add(x~-,,ε,,-~)
   * caso 3:
    * sx_to_be_done = False
    * break.
  * Se sx_to_be_done:
   * Mentre True:
    * Il nodo n calcola e contatta ''x'' = H_rev~-,,t,,-~ ( n, exclude_list=lista_esclusioni ) circoscritto al g-nodo n~-,,j+1,,-~. Il risultato di questa operazione può essere di 3 tipi: 1) viene contattato un nodo x; 2) viene individuato un g-nodo x~-,,j,,-~ dove cercare x, ma il contatto non riesce e viene individuato un g-nodo x~-,,ε,,-~ da escludere dalle prossime ricerche; 3) si rileva che nessun altro nodo può soddisfare la richiesta.
    * caso 1:
     * Il nodo n comunica ad x il messaggio m, cioè gli chiede la sua cache per il servizio p.
     * break.
    * caso 2:
     * lista_esclusioni.add(x~-,,ε,,-~)
    * caso 3:
     * break.
Line 201: Line 207:
 1. Aggiungere questo requisito all'implementazione della funzione H~-,,t,,-~ è banale: basta escludere dalla ricerca iniziale fatta dal nodo n tutti i g-nodi esterni al g-nodo n~-,,j+1,,-~.
Line 202: Line 209:

Con questo algoritmo il nodo n, dato il g-nodo gn_dx appartenente a n~-,,j+1,,-~, contatta il nodo n_dx:

 * Il nodo n prepara il messaggio ''m’'' da inoltrare al gnodo gn_dx. Questo messaggio contiene:
  * ''n'': la tupla n~-,,0,,-~·n~-,,1,,-~·...·n~-,,j,,-~.
  * ''lvl, pos'': le coordinate del g-nodo gn_dx.
  * ''pid'': l'identificativo del servizio p.
  * ''id_msg'': un identificativo generato a caso per questo messaggio.
 * Il nodo n invia il messaggio m’ al suo miglior gateway verso gn_dx. Questo invio viene fatto in modo asincrono con protocollo reliable (TCP).
 * Il nodo n tiene a mente per un certo periodo l'id del messaggio in attesa di una risposta. Se passa un certo tempo (p. es. 2 secondi per il numero di nodi stimati nel proprio g-nodo di livello j) si considera fallito il tentativo di contattare n_dx.

Il messaggio m’ viene così inoltrato:

 * Il nodo ''v'' riceve un messaggio m’.
 * Il nodo v confronta il proprio indirizzo con le coordinate presenti in m’. Se v.pos[m’.lvl] == m’.pos allora il messaggio è giunto al g-nodo gn_dx (vedi sotto il proseguimento dell'algoritmo).
 * Altrimenti il nodo v inoltra m’ al suo miglior gateway verso il g-nodo (m’.lvl, m’.pos) in modo asincrono con protocollo TCP.

Il messaggio m’ raggiunge un nodo dentro il g-nodo gn_dx.

 * ''j'' = m’.lvl
 * ''n_target'' = m’.n~-,,0,,-~·m’n~-,,1,,-~·...·m’n~-,,j-1,,-~.
 * Il nodo v calcola H~-,,t,,-~ ( n_target ) circoscritto al g-nodo v~-,,j,,-~ ed ottiene il g-nodo di livello ''k'' e identificativo ''y''~-,,k,,-~. Sicuramente k < j.
 * Se k == -1 allora il messaggio è giunto alla destinazione finale (vedi sotto il proseguimento dell'algoritmo).
 * Il nodo v modifica i seguenti membri del messaggio m’:
  * lvl diventa k.
  * pos diventa ''y''~-,,k,,-~.
  * x diventa x~-,,0,,-~·x~-,,1,,-~·...·x~-,,k-1,,-~.
 * Il nodo v inoltra m’ al suo miglior gateway verso il g-nodo (m’.lvl, m’.pos) in modo asincrono con protocollo TCP. L'algoritmo prosegue come detto prima con il prossimo nodo che riceve m’.

Il messaggio m’ raggiunge la destinazione finale:

 * Il nodo v prepara uno stub TCP per connettersi al nodo originante tramite percorso interno (vedi trattazione dei percorsi interni ad un g-nodo nel documento [[Netsukuku/ita/docs/ModuloQSPN/LivelliBits|livelli e bits]]) attraverso la tupla m’.n.
 * Una volta realizzata la connessione TCP tra n e v il dialogo consiste in:
  * v comunica a n m’.id_msg;
  * Se n aveva rinunciato ad attendere la risposta a questo messaggio lo comunica a v e qui si interrompe.
  * Altrimenti n conferma la richiesta.
  * v comunica a n tutta la sua cache. Poi la comunicazione si chiude.
Line 280: Line 250:
Quando le operazioni del modulo richiedono il calcolo di h ( x ) su un certo servizio p, il metodo 'perfect_tuple' viene richiamato sull'istanza di !PeerService, quindi tale metodo deve essere pubblico. Quando le operazioni del modulo richiedono il calcolo di h~-,,p,,-~ ( k ) su un certo servizio p, il metodo 'perfect_tuple' viene richiamato sull'istanza di !PeerService, quindi tale metodo deve essere pubblico.

Modulo PeerServices - Analisi Funzionale

Instrada i messaggi (ricevuti da altri nodi o generati dal nodo stesso) che sono destinati ad un dato hash-node.

Mantiene le informazioni necessarie.

Idea generale

Il funzionamento dei servizi peer-to-peer si basa sulla definizione di una funzione che associa ad una chiave k univocamente un nodo esistente nella rete al quale indirizzare delle richieste. Si può opzionalmente aggiungere il requisito che il nodo sia "partecipante" al servizio.

Sia S lo spazio di indirizzi validi per i nodi della rete.

Sia Vt il set di nodi nella rete al tempo t.

Sia αt : S → Vt la funzione suriettiva che al tempo t associa ad un indirizzo il nodo che lo detiene. E' suriettiva in quanto ogni nodo ha almeno un indirizzo. E' una funzione non completamente definita in S poiché un indirizzo potrebbe non essere stato assegnato ad alcun nodo.

Definiamo una funzione che al tempo t assegni ad ogni indirizzo in S un indirizzo nel dominio di αt. Cioè dato un indirizzo valido che potrebbe non essere stato assegnato tale funzione ritorna un indirizzo assegnato.

  • Ht : S → dom(αt)

DHT: Distributed Hash Table

Sia p un servizio, sia K lo spazio delle chiavi definito da questo servizio. Il servizio p definisce una funzione di hash che mappa lo spazio delle chiavi sullo spazio degli indirizzi.

  • hp : K → S

Quando un nodo, al tempo t, vuole scrivere la coppia chiave-valore (k, v) nel database distribuito il nodo calcola:

  • hash_node(k) = αt ( Ht ( hp ( k ) ) )

Contatta quindi il nodo hash_node(k) e chiede di memorizzare la coppia (k, v).

Analogamente il nodo che vuole reperire il dato associato alla chiave k, calcola hash_node(k) e chiede di leggere il dato associato a k.

Questo procedimento realizza un database distribuito, perché ogni nodo mantiene solo una porzione delle associazioni chiave-valore.

Fondamentale è la funzione Ht. Definiamo la funzione Ht(x̄) come l'indirizzo x associato ad un nodo esistente (x ∈ dom(αt)) che minimizza la distanza x̄ - x, in modo più rigoroso minargx∈dom(αt)dist(x̄,x). La funzione dist rappresenta in modo intuitivo la distanza tra due indirizzi, ma è definita in modo che la funzione Ht "cerchi" il primo indirizzo valido "procedendo verso destra" fino al gsize per poi ripartire da 0. Questo comportamento ci ritornerà utile in seguito. Precisamente la funzione dist(x̄,x) si calcola così:

  • x̄ è formato da x̄0·x̄1·...·x̄l-1.

  • x è formato da x0·x1·...·xl-1.

  • distanza = 0;
  • Per j da l-1 a 0:
    • se x̄j == xj:

      • distanza += 0;
    • altrimenti se xj > x̄j:

      • distanza += xj - x̄j;

    • altrimenti:
      • distanza += xj - x̄j + gsize[j];

    • se j>0:

      • distanza *= gsize[j-1];

HDHT: Hierarchical DHT

In una struttura gerarchica come Netsukuku un nodo non ha la conoscenza di tutti i nodi esistenti nella rete, quindi non può da solo computare la funzione Ht in quanto non conosce per intero dom(αt). L'implementazione avviene in modo distribuito.

Sia n un nodo che vuole inviare un messaggio m all'hash-node della chiave k per il servizio p. Il nodo n usa la funzione hp definita da p per calcolare dalla chiave k l'indirizzo . A questo punto dovrebbe calcolare x=Ht(x̄). Procede così:

  • Il nodo n ha indirizzo n0·n1·...·nl-1. Ha inoltre conoscenza di:

    • tutti i nodi appartenenti a n1,

    • tutti i g-nodi di livello 1 appartenenti a n2,

    • ...
    • tutti i g-nodi di livello l-2 appartenenti a nl-1,

    • tutti i g-nodi di livello l-1.
  • Questa conoscenza la possiamo chiamare domnt), cioè il dominio della funzione αt secondo le conoscenze di n.

  • x̄ è formato da x̄0·x̄1·...·x̄l-1.

  • Il nodo n calcola Ht(x̄) secondo le sue conoscenze, cioè trova un livello j e un identificativo xj tali che:

    • xj ∈ nj+1, oppure j == l-1.

    • Il gnodo xj è quello, fra le conoscenze di n, che minimizza la funzione dist(x̄, xj).

    • xj ≠ nj.

    • se j == 0 allora n ha trovato con le sue sole conoscenze x0 = Ht(x̄).

    • come caso estremo n potrebbe trovare che esso stesso è il definitivo Ht(x̄). In questo caso l'algoritmo termina e si passa subito all'esecuzione del messaggio m.

    • Se j > 0 allora il nodo n non conosce l'interno del gnodo xj. Ma la sua esistenza implica che nella rete esistono uno o più nodi al suo interno e tra questi senz'altro quello che ha l'indirizzo x che minimizza la funzione dist(x̄,x).

  • Il nodo n e il nodo x (il destinatario del messaggio m) hanno in comune il g-nodo nj+1. Tutto il percorso che il messaggio deve fare è all'interno di questo g-nodo; quindi ogni singolo nodo intermedio che riceve il messaggio non necessita, per inoltrarlo, di identificativi a livelli maggiori di j.

  • Il nodo n prepara un messaggio m’ da inoltrare al gnodo xj. Questo messaggio contiene:

    • n: la tupla n0·n1·...·nj. Il fatto che la tupla contiene solo gli identificativi inferiori al g-nodo j+1 fa in modo che il coordinatore di un gnodo G sia perfettamente raggiungibile da qualsiasi nodo all'interno di G anche mentre G sta gradualmente migrando.

    • : la tupla x̄0·x̄1·...·x̄j-1.

    • lvl, pos: le coordinate del g-nodo che miriamo a raggiungere, cioè j e xj.

    • pid: l'identificativo del servizio p.

    • id_msg: un identificativo generato a caso per questo messaggio.

  • Il nodo n invia il messaggio m’ al suo miglior gateway verso il gnodo xj. Questo invio viene fatto con protocollo reliable (TCP) senza ricevere una risposta e senza attendere la sua processazione: diciamo in questo senso che il messaggio è inviato in modo asincrono.

  • Il nodo n tiene a mente per un certo periodo l'id del messaggio in attesa di una risposta. Se passa un certo tempo si considera fallito l'invio del messaggio m all'hash-node della chiave k. Più sotto viene descritto cosa dovrà fare n in questo caso.

Il messaggio m’ viene così inoltrato:

  • Il nodo v riceve un messaggio m’.

  • Il nodo v confronta il proprio indirizzo con le coordinate presenti in m’. Se v.pos[m’.lvl] == m’.pos allora il messaggio è giunto al g-nodo che mirava a raggiungere (vedi sotto il proseguimento dell'algoritmo).
  • Altrimenti il nodo v inoltra m’ al suo miglior gateway verso il g-nodo (m’.lvl, m’.pos) in modo asincrono con protocollo TCP.

Il messaggio m’ raggiunge un nodo dentro il gnodo che mirava a raggiungere.

  • Se m’.lvl == 0 allora il messaggio è giunto alla destinazione finale (vedi sotto il proseguimento dell'algoritmo).
  • Il nodo v calcola Ht(m’.x̄) secondo le sue conoscenze relative al suo g-nodo di livello m’.lvl, cioè trova un livello k e un identificativo xk. Sicuramente k < m’.lvl.

  • Se il nodo v trova che esso stesso è Ht(m’.x̄) allora il messaggio è giunto alla destinazione finale (vedi sotto il proseguimento dell'algoritmo).

  • Il nodo v modifica i seguenti membri del messaggio m’:
    • lvl diventa k.
    • pos diventa xk.

    • x̄ diventa x̄0·x̄1·...·x̄k-1.

  • Il nodo v inoltra m’ al suo miglior gateway verso il g-nodo (m’.lvl, m’.pos) in modo asincrono con protocollo TCP. L'algoritmo prosegue come detto prima con il prossimo nodo che riceve m’.

Il messaggio m’ raggiunge la destinazione finale:

  • Il nodo v prepara uno stub TCP per connettersi al nodo originante tramite percorso interno (vedi trattazione dei percorsi interni ad un g-nodo nel documento livelli e bits) attraverso la tupla m’.n.

  • Una volta realizzata la connessione TCP tra n e v il dialogo consiste in:
    • v comunica a n m’.id_msg;
    • Se n aveva rinunciato ad attendere la risposta a questo messaggio lo comunica a v e qui si interrompe.
    • Altrimenti n comunica a v il messaggio m.
    • v elabora il messaggio e risponde a n. Poi la comunicazione si chiude.
  • Il nodo n completa il metodo p2p che aveva generato il messaggio, eventualmente ritornando un risultato.

Fault tolerance

Siano n ed m due nodi che conoscono una certa chiave k per un servizio p. Entrambi sono in grado di calcolare x = Ht ( hp ( k ) ) e di contattare x. Sia j_n il livello del g-nodo comune a n ed x: cioè x ∈ nj_n, x ∉ nj_n-1. Sia j_m il livello del g-nodo comune a m ed x: cioè x ∈ mj_m, x ∉ mj_m-1. Sia j il minore tra j_n e j_m: cioè x ∉ nj-1, x ∉ mj-1. Cioè l'interno del g-nodo xj-1 di livello j-1 a cui appartiene il nodo x è sconosciuto per n e per m. Supponiamo ora che per qualche motivo i messaggi instradati dal modulo PeerServices si perdano all'interno del g-nodo xε (con ε piccolo a piacere), ε < j-1.

Pur essendo questa anomalia circoscritta ad un g-nodo piccolo a piacere, questo impedirebbe ai nodi n ed m di scrivere e leggere dati con chiave k nel servizio p.

Dopo che n vede fallire il suo tentativo di contattare x per salvare un record con chiave k, n deve cercare di isolare il g-nodo malfunzionante. Se lo facesse basandosi solo sulle sue conoscenze potrebbe solo calcolare x’ = Ht ( hp ( k ), exclude_list=[xj_n-1] ). Ora il nodo m cerca di contattare x per leggere il record con chiave k, vede che il suo tentativo di contattare x fallisce e quindi prova ad isolare il g-nodo malfunzionante. Basandosi solo sulle sue conoscenze calcolerebbe x’’ = Ht ( hp ( k ), exclude_list=[xj_m-1] ). La conseguenza è che se j_m ≠ j_n allora n ed m contattano nodi diversi.

Questo significa che un meccanismo robusto deve prevedere che il nodo n che cerca di contattare il nodo x ∈ nj_n e non vi riesce deve rilevare tutti gli identificativi del g-nodo xε (dal livello j_n-1 al livello ε) in cui il messaggio si è perso. Sarà cura di n, prima di effettuare ulteriori tentativi, di memorizzare questa tupla nel formato adeguato per evitare di giungere nuovamente allo stesso g-nodo. Ad esempio il nodo n sa, a seconda del servizio, se deve memorizzare solo la tupla interna al g-nodo in cui è avvenuta la ricerca (nj_n) oppure aggiungere i suoi identificativi fino ad un certo livello (ad esempio se stava contattando il Coordinator di un certo g-nodo) oppure fino all'ultimo livello.

Modifiche agli algoritmi

Modifichiamo l'algoritmo di instradamento considerando che si deve comunicare al nodo originante del messaggio m’ quali g-nodi sono stati raggiunti correttamente.

Modifichiamo inoltre l'algoritmo di calcolo di Ht considerando che deve essere possibile escludere un set di determinati g-nodi. Questi elementi da escludere possono essere, a seconda del momento come vedremo subito, espressi sotto forma di tuple globali (da l-1 a ε) oppure di tuple interne ad un g-nodo a cui il nodo appartiene.

Sia e_list il set di g-nodi da escludere. In questo set ogni g-nodo è espresso sotto forma di tupla globale. Il nodo n istanzia questo set inizialmente vuoto quando inizia il suo tentativo di contattare l'hash-node x̄.

Per prima cosa il nodo n sceglie dal set e_list quei g-nodi che sono nella sua mappa, cioè quelli che hanno il g-nodo direttamente superiore in comune con il nodo stesso, e li mette nel set my_e_list. Il nodo n calcola Ht(x̄, exclude_list=my_e_list) come prima, aggiungendo il vincolo di esclusione di quei g-nodi. Trova un livello j e un identificativo xj ∈ nj+1. Ora il nodo n sceglie dal set e_list quei g-nodi che sono interni al g-nodo xj (il g-nodo xj sicuramente non era nel set e_list) e li aggiunge al messaggio m’ come membro m’.e_list; in questa lista ogni g-nodo è espresso sotto forma di tupla interna a xj (da j-1 a ε) dove j è il livello del g-nodo obiettivo.

Come avveniva in precedenza, n invia m’ al miglior gateway e questo lo inoltra fino a raggiungere un nodo v dentro il g-nodo obiettivo. Quando v deve calcolare Ht(m’.x̄) prima sceglie dal set m’.e_list quei g-nodi che sono nella sua mappa e li mette nel set my_e_list. Poi calcola Ht(m’.x̄, exclude_list=my_e_list) e può trovarsi in una tra 3 diverse situazioni:

  1. v è la destinazione finale di m’.
  2. v individua un g-nodo interno che diventa il nuovo obiettivo di m’, cioè trova un livello k e un identificativo xk.

  3. v trova che a causa dei vincoli nessun g-nodo al suo interno è più valido come obiettivo per m’.

Nel caso 1 il nodo v si connette via TCP ad n attraverso la tupla m’.n e ne esegue la richiesta, come descritto prima.

Nel caso 2 il nodo v sceglie dal set m’.e_list quei g-nodi che sono interni al g-nodo xk (il g-nodo xk sicuramente non era nel set m’.e_list), li esprime sotto forma di tuple interne a xk (da k-1 a ε) e mette questa lista al posto del vecchio contenuto di m’.e_list; effettua anche le altre modifiche a m’ e lo inoltra verso il suo nuovo obiettivo; subito dopo v si connette via TCP ad n attraverso la tupla m’.n e gli comunica (senza necessitare alcuna risposta) l'identificativo m’.id_msg e la tupla che identifica all'interno di m’.nj+1 il g-nodo nuovo obiettivo.

Nel caso 3 il nodo v si connette via TCP ad n attraverso la tupla m’.n e gli comunica (senza necessitare alcuna risposta) il fallimento indicando l'identificativo m’.id_msg e la tupla che identifica all'interno di m’.nj+1 il g-nodo di v, cioè la tupla che nei suoi prossimi tentativi n dovrà escludere.


Con questi accorgimenti quando il nodo n invia il messaggio m’ al g-nodo xj = Ht ( hp ( k ) ) e si mette in attesa delle risposte possono verificarsi questi casi:

  1. Il nodo n riceve (oltre ad alcuni messaggi intermedi) per tempo una risposta da parte di x. Il messaggio m gli viene consegnato.
  2. Il nodo n riceve un messaggio da parte di un g-nodo xε che gli comunica che al suo interno non ci sono obiettivi validi.

  3. Il nodo n riceve alcuni messaggi (0 o più) che gli segnalano dei g-nodi xk con k < j come nuove destinazioni di m’ e poi più niente fino allo scadere del tempo massimo. In questo caso viene scelto come valore ε il più piccolo tra i messaggi ricevuti, oppure se sono stati ricevuti 0 messaggi lo stesso valore j.

Nei casi 2 e 3 abbiamo un tentativo fallito e un valore xε da escludere nelle future ricerche. Il valore xε è espresso sotto forma di tupla interna a xj, quindi il nodo n è in grado di completare la tupla per renderla globale e poi la inserisce nel set e_list. Il nodo n effettuerà quindi altri tentativi escludendo i g-nodi inadatti come è stato descritto sopra.

L'unico errore (eccezione) definitivo ammissibile per una richiesta ad un servizio peer-to-peer sarà quindi il "nessun nodo nella rete partecipa al servizio" in caso di servizi opzionali. Nei servizi obbligatori invece come minimo il nodo n risponde a se stesso.

Repliche

Quando un nodo v riceve la richiesta di memorizzare (o aggiornare, o rinfrescare) un record con chiave k e valore val nella sua porzione del database distribuito del servizio p si occupa di replicare questo dato su un numero q di nodi replica. L'obiettivo è fare sì che se il nodo muore o si sconnette dalla rete, alla prossima richiesta di lettura del dato venga comunque data la risposta corretta. Quindi v deve scegliere i nodi che saranno contattati per la chiave k quando lui non parteciperà più.

Grazie all'introduzione del meccanismo di fault tolerance descritto sopra, scegliere e contattare tali nodi diventa un esercizio banale. Di seguito l'algoritmo:

  • Il nodo v calcola = hp ( k ).

  • lista_repliche = []

  • lista_esclusioni = [v]

  • Mentre lista_repliche.size < q:

    • Calcola e contatta x = Ht ( x̄, exclude_list=lista_esclusioni ) 1 . Il risultato di questa operazione può essere di 3 tipi: 1) viene contattato un nodo x; 2) viene individuato un g-nodo xj dove cercare x, ma il contatto non riesce e viene individuato un g-nodo xε da escludere dalle prossime ricerche; 3) si rileva che nessun altro nodo può soddisfare la richiesta.

    • caso 1:
      • Il nodo n comunica ad x il messaggio m che consiste in "replica il dato k,val nella tua cache per p"
      • lista_repliche.add(x)
      • lista_esclusioni.add(x)
    • caso 2:
      • lista_esclusioni.add(xε)

    • caso 3:
      • break.

Note:

  1. Con la dicitura "contatta x = Ht ( x̄, exclude_list=lista_esclusioni )" si intende la sequenza di operazioni descritte negli algoritmi modificati con il meccanismo di fault tolerance. Quindi in realtà il nodo v quando esegue la funzione Ht mette nella exclude_list solo una parte degli elementi presenti in lista_esclusioni, cioè i g-nodi visibili nella sua mappa; poi a seconda di quale g-nodo xj è stato eletto il nodo v prende un sottoinsieme degli altri elementi presenti in lista_esclusioni e li inserisce nel messaggio m’ con cui cerca di contattare x.

Popolamento della cache al bootstrap

Quando un nodo n entra nella rete (oppure quando inizia a partecipare ad un servizio) può venirgli assegnato un indirizzo prossimo a qualche chiave precedentemente salvata nel database distribuito. Deve svolgere quindi alcune operazioni per reperire e memorizzare i record di sua pertinenza.

Prerequisito di queste operazioni è che il nodo sia maturo, cioè abbia piena conoscenza dei nodi e g-nodi presenti nella rete che sono di sua visibilità. In caso di servizio opzionale deve anche avere piena conoscenza della mappa di nodi partecipanti al servizio.

Abbiamo visto che la funzione Ht cerca l'indirizzo che minimizza la funzione dist e che la funzione dist introduce una sorta di "direzione a destra" nella ricerca dell'indirizzo. Definiamo ora la funzione H_revt(x̄) = minargx∈dom(αt)dist_rev(x̄,x) dove la funzione dist_rev implementa la "direzione a sinistra" nella ricerca dell'indirizzo. Per farlo basta definire dist_rev(x̄,x) = dist(x,x̄). Inoltre va aggiunto al messaggio m’ un campo booleano reverse che impostato a True indica al nodo v di proseguire la ricerca usando H_rev.

Questo è l'algoritmo che esegue n per popolare la sua cache:

  • Il nodo n ha indirizzo n0·n1·...·nl-1.

  • Per ogni livello j da 0 a l-1:

    • lista_esclusioni = [nj]

    • sx_to_be_done = True

    • Mentre True:
      • Il nodo n calcola e contatta x = Ht ( n, exclude_list=lista_esclusioni ) circoscritto al g-nodo nj+1 1 . Il risultato di questa operazione può essere di 3 tipi: 1) viene contattato un nodo x; 2) viene individuato un g-nodo xj dove cercare x, ma il contatto non riesce e viene individuato un g-nodo xε da escludere dalle prossime ricerche; 3) si rileva che nessun altro nodo può soddisfare la richiesta.

      • caso 1:
        • Il nodo n comunica ad x il messaggio m, cioè gli chiede la sua cache per il servizio p.2

        • lista_esclusioni.add(x)
        • break.
      • caso 2:
        • lista_esclusioni.add(xε)

      • caso 3:
        • sx_to_be_done = False
        • break.
    • Se sx_to_be_done:
      • Mentre True:
        • Il nodo n calcola e contatta x = H_revt ( n, exclude_list=lista_esclusioni ) circoscritto al g-nodo nj+1. Il risultato di questa operazione può essere di 3 tipi: 1) viene contattato un nodo x; 2) viene individuato un g-nodo xj dove cercare x, ma il contatto non riesce e viene individuato un g-nodo xε da escludere dalle prossime ricerche; 3) si rileva che nessun altro nodo può soddisfare la richiesta.

        • caso 1:
          • Il nodo n comunica ad x il messaggio m, cioè gli chiede la sua cache per il servizio p.
          • break.
        • caso 2:
          • lista_esclusioni.add(xε)

        • caso 3:
          • break.

Note:

  1. Aggiungere questo requisito all'implementazione della funzione Ht è banale: basta escludere dalla ricerca iniziale fatta dal nodo n tutti i g-nodi esterni al g-nodo nj+1.

  2. Come il nodo n utilizzi la cache reperita dai vari singoli nodi dipende dal servizio. Di norma n memorizza tutti i record nella sua cache.

Requisiti

  • Mappa delle rotte note.
  • Factory per aprire una connessione TCP con un percorso interno ad un proprio g-nodo verso un nodo di cui si conosce l'identificativo interno.
  • Set dei servizi peer-to-peer. Ogni servizio è una istanza di una classe che deriva dalla classe comune PeerService. Ogni servizio ha un identificativo numerico univoco.

  • a

Durante le operazioni del modulo è possibile aggiungere un servizio di tipo opzionale.

Deliverables

  • Viene definita una classe base (PeerService) che può essere derivata per implementare ogni specifico servizio peer-to-peer.

Classi e interfacce

La mappa delle rotte è un oggetto di cui il modulo conosce l'interfaccia IPeersMapPaths. Tramite essa il modulo può:

  • leggere il numero di livelli della topologia (metodo 'i_peers_get_levels');
  • leggere la gsize di ogni livello (metodo 'i_peers_get_gsize');
  • leggere il numero di nodi stimato all'interno del proprio g-nodo a ogni livello (metodo 'i_peers_get_nodes_in_my_group');
  • leggere l'identificativo del proprio nodo a ogni livello (metodo 'i_peers_get_my_pos');
  • determinare se un certo g-nodo esiste nella rete – cioè se appartiene a domnt) (metodo 'i_peers_exists');

  • leggere il numero di nodi stimato all'interno di un certo g-nodo (metodo 'i_peers_get_nodes_in_group').
  • ottenere uno stub per inviare un messaggio al miglior gateway verso un certo g-nodo (metodo 'i_peers_gateway').


In alcuni casi sarà necessario realizzare una comunicazione TCP verso un nodo originante di un messaggio. In tali casi si vuole raggiungere il nodo attraverso un percorso interno al proprio g-nodo di un dato livello. L'oggetto fornito al modulo a questo scopo implementa l'interfaccia IPeersBackConnectionFactory, la quale permette di:

  • realizzare una connessione TCP con un certo nodo (metodo 'i_peers_open_tcp_inside').


Le tuple che il modulo elabora per il calcolo della funzione Ht sono istanze di una classe nota al modulo. Anche se rappresentano degli indirizzi di nodi (all'interno della rete o all'interno di un g-nodo) non viene usata la stessa classe che rappresenta un Netsukuku address (Naddr) o una sua interfaccia.

La classe usata in questo modulo è PeerTuple. Si tratta di una classe serializzabile in quanto le tuple vanno comunicate nei messaggi da inoltrare.


La classe che implementa un servizio deriva da PeerService.

La classe base non sa come ottenere da una chiave k la tupla x, questo procedimento spetta alla classe derivata. Tuttavia molte operazioni saranno uguali nella maggior parte dei servizi. Quindi la classe base cerca di fornire i servizi comuni senza tuttavia essere di impedimento alla classe derivata se vuole usare altre modalità di calcolo. Per fare questo la classe base fornisce:

  • un metodo virtuale 'perfect_tuple' che riceve a parametro la chiave (Object k) e restituisce la tupla x.
  • un metodo astratto 'hash_from_key' che riceve a parametro la chiave (Object k) e un intero (top) e restituisce un intero tra 0 e top.

Quando le operazioni del modulo richiedono il calcolo di hp ( k ) su un certo servizio p, il metodo 'perfect_tuple' viene richiamato sull'istanza di PeerService, quindi tale metodo deve essere pubblico.

Se tale metodo non viene ridefinito dalla classe derivata, il suo comportamento è il seguente. L'istanza conosce le dimensioni dei g-nodi ad ogni livello (gsizes) quindi calcola la dimensione dello spazio degli indirizzi validi. Poi richiama il metodo 'hash_from_key' passando oltre alla chiave k il numero massimo dell'hash (la dimensione dello spazio di indirizzi meno uno). In questo metodo la classe derivata deve occuparsi di associare alla chiave un valore di hash (di norma uniformemente distribuito) compreso tra 0 e il valore massimo (inclusi). Questo metodo è demandato alla classe derivata e quindi è definito astratto. Inoltre deve essere usato solo con la modalità sopra descritta, quindi può essere definito protetto.

Poi, nel metodo 'perfect_tuple', l'istanza usa il valore di hash per produrre una tupla sulla base della sua conoscenza di gsizes.

Se invece la classe derivata ridefinisce il metodo 'perfect_tuple' è libera di calcolare direttamente la tupla x a partire dalla chiave e dalle sue conoscenze.

Netsukuku/ita/docs/ModuloPeers/AnalisiFunzionale (last edited 2015-11-14 14:57:23 by lukisi)