C'è un paio di problemi qui, a seconda dei tuoi obiettivi.
In primo luogo, promuove solo la disponibilità delle risorse sul back-end. Considera se hai 5 server che gestiscono le richieste in coda sul back-end. Se uno di questi server si arresta, la richiesta accodata dovrebbe rientrare in coda e essere riconsegnata a uno dei restanti 4 server.
Tuttavia, mentre i server back-end sono in fase di elaborazione, i server front-end sono in attesa delle effettive richieste di avvio. Se uno di questi server front-end si guasta, tali connessioni vengono perse completamente e spetterà al client originale reinviare la richiesta.
La premessa è forse che i sistemi front-end più semplici hanno un rischio inferiore di errore, e questo è certamente vero per gli errori relativi al software. Ma le schede di rete, gli alimentatori, i dischi rigidi, ecc. Sono piuttosto agnostici per le false speranze dell'uomo e puniscono tutti allo stesso modo. Quindi, considera questo quando parli della disponibilità generale.
Per quanto riguarda la progettazione, il back-end è un processo semplice che attende una coda di messaggi JMS ed elabora ogni messaggio come viene. Ci sono una moltitudine di esempi di questo disponibili e qualsiasi server JMS si adatta ad un livello elevato. Tutto ciò che serve è assicurarsi che la gestione dei messaggi sia transazionale in modo che se l'elaborazione di un messaggio fallisce, il messaggio rimane in coda e può essere riconsegnato ad un altro gestore di messaggi.
Il requisito principale della coda JMS è il clustering. Il server JMS stesso è un singolo punto di errore nel sistema. Perso il server JMS e il tuo sistema è praticamente morto nell'acqua, quindi dovrai essere in grado di raggruppare il server e consentire a consumatori e produttori di gestire correttamente il failover. Di nuovo, questo è specifico per il server JMS, molti lo fanno, ma è piuttosto normale nel mondo JMS.
Il front-end è dove le cose diventano un po 'più complicate, dal momento che i server front-end sono il ponte dal mondo sincrono della richiesta REST al mondo asincrono dei processori back-end. Una richiesta REST segue un tipico schema RPC di consumo del carico utile della richiesta dal socket, mantenendo aperta la connessione, elaborando i risultati e restituendo i risultati al socket originario.
Per manifestare questa mano, è necessario dare un'occhiata al servlet Asynchronous che gestisce la Servlet 3.0 introdotta, ed è disponibile in Tomcat 7, l'ultimo Jetty (non so quale versione), Glassfish 3.xe altri.
In questo caso, ciò che si farebbe quando arriva la richiesta, si converte la chiamata Servlet sincrona nominalmente in una chiamata asincrona usando HttpServletRequest.startAsync(HttpServletRequest request, HttpServletResponse response)
.
Questo restituisce un AsynchronousContext e, una volta avviato, consente al server di liberare il thread di elaborazione. Quindi fai diverse cose.
- Estrarre i parametri dalla richiesta.
- Creare un ID univoco per la richiesta.
- Creare un nuovo payload della richiesta di back-end dai parametri.
- Associare l'ID a AsyncContext e mantenere il contesto (ad esempio inserendolo in una mappa a livello di applicazione).
- Inviare la richiesta di back-end alla coda JMS.
A questo punto, l'elaborazione iniziale è terminata e si ritorna semplicemente da doGet (o servizio o altro). Poiché non hai chiamato AsyncContext.complete(), il server non chiuderà la connessione al server. Dal momento che hai l'archivio AsyncContext sulla mappa tramite l'ID, è utile per mantenere la sicurezza per il momento.
Ora, quando hai inviato la richiesta alla coda JMS, conteneva: l'ID della richiesta (che hai generato), tutti i parametri per la richiesta e l'identificazione del server effettivo che ha effettuato la richiesta. Quest'ultimo bit è importante in quanto i risultati dell'elaborazione devono tornare alla sua origine. L'origine è identificata dall'ID della richiesta e dall'ID del server.
All'avvio del server front-end, è stato avviato anche un thread il cui compito è quello di ascoltare una coda di risposta JMS. Quando imposta la sua connessione JMS, può impostare un filtro come "Dammi solo messaggi per un ServerID di ABC123". In alternativa, è possibile creare una coda univoca per ciascun server front-end e il server back-end utilizza l'ID del server per determinare la coda a cui restituire la risposta.
Quando i processori back-end consumano il messaggio, ricevono l'ID e i parametri della richiesta, eseguono il lavoro, quindi prendono il risultato e li inseriscono nella coda di risposta JMS. Quando riporta il risultato, aggiungerà il ServerID di origine e l'ID della richiesta originale come proprietà del messaggio.
Quindi, se la richiesta originariamente era per Front End Server ABC123, il processore di back-end indirizzerà i risultati su quel server. Quindi, quel thread del listener verrà avvisato quando riceve un messaggio. L'attività Thread Listener è quella di prendere quel messaggio e metterlo su una coda interna all'interno del server front-end.
Questa coda interna è supportata da un pool di thread il cui compito è inviare nuovamente i payload della richiesta alla connessione originale. Lo fa estraendo l'ID della richiesta originale dal messaggio, cercando l'AsyncContext dalla mappa interna discussa in precedenza e inviando i risultati alla risposta HttpServlet associata all'AsyncContext. Alla fine, chiama AsyncContext.complete() (o un metodo simile) per dire al server che hai finito e per consentire che rilasci la connessione.
Per le pulizie, è necessario disporre di un altro thread sul server front-end il cui compito è quello di rilevare quando le richieste sono in attesa nella mappa troppo a lungo. Una parte del messaggio originale avrebbe dovuto essere la volta in cui la richiesta è iniziata. Questo thread può svegliarsi ogni secondo, scansionare la mappa per le richieste, e per quelle che sono state lì troppo a lungo (diciamo 30 secondi), può mettere la richiesta su un'altra coda interna, consumata da una collezione di gestori progettati per informare il cliente che la richiesta è scaduta.
Si desidera queste code interne in modo che la logica di elaborazione principale non sia bloccata in attesa sul client per consumare i dati. Potrebbe essere una connessione lenta o qualcosa del genere, quindi non si desidera bloccare tutte le altre richieste in sospeso per gestirle una per una.
Infine, è necessario considerare che si potrebbe ottenere un messaggio dalla coda di risposta per una richiesta che non esiste più nella mappa interna. Per uno, la richiesta potrebbe essere scaduta, quindi non dovrebbe esserci più. Per un altro, quel server front-end potrebbe essersi fermato e riavviato, quindi la mappa interna della richiesta in sospeso sarà semplicemente vuota. A questo punto, se rilevi di avere una risposta per una richiesta che non esiste più, devi semplicemente scartarla (beh, loggala, quindi scartala).
Non è possibile riutilizzare queste richieste, in realtà non esiste un bilanciamento del carico che ritorni al client. Se il client ti consente di effettuare callback tramite endpoint pubblicati, assicurati di avere solo un altro gestore di messaggi JMS che effettua tali richieste. Ma questo non è un tipo di REST, il REST a questo livello di discussione è più client/server/RPC.
Per quanto riguarda il supporto di framework Asynchronous Servlet a un livello superiore rispetto a un servlet non elaborato, (come Jersey per JAX-RS o qualcosa del genere), non posso dire. Non so quali strutture lo supportano a quel livello. Sembra che questa sia una funzionalità di Jersey 2.0, che non è ancora disponibile. Ci possono essere altri, dovrai guardarti intorno. Inoltre, non eseguire la correzione su Servlet 3.0. Servlet 3.0 è semplicemente una standardizzazione delle tecniche utilizzate in singoli contenitori per un po 'di tempo (in particolare Jetty), quindi potreste voler esaminare le opzioni specifiche del contenitore al di fuori del solo Servlet 3.0.
Ma i concetti sono gli stessi. Il grande takeaway è il listener della coda di risposta con la connessione JMS filtrata, la mappa di richiesta interna su AsyncContext e le code interne e i pool di thread per eseguire il lavoro effettivo all'interno dell'applicazione.
si può utilizzare un bilanciatore di carico? – Henry
Cosa hai trovato finora? – miku
@Henry potrebbe essere quello che vuole mantenere una coda di richieste raggruppate Anche se tutti i server delle applicazioni sono occupati, nessuna richiesta dovrebbe essere rifiutata, perché dovrebbero essere in pool in attesa del loro turno. Il bilanciamento del carico – Subin