Architettura basata su eventi con Pub/Sub

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 sistemi che stanno eseguendo la migrazione dei progetti da on-premise a progetti basati 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.

Confronta l'architettura di un modello di 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 stream di eventi Pub/Sub. In un modello di coda di messaggi, l'editore inserisce i messaggi in una coda nella quale 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 a livello di meccanismo di trasporto e i dati del payload in un'architettura basata su eventi.

Trasporto dei messaggi

I sistemi che muovono i dati all'interno di questi modelli sono chiamati broker dei 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, remoto e disaccoppiato a un sistema di elaborazione downstream usando una coda di messaggi come trasporto.

Il seguente diagramma mostra un modello di coda di messaggi:

I messaggi di un publisher vengono inviati a una coda univoca per ogni iscritto.

Nel diagramma precedente, i messaggi fluiscono da un publisher a monte al processo del 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). Mentre il sottoscrittore della coda può formati da più client, che sono tutti istanze duplicate Sistema B di cui viene eseguito il deployment per la scalabilità e la disponibilità. Se aggiuntivo i processi downstream, ad esempio il sistema C, devono consumare gli stessi messaggi dal producer (sistema A), è necessaria una nuova coda. Devi aggiornare il produttore per pubblicare i messaggi nella nuova coda. Questo modello è spesso definito come trasmissione di un messaggio.

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 modello con ordini garantiti con dati sequenziati secondo un rigoroso ordine di precedenza (FIFO) di accesso ai dati, simile a una coda di attività. Questo pattern è inizialmente facile da implementare, ma presenta in seguito problemi di scalabilità e operatività. A di implementare messaggi ordinati, il sistema ha bisogno di un processo centrale per organizzare e i dati di Google Cloud. Questo processo limita le capacità di scalabilità e riduce la disponibilità del servizio perché è un single point of failure.

I broker di messaggi in queste architetture tendono a implementare logiche aggiuntive ad esempio l'identificazione del sottoscrittore che ha ricevuto i messaggi e il monitoraggio di iscritti. Gli iscritti tendono a essere semplicemente reattivi, non possiedono del sistema nel suo complesso ed eseguire semplicemente una funzione su un messaggio ricevuta. 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é che inviare ciascun messaggio a una coda mirata a un processo, i sistemi basati su eventi tendono pubblicare messaggi in un argomento condiviso e poi uno o più destinatari sottoscrivono a quell'argomento per ascoltare 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:

I messaggi di un publisher vengono inviati a un singolo argomento per tutti gli abbonati.

Da questo modello di pubblicazione e sottoscrizione deriva il termine pub/sub. Questo pattern è anche la base per il prodotto Google Cloud 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 ha bisogno di conoscere 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, quando scopri nuovi utilizzi dei dati nel modello Pub/Sub, non devi aggiornare il sistema di origine per pubblicare code o dati duplicati. Invece, colleghi il nuovo consumatore a un nuovo 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 maggiori opzioni di scalabilità 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 dei 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 i dati downstream. Se esamini i dati per i 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 difficile perché il produttore deve comprendere la logica di business di ciascun consumatore e creare strutture di messaggi separate per diversi casi d'uso. Questo sistema di coda dei messaggi era una pratica comune con i monoliti di grandi dimensioni che la maggior parte delle aziende implementate. Tuttavia, se vuoi avanzare più velocemente, ampliare la portata e innovare rispetto a prima, questi sistemi centralizzati possono diventare un collo di bottiglia perché il cambiamento è rischioso e lento.

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 Pub/Sub

