2013-08-13 1 views
5

Ho sviluppato un server http piuttosto esteso scritto in python utilizzando il tornado. Senza impostare nulla di speciale, il server blocca le richieste e può gestirle solo una alla volta. Le richieste sostanzialmente accedono ai dati (mysql/redis) e li stampano in json. Queste richieste possono richiedere al massimo un secondo nel peggiore dei casi. Il problema è che arriva una richiesta che richiede molto tempo (3 secondi), quindi una richiesta facile arriva immediatamente dopo che richiederebbe 5 ms per gestire. Bene dal momento che quella prima richiesta prenderà 3 secondi, la seconda non inizierà fino a quando non verrà eseguita la prima. Quindi la seconda richiesta richiede> 3 s per essere gestita.server http python, più richieste simultanee

Come posso migliorare questa situazione? Ho bisogno di questa seconda semplice richiesta per iniziare l'esecuzione indipendentemente dalle altre richieste. Sono nuovo di Python, e più esperto con apache/php in cui non vi è alcuna nozione di due richieste separate che si bloccano a vicenda. Ho esaminato mod_python per emulare l'esempio di php, ma sembra che blocchi anche. Posso cambiare il mio server tornado per ottenere la funzionalità che voglio? Ovunque leggo, si dice che il tornado è ottimo per gestire più richieste simultanee.

Ecco il codice demo con cui sto lavorando. Ho un comando di sospensione che sto usando per verificare se la concorrenza funziona. Dormire è un modo giusto per testare la concorrenza?

import tornado.httpserver 
import tornado.ioloop 
import tornado.web 
import tornado.gen 
import time 

class MainHandler(tornado.web.RequestHandler): 
    @tornado.web.asynchronous 
    @tornado.gen.engine 

    def handlePing1(self): 
     time.sleep(4)#simulating an expensive mysql call 
     self.write("response to browser ....") 
     self.finish() 

    def get(self): 
     start = time.time() 
     self.handlePing1() 
     #response = yield gen.Task(handlePing1)#i see tutorials around that suggest using something like this .... 

     print "done with request ...", self.request.path, round((time.time()-start),3) 



application = tornado.web.Application([ 
     (r"/.*", MainHandler), 
]) 

if __name__ == "__main__": 
    http_server = tornado.httpserver.HTTPServer(application) 
    port=8833; 
    http_server.listen(port) 
    print "listening on "+str(port); 
    tornado.ioloop.IOLoop.instance().start() 

Grazie per qualsiasi aiuto!

risposta

0

Ho avuto lo stesso problema, ma nessun tornado, nessun mysql. Hai una connessione al database condivisa con tutto il server?

Ho creato un multiprocessing.Pool. Ciascuno ha la propria connessione db fornita dalla funzione init. Ho avvolto codice lento in funzione e map in Pool. Quindi non ho variabili e connessioni condivise.

Sleep non blocca altri thread, ma la transazione DB potrebbe bloccare i thread.

È necessario impostare Pool nella parte superiore del codice.

def spawn_pool(fishes=None): 
    global pool 
    from multiprocessing import Pool 
    def init(): 
     from storage import db #private connections 
     db.connect() #connections stored in db-framework and will be global in each process 
    pool = Pool(processes=fishes,initializer=init) 

if __name__ == "__main__": 
    spawn_pool(8) 


from storage import db #shared connection for quick-type requests. 

#code here 

if __name__ == "__main__": 
    start_server() 

Molti di richieste simultanee veloci possono rallentamento una grande richiesta, ma questa concorrenza sarà posto su un solo server di database.

3

Modifica: ricorda che Redis è anche a thread singolo, quindi anche se hai richieste simultanee, il collo di bottiglia sarà Redis. Non sarai in grado di elaborare più richieste perché Redis non sarà in grado di elaborarle.

Tornado è un server basato su un ciclo di eventi a thread singolo.

Dalla documentazione:

Utilizzando rete non-blocking I/O, Tornado posso scalare a decine di migliaia di connessioni aperte, che lo rende ideale per il polling lungo, WebSockets, e altre applicazioni che richiedono un connessione a lunga vita ad ogni utente.

La concorrenza nel tornado viene ottenuta tramite callback asincroni. L'idea è di fare il meno possibile nel ciclo degli eventi principale (single-threaded) per evitare il blocco e rinviare le operazioni di I/O attraverso i callback.

Se l'utilizzo di operazioni asincrone non funziona (es: nessun driver asincrono per MySQL o Redis), l'unico modo per gestire più richieste simultanee è eseguire più processi.

Il modo più semplice è far fronte ai processi di tornado con un proxy inverso come HAProxy o Nginx. Il documento tornado consiglia Nginx: http://www.tornadoweb.org/en/stable/overview.html#running-tornado-in-production

In pratica esegui più versioni della tua app su porte diverse. Es:

python app.py --port=8000 
python app.py --port=8001 
python app.py --port=8002 
python app.py --port=8003 

Una buona regola empirica è eseguire 1 processo per ogni core sul server.

Nginx si prenderà cura di bilanciare ogni richiesta in arrivo ai diversi backend. Quindi se una delle richieste è lenta (~ 3s) hai n-1 altri processi che ascoltano le richieste in arrivo. È possibile, e molto probabilmente, che tutti i processi siano impegnati nell'elaborazione di una richiesta slow-ish, nel qual caso le richieste verranno accodate ed elaborate quando qualsiasi processo diventa libero, ad es. terminato l'elaborazione della richiesta.

Consiglio vivamente di iniziare con Nginx prima di provare HAProxy poiché quest'ultimo è un po 'più avanzato e quindi un po' più complesso da configurare correttamente (molti switch da modificare).

Spero che questo aiuti. Key take-away: Tornado è ottimo per l'I/O asincrono, meno per i carichi di lavoro pesanti della CPU.