2010-10-05 18 views
19

Sto scrivendo un server Java che utilizza socket semplici per accettare le connessioni dai client. Sto usando il modello abbastanza semplice in cui ogni connessione ha il proprio thread leggendo da esso in modalità di blocco. pseudo codice:Un thread per client. Fattibile?

handshake(); 

while(!closed) { 
    length = readHeader(); // this usually blocks a few seconds 
    readMessage(length); 
} 

cleanup(); 

(Le discussioni sono creati da un Executors.newCachedThreadPool() quindi non ci dovrebbe essere alcun overhead significativo nella loro partenza)

So che questo è un po 'di una messa a punto ingenua e non sarebbe davvero scalare bene a molte connessioni se i thread erano thread del sistema operativo dedicati. Tuttavia, ho sentito che più thread in Java possono condividere un thread hardware. È vero?

Sapendo che userò la macchina virtuale Hotspot su Linux, su un server con 8 core e 12 GB di RAM, pensi che questa configurazione funzioni bene per migliaia di connessioni? In caso contrario, quali sono le alternative?

risposta

5

È possibile che si riduca a migliaia di clienti. Ma quante migliaia è la prossima domanda.

Una comune alternativa è usare selettori e non-blocking I/O trovato nel pacchetto java.nio.

Alla fine si entra in questione se è utile per configurare il server in una configurazione cluster, bilanciamento del carico su più macchine fisiche.

5

Questa scala bene fino a centinaia di connessioni, non a migliaia. Un problema è che anche un thread Java richiede un bel po 'di stack (ad esempio 256K) e il sistema operativo avrà problemi nella pianificazione di tutti i thread.

Guardate Java NIO o framworks che vi aiuterà a iniziare a fare cose complesse più facilmente (ad esempio Apache Mina)

+1

Ogni thread richiederà un po 'di stack, ma se lo trasformo in un modello non bloccante ogni connessione avrà bisogno di più dati sull'heap, come "in che fase di lettura di un messaggio siamo ora", che è attualmente semplicemente determinato dal puntatore dell'istruzione (parola corretta?) di una discussione. La programmazione potrebbe essere un problema però. –

+1

@Bart: lo spazio extra per connessione non è neanche lontanamente grande come una pila; andare a NIO migliorerà la scalabilità. Il costo è che la maggior parte della gente trova più difficile ingannare ciò che sta succedendo (e Java non ha coroutine, che possono essere utilizzate per bilanciare le preoccupazioni). –

+0

Come ha detto edera, la considerazione principale per ridimensionare a migliaia è davvero lo stack che finirà per consumare. D'altra parte, non sono pienamente convinto che la pianificazione sarà un fattore importante. Sarà un fattore, ma forse non così grande come si potrebbe percepire. Se le attività dominanti sono I/O, i thread produrranno prontamente le CPU nella maggior parte dei casi e riceveranno il paging solo se i dati vengono ricevuti. Se ci pensi, quell'immagine non è troppo diversa anche se usi NIO. È solo che un numero maggiore di thread è coinvolto nel chiedere approssimativamente lo stesso numero di cicli della CPU. – sjlee

3

di avere una buona perfomance durante la manipolazione di molte prese di solito si usa un approccio select che è come Unix API gestisce applicazioni multi-socket a thread singolo che richiedono molte risorse.

Questo può essere fatto tramite il pacchetto java.nio che ha una classe Selector che fondamentalmente è in grado di passare attraverso tutti i socket aperti e avvisare quando sono disponibili nuovi dati.

si registra tutti i flussi aperti all'interno di un unico Selector e quindi è in grado di gestire tutti loro da un solo thread.

È possibile ottenere informazioni supplementari con un tutorial here

+0

Per caso mi sono imbattuto in una domanda SO sul metodo 'InputStream.available()', che avevo dimenticato. Potresti dirmi il vantaggio di usare un 'Selector' invece di avere un thread per gestire più connessioni, usando' available() 'per prevenire il blocco? –

+0

Perché si evita di reimplementare qualcosa che va oltre i vari Socket a livello di codice poiché si utilizzerà qualcosa appositamente progettato per questo scopo :) Il vantaggio principale sarebbe risparmiare tempo per il debug e l'implementazione .. – Jack

+0

Hmm, ma l'implementazione di questo, a almeno nel mio caso, sarebbe estremamente semplice. Il mio protocollo è 1. read integer che specifica la lunghezza del messaggio. 2. leggere quel numero di byte in una volta –

1

Le discussioni non sono così costosi come quelli di una volta, in modo da un'implementazione "ordinario" IO può essere ok per un punto. Tuttavia, se si guarda il ridimensionamento a migliaia o oltre, probabilmente vale la pena indagare su qualcosa di più sofisticato.

Il pacchetto java.nio risolve questo fornendo presa multiplexing/non bloccante IO, che consente di associare più collegamenti ad un selettore. Tuttavia questa soluzione è molto più difficile da ottenere rispetto al semplice approccio di blocco a causa dell'aspetto multithreading e non bloccante.

