tornado python mac
Tornado con ThreadPoolExecutor (0)
Tengo configuración que utiliza Tornado
como servidor http y marco http personalizado. Idea es tener un único controlador de tornados y cada solicitud que llegue debería enviarse a ThreadPoolExecutor
y dejar Tornado
para escuchar nuevas solicitudes. Una vez que el hilo termina de procesar la solicitud, se llama a la devolución de llamada que envía una respuesta al cliente en el mismo subproceso donde se ejecuta el bucle IO.
Desnudo, el código se ve algo como esto. Clase de servidor http base:
class HttpServer():
def __init__(self, router, port, max_workers):
self.router = router
self.port = port
self.max_workers = max_workers
def run(self):
raise NotImplementedError()
Implementación respaldada por Tornado de HttpServer:
class TornadoServer(HttpServer):
def run(self):
executor = futures.ThreadPoolExecutor(max_workers=self.max_workers)
def submit(callback, **kwargs):
future = executor.submit(Request(**kwargs))
future.add_done_callback(callback)
return future
application = web.Application([
(r''(.*)'', MainHandler, {
''submit'': submit,
''router'': self.router
})
])
application.listen(self.port)
ioloop.IOLoop.instance().start()
Manejador principal que maneja todas las solicitudes de tornado (implementado solo GET, pero el resto sería el mismo):
class MainHandler():
def initialize(self, submit, router):
self.submit = submit
self.router = router
def worker(self, request):
responder, kwargs = self.router.resolve(request)
response = responder(**kwargs)
return res
def on_response(self, response):
# when this is called response should already have result
if isinstance(response, Future):
response = response.result()
# response is my own class, just write returned content to client
self.write(response.data)
self.flush()
self.finish()
def _on_response_ready(self, response):
# schedule response processing in ioloop, to be on ioloop thread
ioloop.IOLoop.current().add_callback(
partial(self.on_response, response)
)
@web.asynchronous
def get(self, url):
self.submit(
self._on_response_ready, # callback
url=url, method=''post'', original_request=self.request
)
El servidor se inicia con algo como:
router = Router()
server = TornadoServer(router, 1111, max_workers=50)
server.run()
Entonces, como puede ver, el manejador principal solo envía cada solicitud al grupo de subprocesos y cuando se realiza el proceso, se llama a la devolución de llamada ( _on_response_ready
) que simplemente planifica el final de la solicitud para ejecutarse en el bucle IO (para asegurarse de que se realice en el mismo hilo) donde se está ejecutando IO loop).
Esto funciona. Al menos parece que sí.
Mi problema aquí es el rendimiento con respecto al número máximo de trabajadores en ThreadPoolExecutor.
Todos los manejadores están vinculados IO, no hay cálculos en curso (en su mayoría están esperando DB o servicios externos), así que con 50 trabajadores esperaría que 50 solicitudes concurent terminen aproximadamente 50 veces más rápido que 50 solicitudes concurent con un solo trabajador.
Pero ese no es el caso. Lo que veo son solicitudes casi idénticas por segundo cuando tengo 50 trabajadores en el pool de hilos y 1 trabajador.
Para medir, he usado Apache-Bench con algo como:
ab -n 100 -c 10 http://localhost:1111/some_url
¿Alguien tiene idea de qué estoy haciendo mal? ¿Entendí mal cómo funcionan Tornado o ThreadPool? ¿O combinación?