2012-05-09 12 views
9

Avevo un'app per rails 3 su Nginx/Passenger che ho appena trasferito su Nginx/Thin (1.3.1). Tuttavia, la mia app ora è chiaramente più lenta di quanto non fosse su Passenger. Anche molte richieste scadono.Sottile server con prestazioni insufficienti/Come funzionano i server Web eventi?

Thin è un server Web event. Da quello che ho letto sui server Web presenti, non hanno un concetto di lavoratori. Un "lavoratore" gestisce tutto. Quindi se una richiesta è in attesa su IO, thin passa alla richiesta successiva e quindi a una. Una delle spiegazioni che ho letto sui server evented ha detto che i server evented dovrebbero funzionare altrettanto bene o meglio dei server basati su worker perché sono vincolati solo dalle risorse di sistema.

Tuttavia, l'utilizzo della CPU è molto limitato. Anche l'utilizzo della mia memoria è molto limitato, e non c'è nemmeno molto IO che accada. La mia app crea solo alcune query MySQL.

Qual è il collo di bottiglia qui? Il mio thin server non dovrebbe gestire le richieste finché la CPU non è al 100%? Devo fare qualcosa di diverso nella mia app perché funzioni meglio con un server evented?

risposta

13

Sergio è corretto. La tua app, a questo punto, è probabilmente migliore del tradizionale modello Apache/Passenger. Se si prende la rotta event, in particolare su piattaforme a thread singolo come Ruby, non è MAI possibile bloccare nulla, che si tratti dei server DB, Cache, di altre richieste HTTP che si potrebbero fare - nulla.

Questo è ciò che rende più difficile la programmazione asincrona (evento): è facile bloccare su roba, di solito sotto forma di I/O del disco sincrono o risoluzioni DNS. I framework non eventualizzati (evented) come nodejs fanno attenzione a non fornire (quasi) mai una chiamata della funzione framework che blocca, ma tutto viene gestito utilizzando i callback (incl le query DB).

Questo potrebbe essere più facile da visualizzare, se si guarda al cuore di un server non-blocking a thread singolo:

while(wait_on_sockets(/* list<socket> */ &$sockets, /* event */ &$what, $timeout)) { 
    foreach($socketsThatHaveActivity as $fd in $sockets) { 
     if($what == READ) { // There is data availabe to read from this socket 
      $data = readFromSocket($fd); 
      processDataQuicklyWithoutBlocking($data); 
     } 
     elseif ($what == WRITE && $data = dataToWrite($fd)) { // This socket is ready to be written to (if we have any data) 
      writeToSocket($fd, $data);  
     } 
    } 
} 

