Architetture di sistemi distribuiti

Lezione del 2019-03-14 gio.

Stili Architetturali

Consideriamo il fatto che un sistema distribuito può essere classificato secondo stili architetturali.

Uno stile definisce:

  • componenti;

  • connessioni;

  • scambio di dati;

  • configurazione.

Considerando le componenti come unità modulari che forniscono e richiedono servizi, è possibile classificare i diversi modi in cui questi componenti possono essere connessi. Questi modi sono denominati stili. Uno stile non identifica il singolo sistema, ma la tipologia della sua struttura.

È la configurazione che identifica un sistema bloccandone completamente le caratteristiche.

Tra gli stili architetturali principali abbiamo le seguenti architetture:

  • stratificate (layered);

  • basate su oggetti (object-based);

  • basate sui dati (data-centered);

  • basate su eventi (event-based);

  • basate su spazi di dati condivisi (shared-data space).

Stile a strati

Lo stile architetturale a strati è uno dei più importanti. Si modella con il seguente diagramma.

_images/02_layered_architecture.svg

Con questo stile, un componente ad un determinato livello, chiede un servizio al livello inferiore, ottenendo la relativa risposta. Questo comportamento impatta anche il flusso logico del programma, e l’organizzazione dei dati.

Il modello ISO-OSI è tipicamente a strati. Così come il paradigma Model View Controller (MVC).

Quindi abbiamo a che fare con una organizzazione logica, non fisica. Ma quando si dichiara il numero di livelli e le loro responsabilità, allora si fissa una configurazione, in quanto si lascia la generalità dello stile (a strati), per abbracciare delle specificità che indirizzano con precisione il progetto.

La scelta dei parametri di configurazione è dettata dagli scopi del progetto.

Ad es. il modello ISO-OSI prevede ben 7 livelli per premiare la componibilità/manutenibilità del codice, permettendo la sostituzione di uno strato con un altro equivalente, specializzato in altro ambito (es. TCPv4 sostituito da TCPv6). Così come permettendo una gestione degli errori più granualare.

D’altro canto non si deve ampliare troppo il numero di strati, altrimenti si incappa in un problema di performance. Infatti una richiesta che parte da un livello alto, genererà una serie di richieste a cascata verso i livelli più bassi, creando un maggiore impegno di risorse e tempi di risposta più lunghi.

Stile object-based

Lo stile object-based è schematizzabile con il seguente diagramma.

_images/03_object_based_architecture.svg

In questo caso ogni componente del sistema è un oggetto, e le chiamate tra i diversi oggetti avvengono tramite meccanismi di remote procedure call o tramite remote method invocation.

Lo stile object-based è un superinsieme di quello a strati.

Implementare questo tipo di architettura ha una peculiarità: i diversi componenti si devono conoscere e devono essere in grado di chiamarsi l’uno con l’altro. Ovvero è necessario un legame forte (tight binding).

Stile data-centered

In questo stile, i processi comunicano tramite un repository di dati messo in comune. Repository che può eventualmente essere implementato con un database.

Questo repository è usualmente destrutturato: una vera e propria lavagna su cui scrivere i dati da condividere.

Questo, e il sistema layered, sono gli stili più semplici da implementare. Lo stile object-based è più generale, ma anche più complesso.

Stile event-based

Detto anche architettura a bus, è schematizzato dal seguente diagramma.

_images/04_event_based_architecture.svg

Come si vede i componenti comunicano tramite eventi. Questi eventi possono trasportare dati.

In questa architettura si ha un legame lasco (loose binding), al contrario dello stile object-based, che richiede un legame stretto. Qui le diverse componenti non si conoscono l’una con l’altra. Vi è un bus logico che ha il compito di connettere le componenti, e che è conosciuto da queste.

Un pub/sub system funziona tramite bus.

Un esempio può essere Java Message Service (JMS), o MQTT. Queste sono librerie che implementano il canale di comunicazione. Lo sviluppatore ha il compito di creare i componenti che usano questo servizio per implementare il sistema complessivo.

In questa architettura un publisher è un componente che pubblica una informazione. Di solito le informazioni sono suddivise in topic (categorie). Invece un subscriber è un consumatore di informazioni.

Quando il publisher dichiara la topic dell’informazione pubblicata, allora si parla di architettura pub/sub di tipo topic-based.

MQTT è topic-based. È un gateway centralizzato cui si attaccano i publisher e i subscriber. Anche JMS è centralizzato, ma è possibile creare più gateway e coordinarli.