Se si vuole perseguire qualcosa che va oltre la semplice IO quindi vorrei suggerire a guardare una delle buone biblioteche di astrazione di rete di qualità là fuori. Dalla mia esperienza personale posso raccomandare lo Netty che fa la maggior parte del maneggevole NIO per te.Tuttavia ha una curva di apprendimento, ma una volta che ti sei abituato all'approccio basato sull'evento è molto potente.

1

Se si ha interesse a sfruttare la distribuzione e la gestione di un contenitore esistente, si potrebbe cercare di creare un nuovo gestore di protocollo all'interno di Tomcat. Vedere this answer a una domanda correlata.

UPDATE: This post da Matthew Schmidt ferma il connettore NIO-based (scritto da Filip Hanik) in Tomcat 6 raggiunto 16.000 connessioni simultanee.

Se si desidera scrivere il proprio connettore, dare un'occhiata a MINA per aiutare con astrazioni NIO. MINA ha anche caratteristiche di gestione che possono eliminare necessità di un altro contenitore (si dovrebbe essere preoccupati dispiegamento di molte unità e il loro funzionamento, etc.)

+0

Interessante, ma la mia applicazione non sta attualmente utilizzando un contenitore. Sono anche interessante nel lavorare con NIO me stesso (se risulta necessario) per scopi educativi. –

3

Il JVM per Linux sta usando mappatura uno a uno thread. Ciò significa che ogni thread Java è mappato su un thread OS nativo.

creando così un migliaio di fili o più non è una buona idea, perché avrà un impatto le prestazioni (context switching, cache vampate/misses, synchronization latenza ecc). Inoltre non ha senso se hai meno di un migliaio di CPU.

L'unica soluzione adeguata per servire molti client in parallelo è utilizzare l'I/O asincrono. Si prega di vedere this answer su Java NIO per i dettagli.

Consulta anche:

+1

Grazie, questa è una risposta utile, ma non sono d'accordo con "Non ha senso se hai meno di un migliaio di CPU.". Ha senso mantenere lo stato corrente della connessione ("stiamo leggendo ora un'intestazione o dati", "quanto del messaggio abbiamo ancora ricevuto", ecc.) Sullo stack piuttosto che l'heap, che è più veloce (giusto?) e rende più facile la programmazione. –

+0

Ti fa solo sentire come se fosse più facile programmare (entrambi gli approcci sono relativamente facili IMO). L'uso dello stack non lo rende più veloce di fatto, tutto dipende da ciò che stai facendo. In ogni caso, il trade-off con thread 1K sarà maggiore rispetto al mantenimento di un elenco di sessioni con stato pre-allocato. –

+0

+1 per fili verdi –

2

Prova Netty.

il modello "un thread per richiesta" è il modo in cui vengono scritti la maggior parte dei server di app Java. La tua implementazione può ridimensionare come fanno.

+0

Solo perché "la maggior parte" delle persone fa qualcosa significa che è giusto o il modo migliore. L'unico modo per rispondere con certezza sarebbe scrivere il codice usando entrambi i metodi e test per vedere quale si comporta meglio in termini di utilizzo della CPU e della memoria. A causa delle differenze tra Windows e Linux potrebbe essere necessario prendere in considerazione la possibilità di test in entrambi gli ambienti per essere sicuri. Detto questo, credo che l'unico thread per richiesta non sia scalabile. – Jacob

0

Penso che un approccio migliore sia non gestire i thread da soli. Crea un pool (ThreadExecutor o qualche altra roba) e un semplice dispaccio di lavoro nel tuo pool.

Naturalmente, penso che l'I/O asincrono lo renderà migliore e più veloce, ma ti aiuterà con problemi di socket e di rete. Solo. Quando i thread si bloccano a causa di I/O, la JVM la mette in stop e cambia per un altro thread fino a quando non viene restituito l'I/O di blocco. Ma questo bloccherà solo il thread. Il processore continuerà a funzionare e inizierà a processare altri thread. Quindi, meno il tempo di creare un thread, il modo in cui si utilizza l'I/O non influisce molto sul modello. Se non si creano thread (utilizzando un pool) il problema è risolto.

+0

Io uso un Executor, ma dato che ogni thread fondamentalmente fa 'while (! Closed) read(); 'che in realtà non diminuisce il numero di thread, diminuisce semplicemente il sovraccarico di crearli. –

+0

È esattamente il mio punto. Quando chiami read() il tuo thread bloccherà fino a quando non avrà qualcosa da fare. Non importa quanti thread hai, ma quanti thread RUNNABLE hai e i thread bloccati non contano. Non competeranno per il tempo del processore. Quindi, alla fine avrai solo i thread che hanno cose da fare in competizione per il tempo del processore. –

1

Suggerirei che dipende più esattamente da cosa altro sta facendo il server quando elabora i messaggi. Se è relativamente leggero, le specifiche della tua macchina dovrebbero FACILMENTE far fronte semplicemente alla gestione delle connessioni di migliaia di tali processi. Decine di migliaia è un'altra domanda, forse, ma hai solo bisogno di due macchine sulla stessa rete per testarlo empiricamente e ottenere una risposta definitiva.

0

Perché rollare da solo? È possibile utilizzare un contenitore servlet con servlet, una coda di messaggi o ZeroMQ.

Problemi correlati