CHATEANDO SIN MESENGER

download CHATEANDO SIN MESENGER

of 4

Transcript of 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.