È possibile pensare di filtrare gli eventi da spedire. Ad es. Unicam sta facendo una ricerca in cui si definiscono con logica del primo ordine dei filtri sui dati in osservazione (ad es. genera un evento se la temperatura ha superato 25 gradi), per fare in modo che invece dell’intero streaming dei dati, verso i sottoscrittori partano i soli eventi d’interesse.

I filtri descritti hanno ricadute positive sul traffico dati se sono stabili. Se i componenti del sistema di iscrivono e deiscrivono spesso, allora il lavoro necessario a gestire i filtri associati può portare ad un notevole hoverhead di traffico dati.

Stile shared-data space

È una evoluzione dello stile message-based, che non ha memoria. Invece il shared-data space ha memoria, come nel seguente diagramma.

_images/05_shared_data_space_architecture.svg

In tal modo non è necesario che publisher e suscriber siano attivi entrambi contemporaneamente. Si ha un disaccoppiamento a livello temporale.

Attenzione a non confonderla con lo stile data-centered. Quest’ultimo mette a disposizione una lavagna su cui scrivere e leggere dati. Invece in questo caso abbiamo una interfaccia a servizi con cui interagire.

Architettura di sistema

Una volta deciso lo stile architetturale, è necessario decidere come mettere in atto i componenti scelti: il loro deployment. Questa analisi comporta la definizione dell’architettura del sistema. Architettura che può essere fondamentalmente:

  • centralizzata, ovvero client/server;

  • decentralizzata, ovvero peer to peer;

  • ibrida.

Architettura di sistema centralizzata

Nel caso di architettura centralizzata con comportamento sincrono, il client invia la richiesta al server, e rimane fermo in attesa della relativa risposta.

È possibile pensare di utilizzare tra client e server un protocoolo di trasmissione dati non affidabile. In tal modo si aumenta mediamente la velocità di trasmissione dei dati (per minore overhead). Ma si rischia che l’applicazione duplichi le richieste in caso di perdita del riscontro di avvenuta ricezione. Questa possibilità può essere inaccettabile (si pensi ad eventuali transazioni economiche).

Se le operazioni richieste sono idempotenti, allora vale la pena utilizzare UDP, perché decisamente più veloce e impegna meno banda. In caso contrario, utilizzare TCP.

Con architettura di sistema client server, si può optare per uno stile architetturale a livelli. Ad esempio, di solito una applicazione WEB server ha uno stile a livelli, configurato con tre livelli:

  • user interface,

  • processing level,

  • data level.

Deciso lo stile architetturale a (tre) livelli, poi va studiata la system architecture, ovvero come deployare i livelli nei sistemi fisici. Questa può essere molto diversa, come ci mostra il seguente diagramma nel caso di disponibilità di due macchine: client e server (two-tiered architecture).

_images/06_layered_system_architecture.svg

Come indicato nel diagramma, è possibile localizzare le diverse componenti nel client (parzialmente o integralmente) o nel server (anche in questo caso parzialmente o integralmente).

Usualmente il client ospita parzialmente o integralmente l’interfaccia utente, In questo caso si parla di thin client. Se ospita anche logica applicativa e/o parte della base dati, allora abbiamo un fat client.

Entrambi i tipi di client predetti presentano vantaggi e svantaggi.

Un thin client può essere implementato da una macchina con risorse hardware limitate, mentre un fat client richiede sistemi decisamente più performanti.

Inoltre un thin client significa minore codice applicativo da gestire (messo tutto nel server).

Un client fat si giustifica se non vi è connessione, o se la connessione è inaffidabile. In queste condizioni un fat client permette di lavorare in locale per poi sincronizzare il lavoro quando la connessione è nuovamente disponibile.

Si possono avere architetture di sistema multitiered, ad esempio una three-tiered architecture in cui localizzare il client, un server per la logica applicativa e un server per il database.

Architettura di sistema decentralizzata

Le architetture centralizzate attuano una distribuzione verticale, ovvero pongono componenti logicamente diverse in macchine diverse.

Le architetture decentralizzate perseguonio una logica di distribuzione orizzontale: una componente è suddivisa in parti logicamente equivalenti. Ed ogni parte opera sui propri dati condivisi.

I sistemi peer-to-peer supportano la distribuzione orizzontale.

Tipicamente un sistema peer-to-peer mette la stessa componente in più macchine diverse. Questi sistemi, a differenza del client-server, non assegnano un ruolo alla macchina: sono tutte uguali. Ed ognuna di queste svolge sia il ruolo di richiedente, che di risposta alle richieste. Da cui la dizione servent ovvero contemporaneamente sia client che server.

In questi sistemi è centrale il concetto di overlay network. Questa è una rete logica che connette i componenti, utilizzando una sottostante rete dati fisica 1.

