Cómo entender los tornados
Tornado es un servidor HTTP asíncrono y un framework de desarrollo web escrito en Python. El marco impulsa el sitio web FriendFeed y, más recientemente, Facebook. Facebook se caracteriza por una gran cantidad de usuarios y potentes aplicaciones en tiempo real, por lo que tiene requisitos extremadamente altos de rendimiento y escalabilidad. Dado que el marco ahora es de código abierto (gracias a Facebook), podemos obtener una idea de cómo funciona.
Creo que es necesario hablar sobre IO sin bloqueo (IO sin bloqueo) e IO asíncrona (IO AIO asíncrona). Si ya sabes exactamente cuáles son, puedes pasar a la siguiente sección. Usaré tantos ejemplos como sea posible para ilustrar lo que significan.
Supongamos que está escribiendo una aplicación que necesita solicitar algunos datos de otro servidor (como un servicio de base de datos o la API abierta de Sina Weibo), y estas solicitudes tardarán un tiempo relativamente largo, por ejemplo, 5 segundos. El código para manejar solicitudes en la mayoría de los marcos de desarrollo web se ve así:
def handler_request(self, request):
answ = self.remote_server.query(request) # Esto requiere 5 segundos
request.write_response(answ)
Si este código se ejecuta en un solo hilo, su servidor solo recibirá solicitudes de clientes una vez cada 5 segundos. Durante esos 5 segundos, el servidor no puede hacer nada más, por lo que la eficiencia de su servicio es de 0,2 solicitudes por segundo, oh, eso es una lástima.
Por supuesto, nadie es tan ingenuo, la mayoría de los servidores utilizarán tecnología de subprocesos múltiples para permitir que el servidor reciba solicitudes de múltiples clientes al mismo tiempo. Suponiendo que tenga 20 subprocesos, su rendimiento aumentará en 20 veces, por lo que su servidor ahora es eficiente al aceptar 4 solicitudes por segundo, pero esto aún es demasiado bajo. Sin embargo, los subprocesos son muy costosos en términos de memoria y programación. Dudo que utilice este método para aumentar la cantidad de subprocesos. nunca logrará una eficiencia de 100 solicitudes por segundo.
Si utiliza AIO, es muy fácil lograr miles de solicitudes por segundo. El código para el procesamiento de solicitudes del servidor se cambiará para verse así:
def handler_request(self, request):
self.remote_server.query_async(request, self.response_received) p>
def respuesta_recibida(self, request, answ): # Llama a esta función después de 5 segundos
request.write(answ)
La idea de AIO no es bloquear mientras se espera el resultado, pero proporcionar la función de devolución de llamada como parámetro al marco y dejar que el marco nos notifique a través de la función de devolución de llamada cuando haya un resultado. Esto libera el servidor para aceptar solicitudes de otros clientes.
Sin embargo, aquí es donde AIO se queda corto: el código es un poco poco intuitivo. Además, si está utilizando un software de servidor AIO de un solo subproceso (como Tornado), debe tener cuidado de no bloquear nada, ya que todas las solicitudes que se supone que deben regresar actualmente lo harán de manera perezosa, como se mencionó anteriormente.
Para un estudio más profundo de IO asíncrona, consulte El problema del C10K.
Código fuente
El proyecto está alojado en github, puedes obtenerlo a través del siguiente comando, por supuesto, también puedes obtenerlo leyendo este artículo.
git clone git://github.com/facebook/tornado.git
En el subdirectorio de tornado, cada módulo debe tener un archivo .py, puedes pasar Verifique estos archivos para determinar si el proyecto se ha eliminado completamente del código base. En cada archivo de código fuente, encontrará al menos una cadena de documentación larga que explica el módulo y brinda uno o dos ejemplos de cómo usarlo.
El módulo IOLoop
Nos permite acceder directamente al núcleo del servidor a través del archivo ioloop.py. Este módulo es el núcleo del mecanismo asincrónico. Contiene un conjunto de descriptores de archivos abiertos (Anotación: también conocidos como punteros de archivos) y un controlador para cada descriptor. Su función es seleccionar aquellos descriptores de archivos que están listos para ser leídos o escritos, y luego llamar a sus respectivos manejadores (una implementación de multiplexación IO, que en realidad es un modelo de selección entre los múltiples modelos IO de sockets, o NIO en Java).
Se puede agregar un socket al bucle IO llamando al método add_handler():
def add_handler(self, fd, handler, events):
"""Registra el controlador dado para recibir los eventos dados para fd.""
self._handlers[fd] = handler
self._impl.register(fd, events | self .ERROR)
_handlers Esta variable de tipo diccionario contiene una asignación de descriptores de archivos (en realidad, sockets) a métodos que deben llamarse cuando el descriptor de archivo esté listo (en tornado, este método se llama controlador). Luego, el descriptor de archivo se registra en la lista epoll (al parecer, un mecanismo de sondeo de IO en Unix). Tornado se centra en tres tipos de eventos (aquí se refiere a la aparición del evento de descripción del archivo en el símbolo): Como usted. Como puede ver, ERROR se agrega automáticamente de forma predeterminada.
self._impl es uno de los dos eventos de select.epoll() y selet.select(). Verá más adelante cómo Tornado elige entre ellos. /p>