Questo documento illustra le differenze tra le architetture on-premise basate su code di messaggi e le architetture basate su eventi e su cloud implementate su Pub/Sub. Se provi ad applicare i pattern on-premise direttamente alle tecnologie basate su cloud, potresti perderti il valore unico che rende il cloud così interessante.
Questo documento è rivolto agli architetti di sistema che eseguono la migrazione dei progetti dalle architetture on-premise a quelle basate su cloud. Questo documento presuppone che tu abbia una conoscenza introduttiva dei sistemi di messaggistica.
Il seguente diagramma mostra una panoramica di un modello di coda di messaggi e di un modello Pub/Sub.
Nel diagramma precedente, un modello di coda di messaggi viene confrontato con un modello di stream di eventi Pub/Sub. In un modello di coda di messaggi, il publisher invia i messaggi a una coda in cui ogni sottoscrittore può ascoltare una determinata coda. Nel modello di stream di eventi che utilizza Pub/Sub, il publisher invia messaggi a un argomento che può essere ascoltato da più sottoscrittori. Le differenze tra questi modelli sono descritte nelle sezioni seguenti.
Confronto tra stream di eventi e messaggistica basata su coda
Se utilizzi sistemi on-premise, hai già familiarità con gli enterprise service bus (ESB) e le code queue. Gli stream di eventi sono un nuovo pattern e presentano differenze importanti con vantaggi concreti per i sistemi moderni in tempo reale.
Questo documento illustra le principali differenze nel meccanismo di trasporto e nei dati del payload nell'architettura basata sugli eventi.
Trasporto dei messaggi
I sistemi che spostano i dati in questi modelli sono chiamati broker di messaggi e al loro interno sono implementati vari framework. Uno dei primi concetti è la funzionalità di base che trasporta i messaggi dal publisher al destinatario. Nei framework di messaggi on-premise, il sistema di origine emette un messaggio esplicito, decoupled e remoto a un sistema di elaborazione a valle utilizzando una coda di messaggi come trasporto.
Il seguente diagramma mostra un modello di coda di messaggi:
Nel diagramma precedente, i messaggi passano da un processo di publisher upstrea a un processo di sottoscrittore downstream utilizzando una coda di messaggi.
Il sistema A (il publisher) invia un messaggio a una coda sul broker di messaggi designata per il sistema B (il sottoscrittore). Sebbene l'abbonato alla coda possa essere costituito da più client, tutti questi client sono istanze duplicate del sistema B di cui è stato eseguito il deployment per il ridimensionamento e la disponibilità. Se altri processi a valle, ad esempio il sistema C, devono utilizzare gli stessi messaggi del produttore (sistema A), è necessaria una nuova coda. Devi aggiornare il produttore per pubblicare i messaggi nella nuova coda. Questo modello viene spesso definito passaggio di messaggi.
Il livello di trasporto dei messaggi per queste code potrebbe o meno fornire garanzie sull'ordine dei messaggi. Spesso, le code di messaggi devono fornire un modello con ordine garantito con dati sequenziati in un rigoroso modello di accesso first-in-first-out (FIFO), simile a una coda di attività. Questo pattern è inizialmente facile da implementare, ma presenta in seguito problemi di scalabilità e operatività. Per implementare i messaggi ordinati, il sistema ha bisogno di un processo centrale per organizzare i dati. Questo processo limita le funzionalità di scalabilità e riduce la disponibilità del servizio perché rappresenta un single point of failure.
I broker di messaggistica in queste architetture tendono a implementare una logica aggiuntiva, come il monitoraggio di quali sottoscrittori hanno ricevuto quali messaggi e il monitoraggio del carico dei sottoscrittori. Gli abbonati tendono ad essere semplicemente reattivi, non hanno conoscenza del sistema complessivo ed eseguono semplicemente una funzione al ricevimento del messaggio. Questo tipo di architetture è chiamato smart pipe (sistema di coda di messaggi) e endpoint stupidi (abbonati).
Trasporto Pub/Sub
Analogamente ai sistemi orientati ai messaggi, anche i sistemi di streaming di eventi trasportano i messaggi da un sistema di origine a sistemi di destinazione disaccoppiati. Tuttavia, anziché inviare ogni messaggio a una coda mirata al processo, i sistemi basati su eventi tendono a pubblicare i messaggi in un argomento condiviso, a cui uno o più destinatari si abbonano per ascoltare i messaggi pertinenti.
Il seguente diagramma mostra come vari messaggi vengono emessi da un publisher upstream in un singolo argomento e poi inoltrati al sottoscrittore downstream pertinente:
Da questo modello di pubblicazione e sottoscrizione deriva il termine pub/sub. Questo pattern è anche la base del Google Cloud prodotto chiamato Pub/Sub. In questo documento, pubsub si riferisce al pattern e Pub/Sub al prodotto.
Nel modello pub/sub, il sistema di messaggistica non deve conoscere nessuno degli abbonati. Non tiene traccia dei messaggi ricevuti e non gestisce il carico sul processo di consumo. Gli abbonati monitorano i messaggi ricevuti e sono responsabili della gestione autonoma dei livelli di carico e della scalabilità.
Un vantaggio significativo è che, man mano che trovi nuovi utilizzi per i dati nel modello pub/sub, non devi aggiornare il sistema di origine per pubblicare in nuove code o duplicare i dati. ma colleghi il nuovo consumatore a un nuovo abbonamento senza alcun impatto sul sistema esistente.
Le chiamate nei sistemi di streaming di eventi sono quasi sempre asincrone, inviano eventi e non aspettano alcuna risposta. Gli eventi asincroni consentono opzioni di scalabilità superiori sia per il produttore che per i consumatori. Tuttavia, questo schema asincrono può creare problemi se prevedi garanzie di ordine FIFO per i messaggi.
Dati della coda di messaggi
I dati trasmessi tra sistemi in sistemi di coda di messaggi e sistemi basati su pub/sub sono generalmente definiti messaggi in entrambi i contesti. Tuttavia, il modello in cui vengono presentati i dati è diverso. Nei sistemi di coda di messaggi, i messaggi riflettono un comando che ha lo scopo di modificare lo stato dei dati a valle. Se esamini i dati dei sistemi di coda di messaggi on-premise, l'editore potrebbe indicare esplicitamente cosa deve fare il consumatore. Ad esempio, un messaggio di inventario potrebbe indicare quanto segue:
<m:SetInventoryLevel>
<inventoryValue>3001</inventoryValue>
</m: SetInventoryLevel>
In questo esempio, il produttore comunica al consumatore che deve impostare il livello di inventario su 3001. Questo approccio può essere complicato perché il produttore deve comprendere la logica aziendale di ciascun consumatore e creare strutture di messaggi separate per casi d'uso diversi. Questo sistema di coda di messaggi era una pratica comune con i grandi monoliti implementati dalla maggior parte delle aziende. Tuttavia, se vuoi andare più veloce, scalare di più e innovare più di prima, questi sistemi centralizzati possono diventare un collo di bottiglia perché le modifiche sono rischiose e lente.
Anche questo modello presenta delle sfide operative. Quando si verificano dati errati, record duplicati o altri problemi che devono essere corretti, questo modello di messaggistica presenta una sfida significativa. Ad esempio, se devi eseguire il rollback del messaggio utilizzato nell'esempio precedente, non sai a quale valore impostare il valore corretto perché non hai riferimenti allo stato precedente. Non hai informazioni sul fatto che il valore dell'inventario fosse 3000 o 4000 prima dell'invio del messaggio.
Dati Pubsub
Gli eventi sono un altro modo per inviare i dati dei messaggi. La particolarità è che i sistemi basati su eventi si concentrano sull'evento che si è verificato anziché sul risultato che dovrebbe verificarsi. Invece di inviare dati che indicano l'azione che un consumatore deve intraprendere, i dati si concentrano sui dettagli dell'evento effettivo prodotto. Puoi implementare sistemi basati su eventi su una serie di piattaforme, ma sono spesso utilizzati su sistemi basati su pub/sub.
Ad esempio, un evento di inventario potrebbe avere il seguente aspetto:
{ "inventory":-1 }
I dati sugli eventi precedenti indicano che si è verificato un evento che ha ridotto l'inventario di 1. I messaggi si concentrano sull'evento che si è verificato nel passato e non su uno stato da modificare in futuro. I publisher sono in grado di inviare messaggi in modo asincrono, facilitando così la scalabilità dei sistemi basati su eventi rispetto ai modelli di coda di messaggi. Nel modello pub/sub, puoi disaccoppiare la logica di business in modo che il produttore debba comprendere solo le azioni eseguite su di essa e non debba comprendere le procedure a valle. Gli abbonati a questi dati possono scegliere il modo migliore per gestire i dati che ricevono. Poiché questi messaggi non sono comandi imperativi, l'ordine dei messaggi diventa meno importante.
Con questo pattern è più facile eseguire il rollback delle modifiche. In questo esempio non sono necessarie altre informazioni perché puoi negare il valore dell'inventario per spostarlo nella direzione opposta. I messaggi che arrivano in ritardo o non in ordine non sono più un problema.
Confronto modelli
In questo scenario, il tuo inventario contiene quattro articoli dello stesso prodotto. Un cliente restituisce un conteggio del prodotto e il cliente successivo ne acquista tre. Per questo scenario, supponiamo che il messaggio per il prodotto restituito sia in ritardo.
La tabella seguente confronta il livello di inventario del modello di coda di messaggi che riceve il conteggio dell'inventario nell'ordine corretto con lo stesso modello che riceve il conteggio dell'inventario fuori ordine:
Coda di messaggi (ordine corretto) | Coda di messaggi (non in ordine) |
---|---|
Inventario iniziale: 4 |
Inventario iniziale: 4 |
Messaggio 1: setInventory(5) |
Messaggio 2: setInventory(2) |
Messaggio 2: setInventory(2) |
Messaggio 1: setInventory(5) |
Livello dell'inventario: 2 |
Livello dell'inventario: 5 |
Nel modello di coda di messaggi, l'ordine in cui vengono ricevuti i messaggi è importante perché il messaggio contiene il valore precalcolato. In questo esempio, se i messaggi arrivano nell'ordine corretto, il livello di inventario è 2. Tuttavia, se i messaggi arrivano fuori sequenza, il livello di inventario è 5, il che è impreciso.
La tabella seguente mette a confronto il livello di inventario del sistema basato su Pub/Sub che riceve il conteggio dell'inventario nell'ordine corretto con lo stesso sistema che riceve il conteggio dell'inventario fuori ordine:
Pubsub (ordine corretto) | Pubsub (out of order) |
---|---|
Inventario iniziale: 4 | Inventario iniziale: 4 |
Messaggio 2: "inventory":-3 |
Messaggio 1: "inventory":+1 |
Messaggio 1: "inventory":+1 |
Messaggio 2: "inventory":-3 |
Livello dell'inventario: 2 |
Livello dell'inventario: 2 |
Nel sistema basato su Pub/Sub, l'ordine dei messaggi non è importante perché viene fornito dai servizi che producono eventi. Indipendentemente dall'ordine di arrivo dei messaggi, il livello di inventario è preciso.
Il seguente diagramma mostra come nel modello di coda di messaggi la coda esegua comandi che indicano all'abbonato come deve cambiare lo stato, mentre nel modello pub/sub gli abbonati reagiscono ai dati sugli eventi che indicano cosa è accaduto nel publisher:
Implementazione di architetture basate su eventi
Esistono diversi concetti da considerare quando si implementano architetture basate su eventi. Le sezioni seguenti introducono alcuni di questi argomenti.
Garanzie di consegna
Un concetto che emerge in una discussione sul sistema è l'affidabilità delle garanzie di recapito dei messaggi. Fornitori e sistemi diversi potrebbero fornire diversi livelli di affidabilità, pertanto è importante comprendere le variazioni.
Il primo tipo di garanzia pone una semplice domanda: se un messaggio viene inviato, è garantito che verrà recapitato? Si tratta di una pubblicazione almeno una volta. È garantito che il messaggio venga consegnato almeno una volta, ma potrebbe essere inviato più volte.
Un altro tipo di garanzia è la consegna al massimo una volta. Con la consegna al massimo una volta, il messaggio viene recapitato al massimo una volta, ma non vi sono garanzie che venga effettivamente recapitato.
La variazione finale per le garanzie di recapito è la consegna exactly-once. In questo modello, il sistema invia una sola copia del messaggio che è garantito che verrà recapitata.
Ordina e duplicati
Nelle architetture on-premise, i messaggi spesso seguono un modello FIFO. Per ottenere questo modello, un sistema di elaborazione centralizzato gestisce la sequenzialità dei messaggi per garantire un ordinamento accurato. I messaggi ordinati creano problemi perché per qualsiasi messaggio non riuscito, tutti i messaggi devono essere inviati di nuovo in sequenza. Qualsiasi sistema centralizzato può diventare un problema per la disponibilità e la scalabilità. In genere, la scalabilità di un sistema centrale che gestisce l'ordinamento è possibile solo aggiungendo più risorse a una macchina esistente. Con un unico sistema che gestisce l'ordine, eventuali problemi di affidabilità influiscono sull'intero sistema anziché solo sulla macchina.
I servizi di messaggistica altamente scalabili e disponibili utilizzano spesso più sistemi di elaborazione per garantire che i messaggi vengano recapitati almeno una volta. Con molti sistemi, non è possibile garantire la gestione dell'ordine dei messaggi.
Le architetture basate su eventi non si basano sull'ordine dei messaggi e possono tollerare messaggi duplicati. Se è richiesto l'ordine, i sottosistemi possono implementare tecniche di aggregazione e finestre; tuttavia, questo approccio sacrifica la scalabilità e la disponibilità in quel componente.
Tecniche di filtraggio e fanout
Poiché uno stream di eventi può contenere dati che potrebbero o meno essere necessari per ogni sottoscrittore, spesso è necessario limitare i dati ricevuti da un determinato sottoscrittore. Esistono due pattern per gestire questo requisito: i filtri evento e i fanout evento.
Il seguente diagramma mostra un sistema basato su eventi con filtri degli eventi che filtrano i messaggi per gli abbonati:
Nel diagramma precedente, i filtri di eventi utilizzano meccanismi di filtro che limitano gli eventi che raggiungono l'abbonato. In questo modello, un singolo argomento contiene tutte le varianti di un messaggio. Invece che un abbonato legga ogni messaggio e verifichi se è applicabile, la logica di filtro nel sistema di messaggistica valuta il messaggio e lo tiene lontano dagli altri abbonati.
Il seguente diagramma mostra una variante del pattern di filtro degli eventi chiamata espansione eventi che utilizza più argomenti:
Nel diagramma precedente, l'argomento principale contiene tutte le varianti di un messaggio, ma un meccanismo di distribuzione di eventi ripubblica i messaggi negli argomenti correlati a quel sottoinsieme di iscritti.
Code di messaggi non elaborati
Anche nei sistemi migliori possono verificarsi errori. Le code di messaggi non elaborati sono una tecnica per gestire questi errori. Nella maggior parte delle architetture basate su eventi, il sistema di messaggistica continua a fornire un messaggio a un sottoscrittore finché non viene confermato.
Se si verifica un problema con un messaggio, ad esempio caratteri non validi nel corpo del messaggio, il sottoscrittore potrebbe non essere in grado di confermarlo. Il sistema può non riuscire a gestire lo scenario o addirittura terminare il processo.
In genere, i sistemi riprovano a inviare i messaggi non confermati o con errori. Se un messaggio non valido non viene confermato dopo un periodo di tempo predeterminato, il messaggio scade e viene eliminato dall'argomento. Dal punto di vista operativo, è utile rivedere i messaggi anziché farli scomparire. È qui che entrano in gioco le code di messaggi non elaborati. Anziché rimuovere il messaggio dall'argomento, viene spostato in un altro argomento in cui può essere elaborato di nuovo o esaminato per capire il motivo dell'errore.
Cronologia e repliche degli stream
Gli stream di eventi sono flussi continui di dati. L'accesso a questi dati storici è utile. Potresti voler sapere in che modo un sistema ha raggiunto un determinato stato. Potresti avere domande relative alla sicurezza che richiedono un controllo dei dati. La possibilità di acquisire un log storico degli eventi è fondamentale per le operazioni a lungo termine di un sistema basato su eventi.
Un uso comune dei dati sugli eventi storici è utilizzarli con un sistema di riproduzione. Le ripetizioni vengono utilizzate a scopo di test. Riproducendo i dati sugli eventi di produzione in altri ambienti, come la fase di staging e il test, puoi convalidare le nuove funzionalità in base a set di dati reali. Puoi anche riprodurre i dati storici per recuperare da uno stato di fallimento. Se un sistema si arresta in modo anomalo o perde dati, i team possono riprodurre la cronologia degli eventi da un punto noto come valido e il servizio può ricostruire lo stato che ha perso.
La cattura di questi eventi in code o stream di log basati su log è utile anche quando gli abbonati devono accedere a una sequenza di eventi in momenti diversi. Gli stream di logging possono essere visualizzati nei sistemi con funzionalità offline. Utilizzando la cronologia dello stream, puoi elaborare le ultime voci leggendo lo stream a partire dal cursore last-read.
Visualizzazioni dei dati: in tempo reale e quasi in tempo reale
Poiché tutti i dati passano attraverso i sistemi, è importante che tu possa usarli. Esistono molte tecniche per accedere e utilizzare questi stream di eventi, ma un caso d'uso comune è comprendere lo stato complessivo dei dati in un determinato momento. Spesso si tratta di domande orientate al calcolo, ad esempio "quanti" o "livello corrente", che possono essere utilizzate da altri sistemi o per il consumo umano. Esistono diverse implementazioni che possono rispondere a queste domande:
- Un sistema in tempo reale può funzionare in modo continuo e tenere traccia dello stato corrente. Tuttavia, poiché il sistema dispone solo di un calcolo in memoria, qualsiasi tempo di riposo imposta il calcolo su zero.
- Il sistema può calcolare i valori dalla tabella della cronologia per ogni richiesta, ma questo può diventare un problema perché il tentativo di calcolare i valori per ogni richiesta man mano che i dati aumentano può diventare non fattibile.
- Il sistema può creare snapshot dei calcoli a intervalli specifici, ma l'utilizzo solo degli snapshot non riflette i dati in tempo reale.
Un pattern utile da implementare è un'architettura Lambda con funzionalità sia in tempo quasi reale che in tempo reale. Ad esempio, una pagina di prodotto su un sito di e-commerce può utilizzare visualizzazioni quasi in tempo reale dei dati di inventario. Quando i clienti effettuano ordini, viene utilizzato un servizio in tempo reale per garantire aggiornamenti dello stato dei dati dell'inventario aggiornati al secondo. Per implementare questo pattern, il servizio risponde alle richieste quasi in tempo reale da una tabella di snapshot contenente valori calcolati in un determinato intervallo. Una richiesta in tempo reale utilizza sia la tabella dello snapshot sia i valori nella tabella della cronologia dall'ultimo snapshot per ottenere lo stato corrente esatto. Queste visualizzazioni materializzate degli stream di eventi forniscono dati strategici per gestire processi aziendali reali.
Passaggi successivi
- Scopri di più su Pub/Sub o Cloud Tasks per il passaggio di messaggi e l'integrazione asincrona.
- Prova la guida rapida di Pub/Sub.
- Consulta la panoramica dell'architettura di Pub/Sub
- Esplora architetture di riferimento, diagrammi e best practice su Google Cloud. Consulta il nostro Cloud Architecture Center.