Quando si costruisce un sistema peer-to-peer, il problema è la progettazione della rete logica.

Ad esempio un sistema di streaming è un sistema peer-to-peer. Questo è implementato tramite una catena di nodi IP. Il primo che arriva, si prende lo streaming, quindi il secondo, e così via. Ogni volta si aggiunge un nodo, viene costruito un altro pezzo di overlay network, tipicamente ad albero. Per questo motivo usualmente è più efficace costruire l’overlay osservandolo dall’esterno, perché in tal caso è possibile aderire meglio alla rete fisica, minimizzando i ritardi che potrebbero essere introdotti da un overlay la cui struttura si allontana molto dal fisico.

Di solito quando si costruisce un overlay per lo streaming si dà priorità ad avere un basso numero di figli per ogni nodo, piuttosto che limitare la profondità dell’albero dell’overlay (che introduce ritardo). Questo per non correre il rischio di strangolare eccessivamente la banda passante della rete (che dipende dal numero di nodi figli).

Una overlay network può essere:

  • strutturata,

  • non strutturata.

Le overlay network strutturate tipicamente si costruiscono per trovare i dati distribuiti.

Lezione del 2019-03-14 gio.

Un modo per costruire una rete peer-to-peer strutturata consiste nell’usare hash table distribuite (distributed hash table: DHT) per memorizzare tuple (chiave, valore) e ritrovare il valore associato alla chiave. L’aggettivo distribuito, in questo ambito significa che la coppia (chiave, valore) può stare in un altro computer. Non solo. Si deve considerare una situazione dinamica: i nodi possono aggiungersi, togliersi, o andare in errore. Inoltre deve essere scalabile.

Un esempio è la Chord Network, in cui la struttura dell’overlay è definita imponendo che ogni nodo abbia un predecessore e un successore, e che la struttura sia chiusa su se stessa, come una circonferenza.

Inoltre esiste la seguente regola di associazione: un item con chiave k è registrato nel nodo esistente con l’identificativo id più piccolo tale che: id ≥ k. Si noti che chiavi e identificativi dei nodi utilizzano gli stessi tipi di codici.

Il seguente diagramma illustra una struttura logica a circonferenza. I nodi sono rappresentati dai cerchi. I cerchi bianchi con linea tratteggiata indicano nodi non esistenti. I cerchi grigi con linea continua sono nodi esistenti.

_images/07_chord_network.svg

Nei cerchi sono indicati gli identificativi dei nodi. Gli oggetti hanno chiave associata analoga a quella dei nodi, ovvero i numeri interi non negativi da 0 a 15.

Usando la regola di associazione predetta, si vede che nel nodo 1, sono registrati gli elementi con chiavi {0, 1}, nel nodo 4 sono registrati gli elementi {2, 3, 4}, e così via.

In queste condizioni, la funzione lookup(k) per trovare l’identificativo del nodo che ospita la chiave k è succ(k) (successore di k).

Quando entra un nuovo nodo nell’overlay, dato il suo identificativo, chiederà al nodo successivo di avere tutti gli elementi con le chiavi che sono di sua competenza. Ad es. nel diagramma precedente, se entrasse il nodo 10, gli elementi {8, 9, 10} passerebbero dal nodo 12 al nodo 10.

Analogamente, se un nodo lascia l’overlay, deve cedere tutti i propri elementi al nodo che segue. Sempre dal diagramma precedente, se uscisse il nodo 1, gli elementi {0, 1} passerebbero al nodo 4.

Quando un nodo deve cercare un elemento, interroga il suo successore, che a sua volta propaga l’interrogazione al proprio successore, e così via. È un algoritmo lineare.

Per accellerare la ricerca è possibile usare delle finger tables. Ogni nodo avrà una tabella in cui salva gli indirizzi diretti di alcuni altri nodi. In particolare i nodi che sono a \(2^0\), \(2^1\), \(2^2\), \(2^3\) passi. In tal modo quando un nodo effettua la ricerca di un elemento può:

  • considerare se è in uno dei nodi di cui ha conoscenza diretta;

  • in caso di risposta negativa, può calcolare quale nodo fra questi è il più vicino a quello che lo dovrebbe contenere, e passare a lui la query relativa.

In tal modo si passa da una ricerca lineare ad una ricerca \(O(log_2 \: n)\).

Consideriamo il fatto che per passare da una ricerca \(O(n)\) a una \(O(log_2 \: n)\) abbiamo dovuto indrodurre una stuttura dati (le tabelle) che deve essere gestita durante le attività di modifica dell’overlay network (inserimento/cancellazione dei nodi). Di conseguenza questa modifica si giustifica nel caso di un elevato numero di query e con la rete abbastanza statica. Se la rete è estremamente dinamica, l’onere degli aggiornamenti delle tabelle inficia la maggiore velocià delle query.