Quella che vedete sopra è chiamato il ciclo degli eventi. wait_on_sockets viene solitamente fornito dal sistema operativo sotto forma di una chiamata di sistema, ad esempio selezionare, eseguire il polling, epoll o kqueue. Se processDataQuicklyWithoutBlocking impiega troppo tempo, il buffer di rete della tua applicazione gestito dal sistema operativo (nuove richieste, dati in entrata, ecc.) Si riempirà e causerà il rifiuto di nuove connessioni e il timeout di quelli esistenti, dato che $ socketsThatHaveActivity non viene gestito abbastanza velocemente . Questo è diverso da un server con thread (ad esempio un'installazione tipica di Apache) in quanto ogni connessione viene servita utilizzando un thread/processo separato, quindi i dati in arrivo verranno letti nell'app non appena arrivano e i dati in uscita verranno inviati senza ritardo .

Quali framework non bloccanti come nodejs fanno quando si effettua (ad esempio) una query DB è aggiungere la connessione socket del server DB all'elenco dei socket monitorati ($ socket), quindi anche se la query richiede un po ', il tuo thread (solo) non è bloccato su quella presa. Piuttosto, essi forniscono una richiamata:

$db.query("...sql...", function($result) { ..handle result ..}); 

Come potete vedere sopra, db.query restituisce immediatamente senza alcun blocco sul server db di sorta.Questo significa anche avere spesso di scrivere codice in questo modo, a meno che il linguaggio di programmazione in sé supporta le funzioni asincrone (come il nuovo C#):

$db.query("...sql...", function($result) { $httpResponse.write($result); $connection.close(); }); 

La regola mai-mai-blocco può essere un po 'rilassato se si dispone di molti processi che eseguono ognuno un ciclo di eventi (tipicamente il modo di eseguire un cluster di nodi), o usano un pool di thread per mantenere il loop degli eventi (java, netty ecc., puoi scrivere il tuo in C/C++). Mentre un thread è bloccato su qualcosa, altri thread possono ancora fare il ciclo degli eventi. Ma sotto carico abbastanza pesante, anche questi non riuscirebbero a funzionare. Quindi non avere MAI MAI BLOCCO in un server event.

Quindi, come potete vedere, i server eventi in genere cercano di risolvere un problema diverso - possono avere un gran numero di connessioni aperte. Dove eccellono è solo spingendo byte intorno con calcoli di luce (ad esempio server comet, cache come memcached, vernice, proxy come nginx, squid ecc.). Non vale nulla che, anche se scalano meglio, i tempi di risposta tendono generalmente ad aumentare (niente è meglio che riservare un'intera discussione per una connessione). Naturalmente, potrebbe non essere economicamente/computazionalmente fattibile eseguire lo stesso numero di thread di # connessioni simultanee.

Ora torniamo al tuo problema - ti raccomando di mantenere Nginx in circolazione, dato che è eccellente per la gestione delle connessioni (che è basata su eventi) - generalmente significa gestire i keep-alive HTTP, SSL ecc. Dovresti quindi connetterti alla tua app Rails che utilizza FastCGI, dove devi ancora eseguire i worker, ma non devi riscrivere la tua app per essere completamente evented. Dovresti anche consentire a Nginx di pubblicare contenuti statici, in nessun modo i dipendenti di Rails saranno legati a qualcosa che Nginx può normalmente fare meglio. Questo approccio generalmente scala molto meglio di Apache/Passenger, specialmente se si esegue un sito Web ad alto traffico.

Se riesci a scrivere l'intera app in modo da essere evento, allora è grandioso, ma non ho idea di quanto sia facile o difficile in Ruby.

+0

Wow .. grazie per la risposta dettagliata Tejas. Quindi i parametri che leggo fuori dalla rete .. sono per un genere di applicazione completamente diverso? Il sito di Thin fornisce un'app per i binari come app di esempio per thin. http://code.macournoyer.com/thin/. Avevo l'impressione che potessi semplicemente sostituire il passeggero con un filo sottile e tutto sarebbe stato molto faticoso. –

+0

Se non blocchi da nessuna parte, dovresti essere in grado di ricreare questi parametri di riferimento. – tejas

3

Sì, Thin esegue l'I/O, ma solo per la parte HTTP. Ciò significa che può ricevere dati HTTP in arrivo durante l'elaborazione di una richiesta. Tuttavia, tutto l'I/O di blocco che si esegue durante l'elaborazione sta ancora bloccando. Se il tuo MySQL è lento a rispondere, allora la coda di richiesta sottile si riempirà.

Per il server Web "more" si consiglia di verificare Rainbows.

+0

Ciao Sergio. Perdonate la mia comprensione pedonale di questi concetti. Ho letto che mysql2 gem for rails ha anch'esso un evento di I/O. Inoltre, se questo è il caso, un server basato sul lavoratore non dovrebbe essere sempre migliore? Sto solo correndo come 2 istanze di sottile rispetto a 25 istanze di passeggeri che stavo correndo in precedenza. –

+0

@CoffeeBite: *** può *** fare chiamate asincrone, sì, ma non è automatico e devi scrivere codice per farlo accadere. Di default, è sincrono. –

+0

@ CoffeeBite: "i lavoratori non sarebbero sempre migliori", non sono sicuro. Io stesso uso [Unicorn] (http://unicorn.bogomips.org/) dietro un nginx. Nginx gestisce l'I/O HTTP e gli unicorni servono rapidamente le richieste. –