API Gateway (REST) ​​+ microservizi guidati da eventi


16

Ho un sacco di microservizi di cui espongo le funzionalità tramite un'API REST secondo il modello di gateway API. Poiché questi microservizi sono applicazioni Spring Boot, sto usando Spring AMQP per ottenere una comunicazione sincrona in stile RPC tra questi microservizi. Finora le cose sono andate bene. Tuttavia, più leggo sulle architetture di microservizi guidate dagli eventi e guardo a progetti come Spring Cloud Stream, più sono convinto che potrei fare le cose nel modo sbagliato con l'approccio sincrono RPC (soprattutto perché avrò bisogno di questo per scalare per rispondere a centinaia o migliaia di richieste al secondo dalle applicazioni client).

Capisco il punto dietro un'architettura guidata dagli eventi. Quello che non capisco bene è come utilizzare effettivamente un tale schema quando si è seduti dietro un modello (REST) ​​che si aspetta una risposta ad ogni richiesta. Ad esempio, se ho il mio gateway API come microservizio e un altro microservizio che memorizza e gestisce gli utenti, come potrei modellare una cosa come una GET /users/1in un modo puramente guidato da eventi?

Risposte:


9

Ripeti dopo di me:

Gli eventi REST e asincroni non sono alternative. Sono completamente ortogonali.

Puoi avere l'uno o l'altro, o entrambi, o nessuno dei due. Sono strumenti completamente diversi per domini problematici completamente diversi. In effetti, la comunicazione richiesta-risposta per scopi generici è assolutamente in grado di essere asincrona, guidata dagli eventi e tollerante ai guasti .


A titolo di esempio banale, il protocollo AMQP invia messaggi tramite una connessione TCP. In TCP, ogni pacchetto deve essere riconosciuto dal destinatario . Se un mittente di un pacchetto non riceve un ACK per quel pacchetto, continua a rinviare quel pacchetto fino a quando non viene ACK o fino a quando il livello dell'applicazione "si arrende" e abbandona la connessione. Si tratta chiaramente di un modello di richiesta-risposta non tollerante agli errori, poiché ogni "richiesta di invio di pacchetti" deve avere una "risposta di riconoscimento dei pacchetti" di accompagnamento e la mancata risposta comporta il fallimento dell'intera connessione. Tuttavia AMQP, un protocollo standardizzato e ampiamente adottato per la messaggistica asincrona con tolleranza agli errori, viene comunicato tramite TCP! Cosa dà?

Il concetto chiave qui in gioco è che i messaggi di tolleranza agli errori liberamente accoppiabili e scalabili sono definiti da quali messaggi si inviano , non da come li si invia . In altre parole, l' accoppiamento lento è definito a livello di applicazione .

Diamo un'occhiata a due parti che comunicano direttamente con RESTful HTTP o indirettamente con un broker di messaggi AMQP. Supponiamo che la Parte A desideri caricare un'immagine JPEG sulla Parte B che renderà più nitida, compressa o migliorata l'immagine. La parte A non necessita immediatamente dell'immagine elaborata, ma richiede un riferimento ad essa per un utilizzo e un recupero futuri. Ecco un modo che potrebbe andare in REST:

  • La parte A invia un POSTmessaggio di richiesta HTTP alla parte B conContent-Type: image/jpeg
  • La parte B elabora l'immagine (per molto tempo se è grande) mentre la parte A attende, possibilmente facendo altre cose
  • La parte B invia un 201 Createdmessaggio di risposta HTTP alla parte A con Content-Location: <url>un'intestazione che si collega all'immagine elaborata
  • La parte A considera il proprio lavoro svolto poiché ora ha un riferimento all'immagine elaborata
  • In futuro, quando Partito A ha bisogno l'immagine elaborata, si ottiene utilizzando il link dal precedente Content-Locationintestazione

Il 201 Createdcodice di risposta dice a un client che non solo la sua richiesta ha avuto successo, ma ha anche creato una nuova risorsa. In una risposta 201, l' Content-Locationintestazione è un collegamento alla risorsa creata. Questo è specificato nelle sezioni 6.3.2 e 3.1.4.2 di RFC 7231.