Gli eventi sono un altro modo per inviare i dati dei messaggi. La caratteristica unica è che sono basati su eventi sistemi di controllo si concentrano sull'evento che si è verificato piuttosto che sul risultato che dovrebbe che si verificano. Anziché inviare dati che indicano l'azione che un consumatore deve intraprendere, i dati si concentrano sui dettagli dell'evento effettivo prodotto. Puoi implementare basati su eventi su varie piattaforme, ma vengono spesso 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, il che rende più facile scalare i 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 producer capisca solo le azioni eseguite non ha bisogno di comprendere i processi downstream. 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, sono necessarie ulteriori informazioni perché puoi annullare il valore dell'inventario 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 acquista tre conteggi dello stesso prodotto. In questo scenario, supponiamo che il messaggio del prodotto restituito ha subito un 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 dei 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 i messaggi vengono ricevuti è importante perché il messaggio contiene il valore precalcolato. In questo Ad 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, ovvero inesatte.

La tabella seguente mette a confronto il livello dell'inventario del sistema basato su pubsub ricevere il conteggio dell'inventario nell'ordine corretto con lo stesso sistema ricezione del conteggio dell'inventario non in 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 pubsub, l'ordine dei messaggi non è importante perché è informato dai servizi che producono eventi. Indipendentemente dall'ordine di arrivo dei messaggi, il livello di inventario è accurato.

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:

Esempio di pagamento che confronta le reazioni ai comandi con le reazioni agli eventi.

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? Questo è ciò che viene definito invio almeno una volta. È garantito che il messaggio venga recapitato almeno una volta, ma potrebbero essere inviate più di una volta.

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 raggiungere questo modello, un sistema di elaborazione centralizzato gestisce la sequenza dei messaggi per garantire la precisione degli ordini. 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, i problemi di affidabilità interessano l'intero sistema e non solo quella 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, la gestione dell'ordine dei messaggi non può essere 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 finestre; tuttavia, questo approccio sacrifica la scalabilità e la disponibilità in quel componente.

Tecniche di filtro 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: filtri di eventi e fanout degli eventi.

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

Modello basato su eventi con filtro eventi che filtra i messaggi per i sottoscrittori.

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 varianti di un messaggio. Invece di un sottoscrittore che legge ogni messaggio verificare se è applicabile, la logica di filtro nel sistema di messaggistica valuta il messaggio, conservandolo dagli altri sottoscrittori.

Il seguente diagramma mostra una variante del pattern del filtro degli eventi chiamata fanout dell'evento 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 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 tecnica per gestire tali 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.

I sistemi in genere riprovano 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

I flussi di eventi sono flussi continui di dati. L'accesso a questi dati storici è utile. Potrebbe essere utile sapere in che modo un sistema ha ottenuto 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 storici sugli eventi è quello di utilizzarli con un sistema di ripetizione. Le repliche 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 smette di funzionare o perde dati, i team possono riprodurre l'evento la cronologia da un punto noto e il servizio può ricreare lo stato hanno perso.

È utile anche acquisire questi eventi in code o flussi di log basati su log Quando gli abbonati hanno bisogno di accedere a una sequenza di eventi in momenti diversi. Logging i flussi possono essere visualizzati nei sistemi con funzionalità offline. Utilizzando la cronologia dello stream, puoi elaborare le nuove voci più recenti 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 a questi stream di eventi e utilizzarli, ma un caso d'uso comune è comprendere lo stato complessivo dei dati in un determinato momento. Si tratta spesso di domande orientate al calcolo, come "quanti" o "livello attuale" che può essere utilizzato da altri sistemi o per consumo umano. Esistono diverse implementazioni per risolvere queste domande:

  • Un sistema in tempo reale può funzionare in modo continuo e tenere traccia dello stato attuale state; Tuttavia, poiché il sistema ha solo un calcolo in memoria, qualsiasi il tempo di inattività imposta il calcolo su zero.
  • Il sistema può calcolare i valori della tabella della cronologia per ogni richiesta, ma questo può diventare un problema perché il tentativo di calcolare i valori per ogni richiesta mentre i dati aumentano può 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 è Architettura di Lambda con funzionalità quasi in tempo reale e in tempo reale. Ad esempio, la pagina di un prodotto su un sito di e-commerce possono usare 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 che contiene per i 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 viste materializzate dei flussi di eventi forniscono dati strategici per produrre processi aziendali reali.

Passaggi successivi