2013-01-03 16 views
11

Qual è la migliore soluzione tecnologica (framework/approach) per disporre di una coda di richieste di fronte a un servizio REST. cosicché sia ​​possibile aumentare il numero di istanze del servizio REST per una maggiore disponibilità e inserendo la coda di richieste in primo piano per formare un limite di servizio/transazione per il client di servizio.Coda richiesta davanti a un servizio REST

  1. ho bisogno della tecnologia buona e leggera/scelta quadro per Request Queue (java)
  2. approccio per implementare un consumatore in concorrenza con esso.
+3

si può utilizzare un bilanciatore di carico? – Henry

+0

Cosa hai trovato finora? – miku

+2

@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

risposta

7

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.

  1. Estrarre i parametri dalla richiesta.
  2. Creare un ID univoco per la richiesta.
  3. Creare un nuovo payload della richiesta di back-end dai parametri.
  4. Associare l'ID a AsyncContext e mantenere il contesto (ad esempio inserendolo in una mappa a livello di applicazione).
  5. 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.

+0

cosa devo fare se devo eseguire 5 micro-servizi (container/jetty integrati) che sono collegati a una coda in modo che siano in competizione tra di loro (per una migliore velocità effettiva) e la mia libreria client è responsabile solo fino alla coda (richiesta/risposta) – TheWhiteRabbit

+0

In breve L'interazione asincrona deve essere nascosta utilizzando una libreria client che viene fornita con ogni servizio, sto cercando una scelta tecnologica per quella libreria client che media la richiesta/risposta HTTP dal client di servizio. – TheWhiteRabbit

+0

Non capisco. Il client che effettua la chiamata REST non sarà asincrono, REST non è un sistema basato su async. –

2

Se si riduce il requisito che deve essere in Java, è possibile considerare HAProxy. È molto leggero, molto standard, e fa un sacco di cose buone (richiede pool/keepalive/in coda) bene.

Pensa due volte prima di implementare l'accodamento delle richieste. A meno che il tuo traffico sia estremamente esplosivo, non farà altro che danneggiare le prestazioni del tuo sistema sotto carico.

Si supponga che il sistema sia in grado di gestire 100 richieste al secondo. Il server HTTP ha un pool di thread di lavoro limitato. L'unico modo in cui un pool di richieste può aiutare è se si ricevono più di 100 richieste al secondo. Dopo che il pool di thread di lavoro è pieno, le richieste iniziano ad accumularsi nel pool di bilanciamento del carico. Dal momento che stanno arrivando più velocemente di quanto tu possa gestirli, la coda diventa più grande ... e più grande ... e più grande. Alla fine, anche questo pool si riempie, oppure si esaurisce la RAM e il bilanciamento del carico (e quindi l'intero sistema) si blocca duramente.

Se il server Web è troppo occupato, iniziare a rifiutare richieste e ottenere ulteriore capacità online.

Il pool di richieste può sicuramente aiutare se è possibile ottenere una capacità aggiuntiva in tempo per gestire le richieste. Può anche ferirti gravemente. Considerare le conseguenze prima di attivare un pool di richieste secondarie davanti al pool di thread del worker del server HTTP.

0

Il design che usiamo è un un'interfaccia REST che riceve tutte le richieste e li invio a una coda di messaggi (cioè RabbitMQ)

Poi lavoratori ascoltare i messaggi e eseguirli seguendo determinate regole. Se tutto andasse a posto, avresti ancora la richiesta nel MQ e se hai un alto numero di richieste puoi semplicemente aggiungere lavoratori ...

Controlla questo keynote, mostra la potenza di questo concetto!

http://www.springsource.org/SpringOne2GX2012

+0

ma come si è adattato il modello richiesta/risposta? si adatta al modello sincrono req/resp (dal punto di vista del cliente)? o il cliente ha bisogno di sondare l'attesa/l'ascolto (per l'evento) per una risposta? – TheWhiteRabbit

+1

Per questo esiste un'opzione multipla in REST. Ad esempio, se chiamo il servizio e la richiesta non può ancora essere elaborata, il server restituisce: 202 Accettato. Che significa "La richiesta è stata accettata per l'elaborazione, ma l'elaborazione non è stata completata." – moskiteau

Problemi correlati