{"id":6741,"date":"2024-02-16T12:39:38","date_gmt":"2024-02-16T11:39:38","guid":{"rendered":"https:\/\/blog.besharp.it\/?p=6741"},"modified":"2024-02-16T12:41:32","modified_gmt":"2024-02-16T11:41:32","slug":"comunicazione-asincrona-nei-sistemi-distribuiti-best-practices-e-impatto-su-prestazioni-e-affidabilita","status":"publish","type":"post","link":"https:\/\/blog.besharp.it\/it\/comunicazione-asincrona-nei-sistemi-distribuiti-best-practices-e-impatto-su-prestazioni-e-affidabilita\/","title":{"rendered":"Comunicazione asincrona nei sistemi distribuiti: best practices e impatto su prestazioni e affidabilit\u00e0"},"content":{"rendered":"\n

Introduzione<\/h2>\n\n\n\n

L’asincronia \u00e8 un concetto ampiamente utilizzato, ma che pu\u00f2 portare con s\u00e8 molte complessit\u00e0 nello sviluppo di software. Possiamo trovare asincronismo in svariati contesti, dalle applicazioni front-end ai sistemi back-end, fino ad arrivare alla comunicazione tra tra servizi in sistemi distribuiti. In base al contesto, il meccanismo con cui si costruisce l\u2019asincronismo, le funzionalit\u00e0 e l’impatto sull’esperienza utente possono variare notevolmente, cos\u00ec come anche i possibili vantaggi e le potenziali criticit\u00e0.<\/p>\n\n\n\n

Ad esempio, in un’applicazione front-end, l’implementazione dell’asincronismo garantisce che gli utenti possano ricevere immediatamente feedback sulle loro azioni mentre ulteriore elaborazione avviene lato backend. Questo migliora sia l’efficienza del sistema, che la UX.<\/p>\n\n\n\n

Il back-end solitamente implementa parti asincrone per ottimizzare l’utilizzo delle risorse e facilitare la gestione delle richieste simultanee. Sfruttando i meccanismi asincroni \u00e8 possibile\u00a0 eseguire parallelamente attivit\u00e0 computazionalmente intensive e gestire efficientemente le operazioni di input\/output.<\/p>\n\n\n\n

Parlando poi di sistemi distribuiti, l\u2019utilizzo di meccanismi asincroni pu\u00f2 presentare varie sfide, tra cui la natura inaffidabile della rete, la selezione di protocolli di messaggistica e formati di serializzazione appropriati e l\u2019implementazione di robusti meccanismi di gestione degli errori e di retry. Un sistema distribuito con parti asincrone richiede un\u2019attenta progettazione. Tuttavia, i vantaggi in termini di scalabilit\u00e0, resilienza e tolleranza agli errori valgono gli sforzi di progettazione aggiuntivi.<\/p>\n\n\n\n

In questo articolo esploreremo vari aspetti dell’implementazione della comunicazione asincrona all’interno dei sistemi distribuiti, ma tratteremo anche alcune sfumature dell’asincronia sia nel contesto del front-end che del back-end.\u00a0<\/p>\n\n\n\n

Esaminando in modo completo questi aspetti, puntiamo a far luce sulle complessit\u00e0, sulle best practices e sulle potenziali insidie \u200b\u200b\u200bche possono sorgere durante gli sviluppi.<\/p>\n\n\n\n

Asincronia nello sviluppo front-end<\/h2>\n\n\n\n

Cominciamo il nostro viaggio dallo sviluppo di applicazioni di front-end.<\/p>\n\n\n\n

Per creare applicazioni web con una buona esperienza utente, \u00e8 fondamentale implementare chiamate API asincrone e, conseguentemente, gestire le risposte asincrone.<\/p>\n\n\n\n

I moderni framework di sviluppo front-end, ed anche molte librerie, offrono meccanismi robusti per la comunicazione asincrona con le API. Attraverso tecniche come AJAX e funzionalit\u00e0 JavaScript asincrone come promises e async\/await \u00e8 possibile effettuare richieste e gestirne le risposte senza bloccare il thread principale del browser.<\/p>\n\n\n\n

Con questo approccio, \u00e8 possibile creare interfacce reattive, che creano un’esperienza naturale per l’utente finale riducendo i ricaricamenti delle pagine e il tempo complessivo che l’utente deve attendere per l’output. <\/p>\n\n\n\n

L’esperienza utente \u00e8 generalmente migliore e il consumo complessivo di larghezza di banda viene ridotto, diminuendo i costi e il carico dell’infrastruttura.<\/p>\n\n\n\n

Inoltre, la gestione asincrona della comunicazione con il server permette agli sviluppatori frontend di implementare aggiornamenti in tempo reale, arricchendo ulteriormente l’esperienza dell’utente. Ad esempio, sfruttando la comunicazione WebSocket o il long polling, le applicazioni web possono ricevere aggiornamenti istantanei dal backend, aumentando significativamente la gamma di funzionalit\u00e0 che possono essere implementate.<\/p>\n\n\n\n

Le principali tecniche di programmazione asincrona per lo sviluppo frontend sono le Callback, le Promise e Async\/await. <\/p>\n\n\n\n

Le Callback<\/strong> sono semplicemente funzioni passate come argomenti ad altre funzioni ed eseguite successivamente, in genere al completamento di un’operazione asincrona come la ricezione della risposta ad una chiamata API.<\/p>\n\n\n\n

Sebbene questo meccanismo costituisca la base fondamentale dell\u2019asincromismo in JavaScript, e in precedenza fosse l’unico modo disponibile, pu\u00f2 portare facilmente al fenomeno chiamto \u201ccallback hell\u201d e rendere il codice difficile da leggere e mantenere se annidato profondamente.<\/p>\n\n\n\n

Le Promise<\/strong> rappresentano l’eventuale completamento o fallimento di un’operazione asincrona e consentono il concatenamento di pi\u00f9 azioni asincrone. Sono ampiamente utilizzate nel JavaScript pi\u00f9 moderno e forniscono una migliore leggibilit\u00e0 e gestione degli errori rispetto alle callback, consentendo di concatenare operazioni utilizzando i metodi .then() e .catch().<\/p>\n\n\n\n

Le moderne keyword async\/await consentono di scrivere codice asincrono utilizzando una sintassi di tipo sincrono. Usando await si pu\u00f2 sospendere l’esecuzione finch\u00e9 una promessa non viene risolta o rifiutata. Async\/await \u00e8 particolarmente utile quando si gestiscono pi\u00f9 operazioni asincrone che dipendono l’una dall’altra.<\/p>\n\n\n\n

Asincronismo nel backend<\/h2>\n\n\n\n

L’adozione di componenti asincroni e della programmazione concorrente nello sviluppo backend \u00e8 sempre pi\u00f9 imporante ed \u00e8 spinta dalla necessit\u00e0 di realizzare applicazioni semrpe pi\u00f9 scalabili ed efficienti. <\/p>\n\n\n\n

Uno dei principali vantaggi del parallelismo \u00e8 la riduzione dei tempi di risposta. Ad esempio, suddividere un’attivit\u00e0 computazionalmente intensiva in thread paralleli riduce notevolmente il tempo necessario per la risposta ottimizzando contemporaneamente l’utilizzo della CPU. La scrittura di codice concorrente pu\u00f2 anche ottimizzare le operazioni di I\/O, utilizzando flussi bufferizzati e limitando il tempo in cui il software rimane bloccato per l’I\/O e quindi la quantit\u00e0 di cicli CPU sprecati.<\/p>\n\n\n\n

La maggior parte degli application server utilizzati come interfacce per il sistema backend possono utilizzare il multi-threading o il multitasking per scalare in modo pi\u00f9 efficace e soddisfare il crescente traffico di utenti. Questa scalabilit\u00e0 \u00e8 essenziale per sfruttare tutta la potenza disponibile nel nodo computazionale.<\/p>\n\n\n\n

Ma ci sono anche possibili svantaggi: l’implementazione di componenti asincroni e paralleli introduce ulteriore complessit\u00e0, richiedendo un’attenta progettazione e un’accurata gestione degli errori. Gestire la concorrenza, la sincronizzazione e la coerenza dei dati diventa pi\u00f9 impegnativo e potrebbe portare a bug subdoli.<\/p>\n\n\n\n

Un altro aspetto da considerare \u00e8 che il debug di codice fortemente parallelizzato \u00e8 solitamente complesso e maggiormente soggetto a errori. Potrebbero verificarsi race condition, situazioni di stallo (deadlock) e problemi relativi alla sincronizzazione, che richiedono strategie di test complete e strumenti di debug.<\/p>\n\n\n\n

La tecnica pi\u00f9 comune \u00e8 sfruttare i paradigmi e le librerie di programmazione concorrente per gestire in modo efficiente le operazioni non bloccanti, come callback, promesse o async\/await in Node.js o Python con asyncio.<\/p>\n\n\n\n

Un altro modo \u00e8 sfruttare direttamente il threading e il multitasking in linguaggi che lo consentono, ad esempio Java, Rust o Python. Ci\u00f2 comporta la generazione di thread o processi per gestire attivit\u00e0 parallelizzabili, sfruttando cos\u00ec in modo efficace i processori multi-core. Esistono librerie, fornite sia dal linguaggio che da terze parti, per semplificare la gestione delle attivit\u00e0 di basso livello, introducendo concetti come pool di thread e implementando meccanismi di sincronizzazione come i lock e i semafori per gestire le risorse condivise in modo sicuro in un ambiente parallelo.<\/p>\n\n\n\n

Per attivit\u00e0 specifiche, \u00e8 anche possibile sfruttare prodotti che utilizzano framework di elaborazione distribuita come Apache Kafka, Apache Spark o Apache Flink per elaborare grandi volumi di dati su cluster distribuiti.<\/p>\n\n\n\n

Sistemi distribuiti<\/h2>\n\n\n\n

Nell’architettura dei sistemi distribuiti, i modelli di comunicazione asincroni sono fondamentali per ottenere modularit\u00e0, scalabilit\u00e0 e resilienza.<\/p>\n\n\n\n

A livello infrastrutturale, l\u2019asincronia viene introdotta disaccoppiando i servizi e consentendo una comunicazione senza blocchi tra di loro; l’intera soluzione acquisisce robustezza e solitamente consente un graduale degrado che mette al riparo all’indisponibilit\u00e0 del servizio.<\/p>\n\n\n\n

Una delle motivazioni principali alla base della creazione di sistemi complessi utilizzando servizi disaccoppiati risiede in una miglior gestione della complessit\u00e0. Suddividendo le applicazioni monolitiche in componenti pi\u00f9 piccoli, possiamo semplificare lo sviluppo, facilitare l’implementazione indipendente e promuovere l’agilit\u00e0 in risposta ai requisiti in evoluzione. Inoltre, i servizi di disaccoppiamento possono ridurre il rischio di compromissione dell\u2019integrit\u00e0 del sistema complessivo.<\/p>\n\n\n\n

Vale anche la pena notare che alcune funzionalit\u00e0 sono naturalmente meglio sviluppate utilizzando servizi di disaccoppiamento o asincroni, quindi un’applicazione complessa pu\u00f2 essere composta da alcuni servizi disaccoppiati per il funzionamento asincrono.<\/p>\n\n\n\n

Al centro del disaccoppiamento si trova il concetto di comunicazione asincrona, che funge da fulcro per orchestrare le interazioni tra servizi distribuiti.<\/p>\n\n\n\n

Esistono molti modelli asincroni diversi, come code di messaggi, sistemi di pubblicazione-sottoscrizione e architetture guidate dagli eventi. Questi forniscono le basi per la costruzione di sistemi resilienti e disaccoppiati.<\/p>\n\n\n\n

Le code di messaggi, ad esempio, consentono ai servizi di comunicare in modo asincrono disaccoppiando la produzione e il consumo di messaggi. Ci\u00f2 consente ai servizi di operare in modo indipendente, elaborando i messaggi secondo il proprio ritmo senza essere strettamente vincolati alla disponibilit\u00e0 o alla reattivit\u00e0 di altri componenti.<\/p>\n\n\n\n

Allo stesso modo, i bus degli eventi facilitano la comunicazione liberamente accoppiata consentendo ai servizi di pubblicare e iscriversi a eventi di interesse. Questo paradigma basato sugli eventi promuove flessibilit\u00e0 ed estensibilit\u00e0, permettendo ai servizi di reagire ai cambiamenti dello stato del sistema o agli eventi esterni.<\/p>\n\n\n\n

Molti vantaggi sono legati al disaccoppiamento dei servizi e possono essere ottenuti costruendo sistemi distribuiti asincroni. Il pi\u00f9 importante \u00e8 che la comunicazione asincrona isola e mitiga i guasti a cascata<\/strong> in modo che un guasto in un servizio non si propaghi necessariamente ad altri, riducendo al minimo l’impatto sull’intero sistema.<\/p>\n\n\n\n

Di solito, se un sottoinsieme di microservizi non \u00e8 integro, solo alcune funzioni dell’intero sistema non funzioneranno correttamente. Se l’applicazione \u00e8 progettata per un graduale degrado, quindi, c’\u00e8 la possibilit\u00e0 che l’utente non debba mai fare nulla per far fronte al problema. Ad esempio, quando si utilizza una coda per disaccoppiare un carrello dal servizio ordini, un problema temporaneo in quest’ultimo viene gestito automaticamente e l’utente finale subir\u00e0 un certo ritardo prima di ricevere la conferma dell’ordine. Nel frattempo pu\u00f2 ancora vedere il suo ordine in coda. Viene quindi preso in carico correttamente, anche se necessita ancora di essere confermato.<\/p>\n\n\n\n

Un altro vantaggio significativo \u00e8 che il disaccoppiamento e l\u2019asincronia consentono ai servizi di scalare in modo indipendente, garantendo prestazioni ottimali in condizioni di traffico variabili.<\/p>\n\n\n\n

Tutto ci\u00f2 porta ad una migliore esperienza utente in termini di tempi di risposta pi\u00f9 rapidi, aggiornamenti in tempo reale e interazioni pi\u00f9 fluide.<\/p>\n\n\n\n

Naturalmente, l\u2019interazione dell\u2019utente deve essere considerata e progettata attentamente per garantire un\u2019esperienza fluida e piacevole, soprattutto perch\u00e9 le richieste vengono elaborate in modo asincrono e i loro effetti diventano visibili in seguito. Creare un’esperienza utente non frustrante richiede scelte di progettazione ponderate e una profonda comprensione delle aspettative e dei comportamenti degli utenti.<\/p>\n\n\n\n

Una strategia efficace \u00e8 fornire un feedback progressivo e supportare la trasparenza sullo stato delle operazioni in corso. Invece di lasciare gli utenti all’oscuro mentre le loro richieste vengono elaborate, le applicazioni dovrebbero mostrare un feedback chiaro e tempestivo, indicando che il sistema ha riconosciuto il loro input e sta lavorando attivamente per processarlo.<\/p>\n\n\n\n

Ci\u00f2 pu\u00f2 essere ottenuto attraverso indicatori visivi, come spinner di caricamento, barre di avanzamento o messaggi di stato che informano gli utenti sullo stato di avanzamento delle loro richieste e raccolgono gli stati in un report di facile lettura. L’utente potr\u00e0 procedere tranquillamente nella sua navigazione quando la sua richiesta sar\u00e0 stata correttamente inoltrata e sar\u00e0 passata in fase di elaborazione. Naturalmente, non \u00e8 sempre possibile rendere la risposta indipendente e liberare l’utente, ma quando lo \u00e8, \u00e8 molto meglio riconoscere la richiesta e dare all’utente un modo conveniente per verificare lo stato senza essere costretto ad aspettare.<\/p>\n\n\n\n

La gestione efficace degli errori \u00e8 un altro aspetto critico della progettazione di esperienze user-friendly nei sistemi asincroni. Poich\u00e9 le operazioni asincrone potrebbero riscontrare errori o guasti durante l’esecuzione, \u00e8 essenziale anticipare potenziali problemi e fornire agli utenti un feedback chiaro e utilizzabile quando si verificano.<\/p>\n\n\n\n

Invece di presentare agli utenti messaggi di errore generici o termini tecnici, le applicazioni dovrebbero sforzarsi di comunicare gli errori in un linguaggio comprensibile all\u2019utente che trasmetta la natura del problema e suggerisca passaggi attuabili per la risoluzione. Inoltre, fornire indicazioni contestuali o collegamenti a risorse di aiuto pertinenti pu\u00f2 consentire agli utenti di risolvere i problemi in modo indipendente e ridurre la frustrazione.<\/p>\n\n\n\n

Conclusioni<\/h2>\n\n\n\n

In questo articolo ci siamo tuffati nel campo dell’asincronia e del disaccoppiamento. Abbiamo discusso l’argomento a diversi livelli applicativi, arrivando ad analizzare anche l’impatto che tutto ci\u00f2 pu\u00f2 avere sull’esperienza utente. <\/p>\n\n\n\n

Se avete considerazioni in merito, non esisteate a scriverci!<\/p>\n\n\n\n

Nel frattempo, se volete approfondire le tecniche menzionate nell’articolo, ecco qualche link che fa per voi:<\/p>\n\n\n\n