2013-08-13 17 views
5

Opracowałem dość rozbudowany serwer http napisany w Pythonie z wykorzystaniem tornada. Bez ustawienia niczego specjalnego, serwer blokuje żądania i może obsługiwać tylko jeden na raz. Żądania zasadniczo uzyskują dostęp do danych (mysql/redis) i wypisują je w json. W najgorszym przypadku żądania te mogą potrwać nawet do sekundy. Problem polega na tym, że pojawia się żądanie, które zajmuje dużo czasu (3s), a następnie proste żądanie przychodzi natychmiast po tym, że zajmie 5ms do obsługi. Cóż, skoro ta pierwsza prośba zajmie 3s, druga nie rozpocznie się, dopóki pierwsza nie zostanie wykonana. Tak więc druga prośba wymaga obsługi> 3s.Python serwer http, wiele jednoczesnych żądań

Jak mogę poprawić tę sytuację? Potrzebuję tego drugiego prostego żądania, aby rozpocząć wykonywanie, niezależnie od innych żądań. Jestem nowy w Pythonie i bardziej doświadczony w Apache/PHP, gdzie nie ma pojęcia, że ​​dwa oddzielne żądania blokują się nawzajem. Zajrzałem do mod_python emulować przykład php, ale wydaje się, że również blokować. Czy mogę zmienić swój serwer tornado, aby uzyskać pożądaną funkcjonalność? Gdziekolwiek czytam, mówi, że tornado jest świetne w obsłudze wielu jednoczesnych żądań.

Oto kod demonstracyjny, nad którym pracuję. Mam polecenie sleep, którego używam do testowania, czy współbieżność działa. Czy sen jest dobrym sposobem na przetestowanie współbieżności?

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() 

Dzięki za pomoc!

Odpowiedz

0

Miałem ten sam problem, ale bez tornada, bez mysql. Czy masz jedno połączenie z bazą danych udostępnione dla wszystkich serwerów?

Stworzyłem multiprocessing.Pool. Każda z nich ma własne połączenie db, udostępnione przez funkcję init. Zawijam wolny kod w funkcji i map go do puli. Więc nie mam wspólnych zmiennych i połączeń.

Uśpij nie blokuje innych wątków, ale transakcja DB może blokować wątki.

Musisz ustawić pulę na górze swojego kodu.

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() 

Wiele jednoczesnych szybkich żądań może zwolnić jedno duże żądanie, ale ta współbieżność zostanie umieszczona tylko na serwerze bazy danych.

3

Edycja: pamiętaj, że Redis jest również połączony z jednym wątkiem, więc nawet jeśli masz równoczesne żądania, wąskim gardłem będzie Redis. Nie będzie można przetworzyć więcej żądań, ponieważ Redis nie będzie w stanie ich przetworzyć.

Tornado jest jednowątkowym serwerem opartym na pętli zdarzeń.

Z dokumentacji:

z wykorzystaniem sieci non-blocking I/O, Tornado można skalować do dziesiątki tysięcy otwartych połączeń, co czyni go idealnym miejscem na długie odpytywanie, WebSocket i innych zastosowań, które wymagają długotrwałe połączenie z każdym użytkownikiem.

Współbieżność w tornadzie osiąga się poprzez asynchroniczne wywołania zwrotne. Pomysł polega na tym, aby w pętli głównej zdarzenia (tak jak w przypadku pojedynczego wątku) zrobić jak najmniej, aby uniknąć blokowania i opóźniania operacji we/wy za pośrednictwem wywołań zwrotnych.

Jeśli korzystanie z asynchronicznych operacji nie działa (np. Brak sterownika asynchronicznego dla MySQL lub Redis), jedynym sposobem obsługi większej liczby jednoczesnych żądań jest uruchomienie wielu procesów.

Najprostszym sposobem jest wyprzedzić swoje procesy tornado za pomocą odwrotnego proxy, takiego jak HAProxy lub Nginx. Dokument doc tornado zaleca Nginx: http://www.tornadoweb.org/en/stable/overview.html#running-tornado-in-production

Twoje podstawowe wersje wielu aplikacji na różnych portach. Przykład:

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

Dobrą zasadą jest uruchomienie 1 procesu dla każdego rdzenia na serwerze.

Nginx będzie dbać o równoważenie każdego przychodzącego żądania do różnych serwerów zaplecza. Więc jeśli jedno z żądań jest wolne (~ 3s), masz jeszcze inne procesy n-1 nasłuchujące przychodzące żądania. Jest możliwe - i bardzo prawdopodobne - że wszystkie procesy będą zajęte przetwarzaniem powolnego żądania, w którym to przypadku żądania będą kolejkowane i przetwarzane, gdy dowolny proces stanie się wolny, np. zakończył przetwarzanie żądania.

Zdecydowanie zaleca się, aby zacząć od Nginx przed próbą HAProxy, ponieważ ta ostatnia jest nieco bardziej zaawansowana, a tym samym nieco bardziej skomplikowana do prawidłowego skonfigurowania (wiele przełączników do dostrojenia).

Mam nadzieję, że to pomoże. Kluczowe rozwiązanie na wynos: Tornado doskonale nadaje się do asynchronicznych operacji wejścia/wyjścia, mniej w przypadku ciężkich obciążeń procesora.

Powiązane problemy