Architettura basata su eventi con Pub/Sub

Questo documento illustra le differenze tra le architetture on-premise basate sulla coda di messaggi e le architetture basate su cloud e basate su eventi che vengono implementate su Pub/Sub. Provare ad applicare pattern on-premise direttamente a tecnologie basate su cloud può perdere il valore unico che rende il cloud davvero avvincente.

Questo documento è destinato agli architetti di sistema che stanno eseguendo la migrazione di progetti da architetture on-premise a progettazioni 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 coda di messaggi e di un modello Pub/Sub.

Confronta l'architettura di un modello coda di messaggi con un modello basato su eventi utilizzando Pub/Sub.

Nel diagramma precedente, un modello di coda di messaggi viene confrontato con un modello di flussi di eventi Pub/Sub. In un modello con coda di messaggi, il publisher esegue il push dei messaggi a una coda in cui ogni sottoscrittore può ascoltare una determinata coda. Nel modello di flusso di eventi che utilizza Pub/Sub, il publisher esegue il push dei messaggi a un argomento che possono essere ascoltati da più sottoscrittori. Le differenze tra questi modelli sono descritte nelle sezioni seguenti.

Confronto tra flussi di eventi e messaggistica basata su code

Se lavori con sistemi on-premise, hai già dimestichezza con i bus di servizi aziendali (ESB) e le code di messaggi. I flussi di eventi hanno un nuovo modello e presentano differenze importanti tra i vantaggi concreti per i sistemi in tempo reale moderni.

Questo documento illustra le principali differenze nel meccanismo di trasporto e nei dati payload nell'architettura basata su 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 meccanica sottostante che trasporta i messaggi dall'editore al destinatario. Nei framework di messaggi on-premise, il sistema di origine emette un messaggio esplicito, remoto e disaccoppiato in un sistema di elaborazione downstream utilizzando una coda di messaggi come trasporto.

Il seguente diagramma mostra un modello di coda di messaggi:

I messaggi di un editore vengono inviati a una coda univoca per ciascun sottoscrittore.

Nel diagramma precedente, i messaggi passano da un processo dell'editore a monte a un processo di sottoscrittore downstream utilizzando una coda di messaggi.