Ora, vediamo come funziona questa interazione su un ipotetico protocollo RPC su AMQP:

  • La parte A invia un broker di messaggi AMQP (chiamalo Messenger) un messaggio contenente l'immagine e le istruzioni per instradarlo alla parte B per l'elaborazione, quindi risponde alla parte A con un indirizzo di qualche tipo per l'immagine
  • La parte A aspetta, forse facendo altre cose
  • Messenger invia il messaggio originale della parte A alla parte B.
  • La parte B elabora il messaggio
  • La parte B invia a Messenger un messaggio contenente un indirizzo per l'immagine elaborata e le istruzioni per instradare quel messaggio alla parte A
  • Messenger invia alla parte A il messaggio della parte B contenente l'indirizzo dell'immagine elaborata
  • La parte A considera il proprio lavoro svolto poiché ora ha un riferimento all'immagine elaborata
  • Qualche volta in futuro quando la Parte A ha bisogno dell'immagine, recupera l'immagine usando l'indirizzo (possibilmente inviando messaggi a un'altra parte)

Vedi il problema qui? In entrambi i casi, la parte A non può ottenere un indirizzo immagine fino a quando la parte B non elabora l'immagine . Tuttavia, la Parte A non ha bisogno immediatamente dell'immagine e, a tutti i diritti, non potrebbe importare di meno se l'elaborazione è ancora terminata!

Possiamo risolverlo abbastanza facilmente nel caso AMQP facendo in modo che Party B dica ad A che B ha accettato l'immagine per l'elaborazione, fornendo ad A un indirizzo per dove sarà l'immagine dopo che l'elaborazione è stata completata. Quindi la Parte B può inviare A un messaggio in futuro indicando che l'elaborazione dell'immagine è terminata. Messaggistica AMQP in soccorso!

Tranne indovinare cosa: puoi ottenere la stessa cosa con REST . Nell'esempio AMQP abbiamo cambiato il messaggio "ecco l'immagine elaborata" in un messaggio "l'immagine è in elaborazione, è possibile ottenerla in un secondo momento". Per farlo in HTTP RESTful, useremo il 202 Acceptedcodice e di Content-Locationnuovo:

  • La parte A invia un POSTmessaggio HTTP alla parte B conContent-Type: image/jpeg
  • La parte B invia immediatamente una 202 Acceptedrisposta che contiene una sorta di contenuto di "operazione asincrona" che descrive se l'elaborazione è terminata e dove l'immagine sarà disponibile al termine dell'elaborazione. È inclusa anche Content-Location: <link>un'intestazione che, in una 202 Acceptedrisposta, è un collegamento alla risorsa rappresentata da qualunque sia il corpo della risposta. In questo caso, ciò significa che è un collegamento alla nostra operazione asincrona!
  • La parte A considera il proprio lavoro svolto poiché ora ha un riferimento all'immagine elaborata
  • Qualche volta in futuro, quando la Parte A ha bisogno dell'immagine elaborata, prima ottiene la risorsa dell'operazione asincrona collegata Content-Locationnell'intestazione per determinare se l'elaborazione è terminata. In tal caso, la Parte A utilizza quindi il collegamento nell'operazione asincrona stessa per OTTENERE l'immagine elaborata.

L'unica differenza qui è che nel modello AMQP, Party B dice a Party A quando l'elaborazione delle immagini è terminata. Ma nel modello REST, la Parte A verifica se l'elaborazione viene eseguita appena prima che abbia effettivamente bisogno dell'immagine. Questi approcci sono equivalentemente scalabili . Man mano che il sistema aumenta, il numero di messaggi inviati sia nelle strategie asincrone AMQP sia nelle strategie REST asincrone aumenta con una complessità asintotica equivalente. L'unica differenza è che il client sta inviando un messaggio aggiuntivo anziché il server.

Ma l'approccio REST ha qualche asso nella manica: scoperta dinamica e negoziazione del protocollo . Considera come sono iniziate le interazioni REST di sincronizzazione e asincrone. La parte A ha inviato la stessa identica richiesta alla parte B, con l'unica differenza che è il particolare tipo di messaggio di successo con cui la parte B ha risposto. Cosa succederebbe se la Parte A volesse scegliere se l'elaborazione delle immagini fosse sincrona o asincrona? Cosa succede se la Parte A non sa se la Parte B è anche in grado di elaborare in modo asincrono?

