Download - CHATEANDO SIN MESENGER
-
7/31/2019 CHATEANDO SIN MESENGER
1/4
Python: Gevent y Django DESARROLLO
53Nmero 71WWW. L I NUX - M A G A Z I N E . E S
E
l rendimiento nunca ha sido un
punto fuerte de Python. Bueno,para ser sinceros, tampoco ha
sido uno de sus objetivos. El lenguaje
Python fue creado con el fin de facilitar y
simplificar la creacin de scripts en un
sistema operativo experimental llamado
Amoeba. Para aumentar el rendimiento,
siempre se poda reescribir el cdigo en
C e invocarlo desde Python.
Con la entrada de Python en el mundi-
llo de los framework web rpidamente
apareci la necesidad de contar con
algn sistema que permitiese a unapgina web en Python escalar. La
memoria nunca ha sido un problema en
Python, por lo que el foco siempre ha
estado en la capacidad de Python de res-
ponder a una gran cantidad de eventos o
peticiones por segundo. No hay nada
peor en el mundo que ser incapaz de res-
ponder a tus usuarios cuando se satura
el sistema.
La solucin tradicional a este pro-
blema siempre se ha basado en el uso
del multiproceso o la multihebra. Porcada peticin que recibamos se genera
un proceso o hebra que la atender y que
se eliminar tan pronto como se termine
de responder. Esta solucin suele ir
bien pero requiere cantidades ingentes
de recursos (memoria y cpu) y se agota
rpidamente. Tener miles de hebras oprocesos saturar rpidamente nuestro
sistema. Adems, no debemos olvidar
que el sistema de hebras de Python est
herido de muerte desde su creacin: el
GIL obliga al programa a bloquear todas
las hebras cuando una de ellas entra en
el cdigo del intrprete de Python algo
muy normal eliminando la posibilidad
de aprovechar los varios ncleos con los
que cuentan las cpus modernas
No hay solucin a este problema? S,
existe una solucin y adems se estponiendo de moda: emplear un bucle de
eventos (Event Loop). Vamos a analizar
cmo funciona un pequeo chat html
que viene como ejemplo en el cdigo
fuente de Gevent (Recurso 1), una de las
libreras que implementan un bucle de
eventos en Python.
WebchatComencemos por instalar todo lo necesa-
rio para ejecutar el chat. Si tenemos ins-
talado en nuestro sistema virtualenv, lospasos que debemos seguir son:
bash$ virtualenv U
--no-site-packages U
lm-chat
bash$ cd lm-chat
bash$ source bin/activatebash$ bin/pip install U
gevent django
Gevent hace uso de la librera C libe-
vent, por lo que deberemos instalarla, as
como el paquete de desarrollo (normal-
mente llamado libevent-dev), con nues-
tro sistema de paquetes (rpm, apt-get).
El paquete geventposee componentes en
C, de modo que veremos cmo se compi-
lan algunos ficheros durante el proceso
de instalacin con pip.Una vez tenemos nuestros paquetes
instalados, deberemos descargar el
cdigo fuente del ejemplo de Gevent.
Para ello vamos a descargar Geventcon
hg(mercurial):
bash$ hg clone U
https://bitbucket.org/denisU
/gevent
El cdigo fuente que nos interesa se
encuentra en el directorio examples/web-chat y es una aplicacin django. Para
arrancarla necesitaremos ejecutar:
bash$ cd gevent/examples/webchat
bash$ python run.py
Se arrancar el servidor web en el puerto
8088, as que podremos acceder a l si
ponemos en nuestro navegador
http://127.0.0.1:8088; veremos entonces
una pgina como la que aparece en la
Figura 1. En la parte inferior se encuen-tra una caja de texto en la que ser posi-
ble escribir mensajes. Si ahora conecta-
mos desde otro ordenador a la misma
pgina podremos ver la magia de Gevent
en funcionamiento. Cuando escribimos
Los bucles de eventos
estn cambiando las
reglas en el desarrollo
de sistemas web, y
Gevent nos ofrece toda
su potencia de forma
simple y directa
POR JOS MARA RUZ
Gevent y Django
CHATEANDO SINMESSENGER
-
7/31/2019 CHATEANDO SIN MESENGER
2/4
DESARROLLO Python: Gevent y Django
54 Nmero 71 WWW. L I NUX - M A G A Z I N E . E S
Gevent sin prcticamente tener que
modificar nuestro cdigo fuente.
Una vez hemos importado de la libre-
ra gevent el mdulo monkey, ejecuta-
mos el mtodo patch_all(). Este mtodo
reemplaza un gran nmero de llamadas
importantes de la librera estndar dePython, por ejemplo:
La funcin os.fork
Funciones de thread
Funciones de socket
Funciones de ssl
Funciones de httplib
Funciones de select
Las mayora de estas funciones bloquean
el intrprete de Python mientras esperan
algn tipo de respuesta. Gevent pasa a
gestionar estas funciones mediante even-
tos, ejecutndolas y aadiendo un call-back que se disparar cuando el evento
de turno (por ejemplo, que ha llegado
informacin desde la red) se dispare,
permitiendo mientras tanto la ejecucin
de otro cdigo. Gevent no nos permite
ejecutar ms cdigo a la vez, sino que se
encarga de gestionar aquel cdigo que
normalmente se bloquea esperandoalgn tipo de recurso.
Como podemos ver en el Listado 1,
Gevent tambin nos proporciona un ser-
vidor WSGIque podemos usar para eje-
cutar la aplicacin Django. Este servidor
tambin funciona empleando el bucle de
eventos de Gevent, lo que implica que no
se bloquear. Y esto qu significa? Pues
que, por ejemplo, podremos responder a
cientos, o incluso miles, de peticiones
por segundo sin que la cpu apenas se
esfuerce y sin consumir una cantidad dememoria excesiva.
El resto del cdigo en este fichero sim-
plemente arranca la aplicacin Django
dentro del servidor que nos provee
Gevent.
Ya tenemos arrancado el servidor pero,
cmo hace uso de Gevent para imple-
mentar el chat?
La Sala de ChatEl objeto ChatRoom, ver Listado 2, es el
encargado de gestionar nuestro chat. Lapgina que se ve en la Figura 1 trabaja tal
como se muestra en la Figura 2. En el
navegador se ejecutan una serie de funcio-
nesjavascriptque hacen uso de la librera
JQuery. Su misin es la de recoger el texto
que escribamos en el campo de texto,
enviarlo a nuestro servidor Geventy mos-
trar las respuestas dentro de la pgina.
Para poder mostrar los mensajes que
otros escriben en la pgina, el cdigo
javascript consultar nuestra pgina
cada 500 milisegundos. Si tratramos dehacer esto con una aplicacin Django
normal y corriente colapsaramos el ser-
un mensaje desde un ordenador aparece
inmediatamente en el otro y viceversa.
En teora podemos aadir a nuestro chat
tantos ordenadores como queramos.
Veamos cmo funciona este pro-
grama.
Monkey PatchingComencemos por el fichero run.py (ver
Listado 1) porque es ah donde
comienza el trabajo de Gevent. A diferen-
cia de otras libreras de bucle de eventos
como Twisted, Geventtrata de no interfe-
rir con el programador. Para ello emplea
una tcnica, muy famosa en el mundo de
Ruby, denominada Monkey Patching.
Consiste en sobreescribir mtodos, cla-
ses y funciones de las libreras base de
Python, respetando su interfaz perocambiando la forma en la que trabajan.
De esta forma podemos beneficiarnos de
01 #!/usr/bin/python
02 from gevent import monkey; monkey.patch_all()
03 from gevent.wsgi import WSGIServer
04 import sys
05 import os
06 import traceback
07 from django.core.handlers.wsgi import WSGIHandler
08 from django.core.management import call_command
09 from django.core.signals import got_request_excep-
tion
10
11 sys.path.append(..)
12 os.environ[DJANGO_SETTINGS_MODULE] =
webchat.settings
13
14 def exception_printer(sender, **kwargs):
15 traceback.print_exc()
16
17 got_request_exception.connect(exception_printer)
18
19 call_command(syncdb)
20 print Serving on 8088...
21 WSGIServer((, 8088), WSGIHandler()).serve_for-
ever()
Listado 1: El Fichero run.py
Figura 1: Escena nocturna con estrellas que caen.
-
7/31/2019 CHATEANDO SIN MESENGER
3/4
vidor rpidamente. Pero gracias a Gevent
podremos gestionar esta carga de trabajo
sin demasiados problemas.
Aunque ChatRoom slo tiene dos
mtodos, su cdigo est tan compactado
que vamos a analizarlos con detalle porseparado.
message_update()Lo que viene a continuacin es un poco
difcil de entender la primera vez, pero
una vez comprendido, los bucles de
eventos en Python dejarn de tener mis-
terios. El secreto de los bucles de eventos
consiste en que el cdigo se divide entre
generadores de eventos y consumidores
de eventos. Los bucles de eventos nos
ofrecen mecanismos mediante los cualespodemos hacer que una ejecucin se
pare, se bloquee, esperando a que un
determinado evento aparezca en escena,
liberando al programa para que mientras
tanto ejecute otro cdigo.
El cdigo comienza rescatando de la
sesin que el navegador tiene en nuestro
servidor el valor para la llave cursor.
Lo que el programa hace es emplear una
variable en la sesin para controlar cul
ha sido el ltimo mensaje que ha reci-
bido ese navegador. En el caso de que nohaya mensajes o el ltimo mensaje
enviado sea el que aparece en la sesin
del navegador bloqueamos con:
self.new_message_event.wait()
Lo que ocurre en ese preciso momento
es lo que hace tan especiales a los bucles
de eventos. Gevent deja bloqueado ese
hilo de ejecucin y almacena tanto el
entorno como la posicin en el programa
en una lista, a la espera de que el evento
self.new_message_event se active. O sea,mientras no haya informacin nueva
para el navegador cuya sesin tenemos
en ese instante, no continuaremos ejecu-
tando el cdigo. Si el evento aparece, se
contina la ejecucin del cdigo. Usando
enumerate se genera una lista con ndi-
ces a partir de la cach, y si alguno de
los mensajes de la cach se corresponde
al cursor, entonces devolvemos en for-
mato JSON todos los mensajes desde ese
punto hasta el final de la cach:
>>> l = [a,b,c,d]
>>> datos = enumerate(l)
>>> for indice,e in datos:
Python: Gevent y Django DESARROLLO
55Nmero 71WWW. L I NUX - M A G A Z I N E . E S
01 class ChatRoom(object):
02 cache_size = 200
03
04 def __init__(self):
05 self.cache = []
06 self.new_message_event = Event()
07
08 def main(self, request):
09 if self.cache:
10 request.session[cursor] = self.cache[-1][id]
11 return render_to_response(index.html,
12 {MEDIA_URL: settings.MEDIA_URL,
13 messages: self.cache})
14
15 def message_new(self, request):
16 name = request.META.get(REMOTE_ADDR) or Anony-
mous
17 forwarded_for = request.META.get(HTTP_X_FOR-
WARDED_FOR)
18 if forwarded_for and name == 127.0.0.1:
19 name = forwarded_for
20 msg = create_message(name, request.POST[body])
21 self.cache.append(msg)
22 if len(self.cache) > self.cache_size:
23 self.cache = self.cache[-self.cache_size:]
24 self.new_message_event.set()
25 self.new_message_event.clear()
26 return json_response(msg)
27
28 def message_updates(self, request):
29 cursor = request.session.get(cursor)
30 if not self.cache or cursor == self.cache[-1][id]:
31 self.new_message_event.wait()
32 assert cursor != self.cache[-1][id], cursor
33 try:
34 for index, m in enumerate(self.cache):
35 if m[id] == cursor:
36 return json_response({messages:
self.cache[index + 1:]})
37 return json_response({messages: self.cache})
38 finally:
39 if self.cache:
40 request.session[cursor] = self.cache[-1][id]
41 else:
42 request.session.pop(cursor, None)
Listado 2: Objeto ChatRoom
Figura 2: Esquema de funcionamiento del chat.
-
7/31/2019 CHATEANDO SIN MESENGER
4/4
de un proxy, y por tanto, tiene una IP
diferente a la que aparece en
REMOTE_ADDR.
Vale, ya tenemos identificado al usua-
rio, ahora generamos la estructura de
datos que contendr su mensaje con la
funcin create_message() y la almacena-
mos en nuestra cach. Como la cach
puede haber alcanzado un tamao supe-
rior al permitido, comprobamos si esto
ocurre y eliminamos de la misma los
mensajes sobrantes. Para ello hacemosuso de una caracterstica de las listas en
Python, los ndices inversos. Son algo
liosos, as que un par de ejemplos aclara-
rn rpidamente cmo funcionan:
>>> l = [1,2,3,4,5]
>>> l[:3]
[1, 2, 3]
>>> l[-3:]
[3, 4, 5]
>>> l[-1]
5>>> l[-30:]
[1,2,3,4,5]
En nuestro caso slo queremos los pri-
meros 200 elementos de la lista comen-
zado desde atrs, as que empleamos
self.cache[-self.cache_size:] para obte-
nerlos. Todo est listo para el momento
crucial, ese que hemos estado espe-
rando. Como la lista de mensajes
self.cache est preparada y podemostener unos cuantos message_update()
bloqueados esperando, mandamos una
seal indicando que hay nueva infor-
macin mediante la llamada
self.new_message_event.set(). Todos
los puntos del cdigo fuente que se blo-
quearon en el evento self.new_mes-
sage_event con wait() pasan a desblo-
quearse y a ejecutarse uno a uno. En
nuestro caso esto implica que un gran
nmero de navegadores, cuyo cdigo
javascript estaba tratando de obtenerinformacin en la direccin
messages/update, de pronto encuentran
que se les responde con datos, y se
actualizarn con los ltimos mensajes
del chat (ver Figura 3). En cuanto la
lista de bloqueo se vace llamamos a
self.new_message_event.clear() para
volver a poner el evento a False. Si no
lo hicisemos, la llamada a
self.new_message_event.wait() no blo-
queara el cdigo y se pasara a la
siguiente instruccin inmediatamente.Nuestro sistema vuelve a estar listo
para un nuevo mensaje desde el chat y
todas las llamadas a message/updates se
bloquern.
ConclusinLos bucles de eventos estn ganando la
partida a las hebras como modelo para
sistemas concurrentes de alto rendi-
miento. El xito sin precedentes de
Nginx (Recurso 2) un servidor web
basado en eventos o la popularidad deNode.js ( Recurso 3) se la debemos a esta
tecnologa.
Como hemos podido ver en este art-
culo, Python no slo cuenta con varias
implementaciones de bucles de eventos,
sino que adems algunas como Gevent
son muy sencillas de usar y nos permi-
ten generar comportamientos muy com-
plejos con menos de 100 lneas de
cdigo. I
... if e == b:
... print l[indice:]
...
[b, c, d]
>>>
Por ltimo, actualizamos nuestro cursor
a la ltima posicin de la cach en ese
momento.
message_new()
El cdigo de message_new() en el Lis-tado 2 comienza por averiguar quin ha
mandado el mensaje. Toda peticin
HTTP posee usa serie de campos que
identifican a la mquina que la ha reali-
zado, pero es posible que esa mquina
decida no poner esa informacin, por lo
que el cdigo se cura en salud, y en lugar
de tratar de cargar el campo
REMOTE_ADDR (direccin remota) hace
uso del mtodo get() de los diccionarios,
que devuelve None en el supuesto de
que no exista la llave. Si fuese ese elcaso, llamaremos a nuestro usuario
Anonymous. El campo HTTP_X_FOR-
WARDED_FOR sirve para que el paquete
HTTP nos indique si la mquina que ha
originado la peticin se encuentra detrs
DESARROLLO Python: Gevent y Django
56 Nmero 71 WWW. L I NUX - M A G A Z I N E . E S
[1] Gevent: http://www.gevent.org/
[2] Servidor web Nginx:http://nginx.org/
[3] Servidor basado en eventos Node.js:
http://nodejs.org/
RECURSOS
Figura 3: La ejecucin en message_update se bloquea hasta que message_new lo libera.