Il sistema A (l'editore) invia un messaggio a una coda sul broker di messaggi designato per il sistema B (sottoscrittore). Sebbene il sottoscrittore della coda possa essere composto da più client, tutti questi client sono istanze duplicate del sistema B di cui viene eseguito il deployment per la scalabilità e la disponibilità. Se altri processi downstream, ad esempio il sistema C, devono consumare gli stessi messaggi del produttore (sistema A), è necessaria una nuova coda. Devi aggiornare il producer per pubblicare i messaggi nella nuova coda. Questo modello è spesso indicato come trasmissione di messaggi.

Il livello di trasporto dei messaggi per queste code potrebbe o meno fornire garanzie relative agli ordini dei messaggi. Spesso, si prevede che le code di messaggi forniscano un modello garantito dall'ordine con i dati sequenziati in un modello di accesso FIFO (first-in-first-out), simile a una coda di attività. Questo modello è inizialmente facile da implementare, ma alla fine presenta problemi di scalabilità e operative. 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à dei servizi perché è un single point of failure.

I broker di messaggistica in queste architetture tendono a implementare logica aggiuntiva, come il monitoraggio di quale sottoscrittore ha ricevuto determinati messaggi e il monitoraggio del carico dei sottoscrittori. Gli abbonati tendono a essere solo reattivi, che non conoscono il sistema generale ed eseguono semplicemente una funzione alla ricezione del messaggio. Questi tipi di architetture sono chiamati smart pipe (sistema della coda di messaggi) ed endpoint fittizi (sottoscrittore).

Trasporto Pub/Sub

Analogamente ai sistemi orientati ai messaggi, anche i sistemi di flussi di eventi trasportano i messaggi da un sistema di origine a sistemi di destinazione disaccoppiati. Tuttavia, piuttosto che inviare ciascun messaggio a una coda mirata al processo, i sistemi basati su eventi tendono a pubblicare messaggi in un argomento condiviso, quindi uno o più destinatari si iscrivono a quell'argomento per ascoltare i messaggi pertinenti.

Il seguente diagramma mostra come i vari messaggi vengono emessi da un publisher upstream in un singolo argomento e quindi indirizzati al sottoscrittore a valle pertinente:

I messaggi di un editore vengono inviati a un singolo argomento per tutti gli iscritti.

Questo modello di pubblicazione e sottoscrizione è la fonte del termine pub/sub. Questo pattern è anche la base per il prodotto Google Cloud chiamato Pub/Sub. In questo documento, pubsub fa riferimento al pattern e Pub/Sub fa riferimento al prodotto.

Nel modello Pub/Sub, il sistema di messaggistica non ha bisogno di conoscere nessuno dei sottoscrittori. Non tiene traccia dei messaggi ricevuti e non gestisce il carico sul processo di utilizzo. Al contrario, i sottoscrittori monitorano i messaggi ricevuti e sono responsabili della gestione autonoma dei livelli di carico e della scalabilità.

Un vantaggio significativo è che, quando incontri nuovi utilizzi dei dati nel modello Pub/Sub, non devi aggiornare il sistema di origine per pubblicare in nuove code o dati duplicati. Puoi invece collegare il nuovo consumer a un nuovo abbonamento senza alcun impatto sul sistema esistente.

Le chiamate nei sistemi di flussi di eventi sono quasi sempre asincrone, inviano eventi e non attendono alcuna risposta. Gli eventi asincroni offrono maggiori opzioni di scalabilità sia per il produttore che per i consumatori. Tuttavia, questo pattern asincrono può creare verifiche se ti aspetti garanzie per gli ordini dei messaggi FIFO.

Dati della coda dei messaggi

I dati trasferiti tra i sistemi nei sistemi a coda di messaggi e in quelli basati su Pub/Sub vengono generalmente definiti messaggio in entrambi i contesti. Tuttavia, il modello in cui vengono presentati i dati è diverso. Nei sistemi con coda di messaggi, i messaggi riflettono un comando pensato per modificare lo stato dei dati downstream. Se esamini i dati per i sistemi on-premise di coda di messaggi, il publisher potrebbe dichiarare 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 impegnativo perché il produttore deve comprendere la logica di business di ciascun consumatore e creare strutture di messaggi distinte 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 muoverti più velocemente, ampliare la portata e innovare più di prima, questi sistemi centralizzati possono diventare un collo di bottiglia perché il cambiamento è rischioso e lento.

Questo modello presenta anche 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 su cosa impostare il valore corretto perché non hai alcun riferimento allo stato precedente. Non disponi di informazioni sul valore dell'inventario, ossia 3000 o 4000 prima dell'invio del messaggio.

Dati Pub/Sub

Gli eventi rappresentano un altro modo per inviare i dati dei messaggi. La caratteristica unica è che i sistemi basati su eventi si concentrano sull'evento che si è verificato piuttosto che sul risultato che dovrebbe verificarsi. Anziché inviare i dati che indicano quale azione dovrebbe intraprendere un consumatore, si concentrano sui dettagli dell'effettivo evento generato. Puoi implementare sistemi basati su eventi su una varietà di piattaforme, ma questi vengono spesso utilizzati sui sistemi basati su Pub/Sub.

Ad esempio, un evento dell'inventario potrebbe avere il seguente aspetto:

{ "inventory":-1 }

I dati sugli eventi precedenti indicano che si è verificato un evento che ha diminuito l'inventario di 1. I messaggi sono incentrati sull'evento che si è verificato in passato e non su uno stato da modificare nel futuro. I publisher sono in grado di inviare messaggi in modo asincrono, semplificando la scalabilità dei sistemi basati su eventi rispetto ai modelli della coda di messaggi. Nel modello Pub/Sub, puoi disaccoppiare la logica di business in modo che il producer debba solo comprendere le azioni eseguite al suo interno e non debba comprendere i processi downstream. Gli abbonati ai 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 ulteriori informazioni perché puoi annullare il valore dell'inventario per spostarlo nella direzione opposta. I messaggi in ritardo o fuori ordine non sono più un problema.

Confronto modelli

In questo scenario, hai quattro articoli dello stesso prodotto nel tuo inventario. Un cliente restituisce un numero di prodotti, mentre il cliente successivo ne acquista tre. Per questo scenario, supponiamo che il messaggio per il prodotto restituito sia stato ritardato.

La tabella seguente confronta il livello di inventario del modello coda di messaggi che riceve il conteggio dell'inventario nell'ordine corretto e lo stesso modello riceve il conteggio dell'inventario in ordine errato:

Coda dei messaggi (ordine corretto) Coda dei messaggi (non disponibile)
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 coda di messaggi, l'ordine in cui i messaggi vengono ricevuti è importante perché contengono il valore precalcolato. In questo esempio, se i messaggi arrivano nell'ordine corretto, il livello dell'inventario è 2. Tuttavia, se i messaggi non arrivano nell'ordine corretto, il livello dell'inventario è 5, che non è accurato.

La seguente tabella confronta 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 in ordine errato:

Pub/Sub (ordine corretto) Pub/Sub (non disponibile)
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 è rilevante perché è influenzato da servizi che producono eventi. Indipendentemente dall'ordine in cui arrivano i messaggi, il livello dell'inventario è preciso.

Il seguente diagramma mostra come, nel modello Coda di messaggi, la coda esegue comandi che indicano al sottoscrittore come dovrebbe cambiare lo stato, mentre nel modello Pub/Sub i sottoscrittori reagiscono ai dati degli eventi che indicano cosa è successo nel publisher:

Esempio di pagamento che confronta la reazione ai comandi con quella agli eventi.

Implementazione di architetture basate su eventi

Quando implementi architetture basate su eventi, devi prendere in considerazione una serie di concetti. Nelle sezioni seguenti vengono presentati alcuni di questi argomenti.

Garanzie di consegna

Uno dei concetti che emergono in una discussione di sistema è l'affidabilità delle garanzie di consegna dei messaggi. Fornitori e sistemi diversi possono fornire livelli di affidabilità diversi, quindi è importante capire le variazioni.

Il primo tipo di garanzia pone una domanda semplice: se un messaggio viene inviato ha la certezza che verrà consegnato? Si tratta della cosiddetta consegna "atleast-once". È garantito che il messaggio venga recapitato almeno una volta, ma potrebbe essere inviato più di una volta.

Un altro tipo di garanzia è la consegna "at-most-once". Con la consegna "Al massimo una volta", il messaggio viene recapitato al massimo una volta, ma non vi è alcuna garanzia che venga effettivamente recapitato.

La variante finale per le garanzie di pubblicazione è la pubblicazione "exactly-once". In questo modello, il sistema invia una sola copia del messaggio di cui è garantita la consegna.

Ordine e duplicati

Nelle architetture on-premise, i messaggi spesso seguono un modello FIFO. Per ottenere questo modello, un sistema di elaborazione centralizzato gestisce la sequenza dei messaggi per garantire un ordinamento preciso. I messaggi ordinati creano verifiche perché, per qualsiasi messaggio non riuscito, tutti i messaggi devono essere inviati di nuovo in sequenza. Qualsiasi sistema centralizzato può diventare una sfida per la disponibilità e la scalabilità. La scalabilità di un sistema centrale che gestisce gli ordini è in genere raggiungibile solo aggiungendo più risorse a una macchina esistente. Con un singolo sistema che gestisce l'ordine, qualsiasi problema di affidabilità ha un impatto sull'intero sistema anziché solo su quella macchina.

I servizi di messaggistica altamente scalabili e disponibili spesso utilizzano più sistemi di elaborazione per garantire che i messaggi vengano recapitati almeno una volta. In molti sistemi la gestione dell'ordine dei messaggi non è garantita.

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 windowing, ma questo approccio sacrifica la scalabilità e la disponibilità in quel componente.

Tecniche di filtro e fanout

Poiché un flusso di eventi può contenere dati che potrebbero essere o meno necessari per ogni sottoscrittore, è spesso necessario limitare i dati ricevuti da un determinato abbonato. Esistono due pattern per la gestione di questo requisito: i filtri per gli eventi e i fanout degli eventi.

Il seguente diagramma mostra un sistema basato su eventi con filtri di eventi che filtrano i messaggi per i sottoscrittori:

Modello basato su eventi con filtro eventi che filtra i messaggi in base ai sottoscrittori.

Nel diagramma precedente, i filtri di eventi utilizzano meccanismi di filtro che limitano gli eventi che raggiungono il sottoscrittore. In questo modello, un singolo argomento contiene tutte le varianti di un messaggio. Invece di un sottoscrittore che legge ogni messaggio e verificare se è applicabile, la logica di filtro nel sistema di messaggistica valuta il messaggio, impedendolo agli altri sottoscrittori.

Il seguente diagramma mostra una variante del pattern di filtro degli eventi, chiamato fanout degli eventi, che utilizza più argomenti:

Modello basato su eventi con fanout degli eventi che ripubblica i messaggi sugli argomenti.

Nel diagramma precedente, l'argomento principale contiene tutte le varianti di un messaggio, ma un meccanismo di fanout degli eventi ripubblica i messaggi su argomenti correlati a quel sottoinsieme di sottoscrittori.

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 messaggi continua a fornire un messaggio a un sottoscrittore finché non viene confermato dal sottoscrittore.

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 confermare il messaggio. Il sistema può non riuscire a gestire lo scenario o persino terminare il processo.

Solitamente i sistemi provano a riprovare i messaggi non confermati o con errori. Se un messaggio non valido non viene confermato dopo un periodo di tempo prestabilito, alla fine scade e viene eliminato dall'argomento. Dal punto di vista operativo, è utile esaminare i messaggi invece che sparire. È qui che entrano in gioco le code di messaggi non elaborati. Anziché rimuovere il messaggio dall'argomento, il messaggio viene spostato in un altro argomento, dove può essere rielaborato o rivisto per capire perché è stato eliminato.

Cronologia dello stream e repliche

I flussi di eventi sono flussi di dati continui. L'accesso a questi dati storici è utile. Potresti voler sapere come un sistema ha raggiunto un determinato stato. Potresti avere domande relative alla sicurezza che richiedono un controllo dei dati. Essere in grado di acquisire un log storico degli eventi è fondamentale nelle operazioni a lungo termine di un sistema basato su eventi.

Un uso comune dei dati storici sugli eventi è quello di utilizzarli con un sistema di riproduzione. Le repliche vengono utilizzate a scopo di test. La riproduzione dei dati sugli eventi di produzione in altri ambienti, come quelli in fase e test, ti consente di convalidare nuove funzionalità in base a set di dati reali. Puoi anche riprodurre di nuovo i dati storici per ripristinare lo stato non riuscito. Se un sistema non funziona o perde dati, i team possono riprodurre di nuovo la cronologia eventi da un momento noto e il servizio può ripristinare lo stato in cui ha perso.

L'acquisizione di questi eventi in code basate su log o flussi di logging è utile anche quando i sottoscrittori hanno bisogno di accedere a una sequenza di eventi in momenti diversi. I flussi di logging possono essere visualizzati in sistemi con funzionalità offline. Utilizzando la cronologia dei flussi, puoi elaborare le nuove voci più recenti leggendo lo stream a partire dal puntatore dell'ultima lettura.

Visualizzazioni di dati: in tempo reale e quasi in tempo reale

Ora che tutti i dati passano attraverso i sistemi, è importante che tu sia in grado di utilizzarli. Esistono molte tecniche per accedere a questi flussi di eventi e utilizzarli, ma un caso d'uso comune consiste nel comprendere lo stato generale dei dati in un momento specifico. Si tratta spesso di domande orientate al calcolo, ad esempio "quanti" o "livello attuale" possono essere utilizzate da altri sistemi o per il consumo umano. Esistono molteplici implementazioni che possono rispondere a queste domande:

  • Un sistema in tempo reale può essere eseguito senza interruzioni e tenere traccia dello stato attuale; tuttavia, se il sistema dispone solo di un calcolo in memoria, per qualsiasi tempo di inattività il calcolo viene impostato su zero.
  • Il sistema può calcolare i valori dalla tabella della cronologia per ogni richiesta, ma questo può diventare un problema perché cercare di calcolare i valori per ogni richiesta mentre i dati crescono può alla fine diventare impossibile.
  • 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 quasi in tempo reale che in tempo reale. Ad esempio, la pagina di un prodotto su un sito di e-commerce può utilizzare le visualizzazioni quasi in tempo reale dei dati di inventario. Quando i clienti effettuano gli ordini, viene utilizzato un servizio in tempo reale per garantire aggiornamenti rapidi sullo stato dei dati di inventario. Per implementare questo pattern, il servizio risponde alle richieste quasi in tempo reale da una tabella di snapshot che contiene valori calcolati in un determinato intervallo. Una richiesta in tempo reale utilizza sia la tabella snapshot sia i valori nella tabella della cronologia dall'ultimo snapshot per ottenere l'esatto stato attuale. Queste viste materializzate dei flussi di eventi forniscono dati strategici per generare processi aziendali reali.

Passaggi successivi