Passiamo alla rete peer-to-peer destrutturata. Questo significa che la overlay network dei nodi che la compongono viene costruita con algorimi randomici.

In pratica ogni nodo ha conoscenza (casuale) di alcuni vicini. Se ha necessità di comunicare (ad es. cercare chi ha un elemento) può solo farlo verso i nodi che conosce direttamente, e attendere che questi, a loro volta, comunichino con i propri vicini … Questo comportamento viene detto flooding (ovvero: inondazione/tracimazione).

Una applicazione di overlay network su peer-to-peer destrutturata è presente in 5G. Qui una delle opzioni disponibili è il device to device comunication. Ovvero la possibilità di usare un device intermedio come ponte per raggiungere l’antenna da cui entrare in rete. Però è necessario conoscere l’esistenza del nodo vicino, e questo si fa con messaggi di broadcast.

Queste reti destrutturate non sono in grado di supportare un elevato numero di nodi: il numero di messaggi cresce rapidamente al crescere dei nodi, rischiando di saturare la rete fisica.

L’aspetto positivo consiste nel fatto che, essendo un overlay destrutturato, non vi sono strutture dati da mantenere. Nodi che si aggiungono, o che vanno via, non influenzano il tutto: un percorso in qualche modo si trova sempre.

Non essendoci una struttura da mantenere, questa soluzione è particolarmente adatta per ambienti mobili.

Per cercare di coniugare i vantaggi dei peer-to-peer stutturati con i destrutturati, si sono ideate le architetture superpeers.

Queste architetture sono spesso utilizzate nelle reti di sensori, ad esempio zigbee oppure Apple HomeKit.

In questo caso i nodi si organizzano eleggendo dei nodi superpeer, come nel seguente diagramma, dove i nodi grigi rappresentano i superpeer, mentre i nodi bianchi sono nodi regolari.

_images/08_superpeer.svg

In pratica la rete viene partizionata in gruppi di nodi, detti cluster. Per ogni cluster viene eletto un superpeer. Con questa organizzazione non si effettua il flooding di tutti i nodi, ma solo tra i superpeer. Mentre i nodi normali parlano solo con il proprio superpeer.

Volendo, si possono organizzare in modo strutturato le comunicazioni tra i superpeer, ad esempio facendoli parlare solo con il padre in una struttura ad albero.

Attenzione al fatto che questa struttura va gestita, in particolare per l’elezione del superpeer. Questo può essere un problema: ci deve essere un protocollo di leader election. Un aspetto negativo della elezione è che se vi sono fallimenti (di nodi o di rete dati) durante l’elezione, questa può non funzionare (teorema di Fisher). Si pensi al caso in cui una rete venga frazionata in due a causa di una interruzione fisica: vengono eletti due leader, uno per ogni sottorete. Quando poi la connessione risale, è necessario capire che vi sono due (o più) leader nella stessa partizione di rete 2, ed effettuare una nuova elezione.

A causa di questo problema, nelle reti domotiche i superpeer vengono impostati manualmente.

Architettura di sistema ibrida

Infine si possono avere architetture ibride, in cui una parte è centralizzata (client/server), e una parte è distribuita (peer-to-peer).

Anche le reti superpeer possono essere considerate esempi di architetture ibride.

La motivazione per l’uso di queste architetture va cercata nel fatto che le reti decentralizzate pure impongono una quantità di messaggi sulla rete fisica decisamente elevata. Quindi si cerca di mitigare questo aspetto utilizzando un innesco centralizzato (più efficiente in quanto a messaggi in rete) seguito da una elaborazione decentralizzata.

Un esempio di questo tipo di architettura è il BitTorrent. In questo caso è necessario scaricare un file torrent con le indicazioni dei tracker. I tracker sono server centrali che tengono traccia dei nodi attivi. Dato l’elenco dei nodi attivi in cui sono presenti le chiavi cercate, è possibile avviare l’elaborazione peer-to-peer.


1

Si ricordi l’esempio della struttura organizzativa aziendale: un impiegato parla con il suo capo ufficio, che a sua volta parla con il capo divisione … Tutti usano il telefono (rete fisica), ma l’impiegato (anche se la rete fisica lo permetterebbe) non chiama direttamente il capo divisione, passa tramite il suo capo ufficio.

2

Scoprire dinamicamente che esiste più di un leader in una stessa partizione di rete è complesso.