Bene, HTTP ha già un protocollo standardizzato per questo! Si chiama Preferenze HTTP, in particolare la respond-asyncpreferenza di RFC 7240 Sezione 4.1. Se la Parte A desidera una risposta asincrona, include Prefer: respond-asyncun'intestazione con la sua richiesta POST iniziale. Se la Parte B decide di onorare questa richiesta, invia una 202 Acceptedrisposta che include a Preference-Applied: respond-async. Altrimenti, la Parte B ignora semplicemente l' Preferintestazione e rispedisce 201 Creatednormalmente.

Ciò consente alla Parte A di negoziare con il server, adattandosi dinamicamente a qualsiasi implementazione di elaborazione delle immagini con cui sta parlando. Inoltre, l'uso di collegamenti espliciti significa che la Parte A non deve conoscere parti diverse da B: nessun broker di messaggi AMQP, nessuna misteriosa Parte C che sappia come trasformare effettivamente l'indirizzo dell'immagine in dati di immagine, nessun secondo B-Async parte se devono essere fatte richieste sia sincrone che asincrone, ecc. Descrive semplicemente ciò di cui ha bisogno, ciò che opzionalmente vorrebbe, e quindi reagisce a codici di stato, contenuto della risposta e collegamenti. AggiungereCache-Controlintestazioni per istruzioni esplicite su quando conservare copie locali dei dati, e ora i server possono negoziare con i clienti di quali risorse i clienti possono conservare copie locali (o anche offline!). Questo è il modo in cui si creano microservizi con tolleranza agli errori liberamente accoppiati in REST.


1

Il fatto che tu debba o meno essere guidato esclusivamente dagli eventi dipende, ovviamente, dal tuo scenario specifico. Supponendo che tu debba davvero esserlo, allora potresti risolvere il problema:

Archiviazione di una copia locale di sola lettura dei dati ascoltando i diversi eventi e acquisendo le informazioni nei loro payload. Sebbene ciò ti fornisca letture rapide per quei dati, archiviati in un formato adatto a quell'applicazione esatta, significa anche che i tuoi dati saranno infine coerenti tra i servizi.

Per modellare GET /users/1con questo approccio, si potrebbe ascoltare il UserCreatede UserUpdatedeventi, e memorizzare il sottoinsieme utile dei dati degli utenti del servizio. Quando è quindi necessario ottenere le informazioni sugli utenti, è possibile semplicemente eseguire una query nel proprio archivio dati locale.

Per un minuto, supponiamo che il servizio che espone l' /users/endpoint non pubblichi alcun tipo di evento. In questo caso, è possibile ottenere risultati simili semplicemente memorizzando nella cache le risposte alle richieste HTTP effettuate, annullando così la necessità di effettuare più di 1 richiesta HTTP per utente entro un determinato periodo di tempo.


Capisco. Ma che dire della gestione degli errori (e dei rapporti) ai clienti in questo scenario?
Tony E. Stark,

Voglio dire, come posso riportare ai client REST errori che si verificano durante la gestione UserCreateddell'evento (ad esempio, nome utente duplicato o interruzione della posta elettronica o del database).
Tony E. Stark,

Dipende da dove stai eseguendo l'azione. Se ti trovi all'interno del sistema utente, puoi eseguire tutta la convalida, scrivendo nell'archivio dati lì, quindi pubblica l'evento. Altrimenti, ritengo perfettamente accettabile eseguire una richiesta HTTP standard /users/all'endpoint e consentire a quel sistema di pubblicare il suo evento se ha esito positivo e rispondere alla richiesta con la nuova entità
Andy Hunt,

0

Con un sistema di provenienza di eventi, gli aspetti asincroni entrano normalmente in gioco quando viene modificato qualcosa che rappresenta lo stato, forse un database o una vista aggregata di alcuni dati. Utilizzando il tuo esempio, una chiamata a GET / api / users potrebbe semplicemente restituire la risposta da un servizio che ha una rappresentazione aggiornata di un elenco di utenti nel sistema. In un altro scenario, la richiesta a GET / api / users potrebbe far sì che un servizio utilizzi il flusso di eventi dall'ultima istantanea degli utenti per creare un'altra istantanea e semplicemente restituire i risultati. Un sistema guidato da eventi non è necessariamente puramente asincrono da Richiesta a Risposta, ma tende ad essere al livello in cui i servizi devono interagire con altri servizi. Spesso non ha senso restituire in modo asincrono una richiesta GET e quindi puoi semplicemente restituire la risposta di un servizio,

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.