Anda di halaman 1dari 4

Python: Thrift DESARROLLO

Serializacin estilo Web 2.0


dos versiones. Cualquier objeto serializable en Python puede serializarse con Pickle y recuperarse de nuevo intacto. Aunque parezca una burrada, Sqlite puede considerarse un formato de serializacin. En principio no existe nada que nos impida usar bases de datos Sqlite como formato para transportar nuestros datos entre aplicaciones. Ser lento, pero funcionar. Todos estos formatos tienen algn tipo de problema, ya sea la complejidad de XML, la extrema simplicidad de JSON, la falta de interoperabilidad de Pickle o la lentitud de usar Sqlite. Cuando la serializacin es vital para el funcionamiento de nuestros sistemas, necesitamos algo ms potente.

Hacer que distintos servicios se comuniquen entre ellos es todo un problema que Facebook ha tratado de solucionar con Thrift. POR JOS MARA RUZ

Oksana Tkachuk - 123RF.com

Instalacin de Apache Thrift


Thrift es un protocolo de serializacin binario y tipado que define tanto datos como servicios (ver Figura 1). Al ser binario es mucho ms eficiente que los formatos de texto, y puede enviar la misma cantidad de informacin de forma ms compacta, ahorrando ancho de banda. Adems est tipado, lo que significa que toda informacin que se transmita ser de un tipo de dato determinado. Esto es imprescindible si queremos ser capaces de intercambiar datos con lenguajes tipados como C#, Java o C++. Podemos descargar el cdigo fuente de Thrift desde el enlace que aparece en el Recurso 1. Necesitaremos disponer de flex as como de la librera de desarrollo Boost en nuestro sistema para poder compilar Thrift (en Ubuntu necesitaremos el paquete libboost-dev, por ejemplo). Una vez la tengamos instalada deberemos descomprimir el fichero, compilarlo e instalarlo. Es recomendable ejecutar el configure indicando los lenguajes para los que no queremos generar un compilador, puesto que tratar de generar los compiladores de todos los lenguajes:
shell$ ./configure U -without-javaU --without--csharp ... shell$ make shell$ sudo make install

a informacin quiere ser libre es uno de los lemas hacker, pero habra que preguntarse cmo se mover una vez que lo sea. En un mundo como el actual, donde la informacin y su procesado pueden encontrarse distribuidos en decenas (o miles!) de servidores, es preciso contar con algn mecanismo que permita comunicar sistemas informticos de todo tipo sin tener que recurrir a tecnologas de bajo nivel. Este problema ha sido resuelto por muchos mediante el empleo del protocolo HTTP, pero cuando lo que buscamos es rendimiento, HTTP puede no ser una opcin. No es de extraar que dos de las empresas de referencia de Internet, Facebook y Google, se hayan enfrentado a este mismo problema y hayan creado, de forma totalmente independiente, dos tecnologas que buscan solucionarlo: Thrift [1] y Protocol Buffers [2]. Tanto Facebook como Google necesitaban una forma de poder comunicar sistemas informticos desarrollados en diferentes lenguajes de programacin para que pudiesen intercambiar datos entre ellos. Por ms que queramos a nuestro querido Python, cuando el rendimiento es una prioridad no es nuestra mejor opcin. Y puede ocurrir tambin lo contrario: por mucho que nos guste Java, en numerosas ocasiones la velocidad de desarrollo con Python no tiene rival! En este nmero echaremos un vistazo a la tecnologa Thrift de Facebook y vere-

mos cmo Python puede usarla para comunicarse con un sistema Java que est ganando gran popularidad: ElasticSearch.

Serializando
Al acto de transformar un formato interno de datos en algo que podamos transmitir o almacenar de forma externa se le suele denominar serializar. Existe una cantidad inimaginable de formatos que podemos usar para serializar datos, y Python viene de serie con varias opciones: XML JSON Pickle Sqlite Todos ellos cuentan con ventajas e inconvenientes. XML es uno de los formatos ms extendidos del mundo. Al ser un formato de texto es fcilmente manipulable, y es posible editarlo a mano. Su nivel de complejidad es seleccionable, podemos hacer que sea tan complejo y almacene tanta informacin como deseemos. JSON fue la respuesta de la Web a la complejidad de XML. Al igual que l, es un formato de texto, pero su estructura est cerrada. Es sorprendentemente simple y sencillo de manejar, lo que no ayuda demasiado en situaciones complejas. Pickle es en realidad un mecanismo propio de Python para la superlacin de objetos. Ningn otro lenguaje parece disponer de soporte para l, y cuenta con al menos

Podemos comprobar que se ha instalado correctamente ejecutando el compilador de Thrift:

WWW.LINUX- MAGAZINE.ES

Nmero 73

47

DESARROLLO Python: Thrift

Ya tenemos todo lo necesario para usar Thrift, pasemos a usarlo.

Listado 1: Fichero hola.thrift


01 service Saludos { string hola(1: string 02 nombre) 03 }

Hola mundo
Thrift funciona como un compilador (Figura 2) que acepta una descripcin de los datos y servicios, la interfaz, que vamos a utilizar y a partir de la cual generar una librera en el lenguaje de destino que codificar y decodificar ese formato en el lenguaje de programacin que le indiquemos. Es ms sencillo verlo con un ejemplo. Digamos que queremos disponer de un servicio llamado hola que acepta una cadena de texto y devuelve otra cadena de texto. Lo primero que necesitaremos es crear un fichero de descripcin en el formato de Thrift como el que aparece en el Listado 1. Una vez tengamos el fichero listo, podemos generar el cdigo Python con el siguiente comando:
shell$ thrift -gen pyU hola.thrift

ponder a la interfaz declarada en el fichero hola.thrift, mientras que en Listado 3 se encuentra el cdigo que usar este servicio. Para verlos en funcionamiento tendremos que arrancar primero el servidor en un terminal:
shell$ python servidor.py Arrancando el servidor...

Figura 1: Esquema de trabajo con Thrift.

Y el cliente en otro terminal:


shell$ python cliente.py Hola mundo shell$

shell$ thrift Usage: thrift [options] file Options: -version Print the ?. ....

Necesitamos un componente ms para poder hacer uso de Thrift: la librera python thrift. Instalarla es mucho ms sencillo:
shell$ pip install thrift

El comando generar un directorio llamado gen-py que contendr el mdulo que vamos a usar. Debemos copiar el mdulo hola al directorio en el que vayamos a ejecutar tanto el script del Listado 2 como el del Listado 3. El cdigo del Listado 2 genera un servidor de red que res-

Ha funcionado! Thrift nos permite arrancar un servidor de red con el servicio que hemos definido. La librera se encarga de prcticamente todo, lo que nos permite concentrarnos en crear el cdigo fuente de nuestro servicio. Pero es Thrift rpido? Hagamos una prueba. Con el servidor an arrancado, vamos a mandar

Listado 2: Fichero servidor.py


01 #!/usr/bin/env python 02 03 import sys 04 05 from hola import Saludos 06 from hola.ttypes import * 07 08 from thrift.transport import TSocket 09 from thrift.server import TServer 10 11 ## Servicio 12 class SaludosHandler: 13 def hola(self,nombre): 14 return Hola {0}.format(nombre) 15 16 ## Pasos necesarios para arrancar 17 ## el servidor 18 handler = SaludosHandler() 19 processor = Saludos.Processor(handler) 20 transport = TSocket.TServerSocket(9090) 21 tfactory = TTransport.TBufferedTransportFactory() 22 pfactory = TBinaryProtocol.TBinaryProtocolFactory() 23 24 ## Arrancamos 25 servidor = TServer.TSimpleServer(processor, transport, tfactory, pfactory) 26 27 print Arrancando el servidor... 28 servidor.serve() 29 print acabamos.

Listado 3: Fichero cliente.py


01 import sys 02 from hola import Saludos 03 from hola.ttypes import * 04 from hola.constants import * 05 06 from thrift import Thrift 07 from thrift.transport import TSocket 08 from thrift.transport import TTransport 09 from thrift.protocol import TBinaryProtocol 10 11 try: 12 transport = TSocket.TSocket(localhost, 9090) 13 transport = TTransport.TBufferedTransport(t ransport) 14 protocol = TBinaryProtocol.TBinaryProtocol(transport) 15 16 cliente = Saludos.Client(protocol) 17 18 ## Conectamos 19 transport.open() 20 21 cadena = cliente.hola(mundo) 22 print cadena 23 24 ## Cerramos la conexin 25 transport.close() 26 except Thrift.TException, tx: 27 print %s % (tx.message)

48

Nmero 73

WWW.LINUX- MAGAZINE.ES

Python: Thrift DESARROLLO

10000 mensajes mediante el cdigo del Listado 4:


shell$ time python U test_velocidad.py real 0m1.715s user 0m0.820s sys 0m0.156s

No est nada mal, siendo Python, y sin usar ninguna optimizacin. Podemos parar el servidor pulsando la combinacin de teclas Control+C.

Cmo Funciona el Cdigo Fuente


Analicemos el cdigo del Listado 2. Cargamos el mdulo hola resultado de nuestra definicin en el fichero hola.thrift, y que se encontraba dentro del directorio gen-py. Este mdulo es en realidad el nombre del propio fichero, que Thrift ha convertido en mdulo, por lo que hay que tener cuidado con el nombre que demos al fichero .thrift. Dentro del fichero hemos definido un servicio llamado Saludos. Thrift nos permite reunir grupos de funciones y variables bajo servicios. As es muy sencillo organizar nuestro cdigo. Pasamos a cargar el fichero ttypes que contiene todas las funciones, objetos y variables que necesitaremos para usar Thrift. Nuestro servicio ser un objeto Python con mtodos que tengan los mismos nombres y parmetros que definimos en el fichero hola.thrift. Por convencin se aade la palabra Handler al nombre del servicio que vamos a implementar, en nuestro caso SaludosHanlder. Mediante el mtodo Saludos.Procesor() indicamos qu objeto implementar la interfaz definida. Al objeto resultante se le suele llamar processor, puesto que su funcin ser procesar peticiones. En este punto podemos elegir cmo vamos a usar nuestro procesador. Thrift

debe trabajar sobre algn protocolo de comunicaciones, ofreciendo varias posibilidades dependiendo del lenguaje de programacin que usemos. En Python es posible usar un socket, el protocolo http o Twisted. Por simplicidad vamos a usar un socket, que es el protocolo de ms bajo nivel, mediante la clase TSocket. Sobre el protocolo de comunicaciones debemos montar un servidor, TServer, que atienda los mensajes que lleguen y se los pase al procesador. Como puedes ver, los nombres son bastante descriptivos. Thrift nos obliga an a hacer algunas elecciones. Debemos indicar al servidor qu clase de protocolo vamos a usar en nuestro caso TBinaryProtocol y cmo queremos que se comporte el servidor, usando un bfer con TBufferedTransport. Thrift es configurable y nos ofrece diferentes opciones para casi todo. Podramos haber seleccionado un protocolo basado en JSON mediante TJSONProtocol, por ejemplo. Ya slo nos falta arrancar el servidor, instanciando por ejemplo TSimpleServer y llamando al mtodo server() que se bloquear mientras no lleguen mensajes.

Figura 2: Http vs Thrift.

ElasticSearch
Como ejemplo del uso de Thrift vamos a crear un pequeo programa Python que emplee esta tecnologa para interactuar con el motor de bsqueda de moda: ElasticSearch [3]. ElasticSearch est revolucionando el mundo de los motores de bsqueda. Ofreciendo el rendimiento de Solr/ Lucene, pero aadiendo la capacidad de trabajar de forma distribuida, reparte el ndice de bsqueda entre varias mquinas de forma automtica. Est programado en Java y ofrece varios protocolos de trabajo, siendo posible comunicarse con el servidor ElasticSearch mediante Rest sobre http o Thrift (Figura 3). Para instalar ElasticSearch slo tenemos que descargarlo desde la direccin del Recurso 3 :

shell$ wget -c U https://github.com/downloads/U elasticsearch/elasticsearch/U elasticsearch-0.16.2.tar.gz shell$ tar zxpf U elasticsearch-0.16.2.tar.gz shell$ cd elasticsearch-0.16.2 shell$ cd bin shell$ ./plugin -install U transport-thrift shell$ ./elasticsearch -f

Listo! Ya tenemos funcionando un motor de bsqueda con ndice distribuido y que se comunica usando Thrift. Es normal que ElasticSearch se est ganando el corazn de muchos desarrolladores. Debemos generar el mdulo de la interfaz de Thrift para Python. Para ello debemos descargar el fichero elasticsearch.thrift de la direccin que aparece en el Recurso 4. Y compilarlo:
shell$ thirft --gen U py elasticsearch.thrift

Cuando tengamos el directorio gen-py, extraemos de su interior el mdulo elasticsearch y lo ubicamos en el mismo directo-

Listado 4: Test de Velocidad


01 import sys 02 sys.path.append(./gen-py) 03 04 from hola import Saludos 05 from hola.ttypes import * 06 from hola.constants import * 07 08 from thrift import Thrift 09 from thrift.transport import TSocket 10 from thrift.transport import TTransport 11 from thrift.protocol import TBinaryProtocol 12 13 transport = TSocket.TSocket(localhost, 9090) 14 transport = TTransport.TBufferedTransport(t ransport) 15 protocol = TBinaryProtocol.TBinaryProtocol(transport) 16 17 cliente = Saludos.Client(protocol) 18 19 transport.open() 20 21 for i in range(0,10000): 22 cadena = cliente.hola(mundo) 23 24 transport.close()

WWW.LINUX- MAGAZINE.ES

Nmero 73

49

DESARROLLO Python: Thrift

rio en el que pongamos el script del Listado 5. Cuando lo ejecutemos, ste ser el resultado:
shell$ time python busqueda.py [{u_score: 0.38431653, U u_type: uarticulo, U u_id: u1, u_source: {utitulo: U uThrift, Python y U ElasticSearch}, u_index: ulinuxmagazine}] real 0m0.353s user 0m0.040s sys 0m0.016s

Hemos creado un ndice, aadido un modelo de documento, insertado un documento y realizado 100 bsquedas en 300 milisegundos. ElasticSearch trabaja usando una API Rest que acepta comandos codificados en URIs (rutas) mediante los mtodos tpicos de HTTP. Tanto los datos enviados como los recibidos se codifican en JSON, que podemos codificar y decodificar empleando la librera json de Python. El esquema de trabajo es parecido al que hemos visto con anterioridad. Creamos una conexin usando TSocket, especificamos el tipo de transporte y el protocolo (en este caso una variante del binario) y generamos un cliente. La clase RestRequest ha sido generada por Thrift y tiene cuatro parmetros: Mtodo (POST, GET, PUT) URI (<indice>/<modelo>/<id>)

Cabeceras (Headers) Cuerpo del mensaje (Body) Algunos mtodos exigen el uso del body (por ejemplo los que requieren el mtodo POST), mientras que otros slo requieren el URI (GET) Y por qu usamos nmeros para el tipo de mtodo usado? Si echamos un vistazo al fichero elasticsearch.thrift veremos que ah se declaran los nmeros que usaremos para los mtodos. En nuestro ejemplo podemos ver diferentes mtodos en uso. Crear un ndice exige un POST, aadir un modelo, un PUT y hacer una consulta, un GET. Cada llamada de ejecucin de un request devuelve un objeto RestResponse con un campo body, en el que encontraremos el resultado codificado en JSON.

Conclusin
Thrift puede parecer algo complejo ahora que todos nos hemos acostumbrado a emplear HTTP como protocolo para las peticiones remotas. Pero existen muchas situaciones en las que necesitaremos utilizar un protocolo que consuma menos ancho de banda y ofrezca ms rendimiento. Tanto Facebook como Google han tenido que desarrollar su propia tecnologa para solventar este problema, y ambos han tenido la gentileza de liberarla como software libre. Y por si fuese poco, ambos sistemas generan cdigo Python, todo un regalo para nuestra comunidad. I
Figura 3: Funcionamiento de ElasticSearch.

RECURSOS
[1] Tecnologas Thrift de http://thrift.apache.org/ Facebook: [2] Tecnologa Protocol Buffers de Google: http://code.google.com/p/ protobuf/ [3] Motor de bsqueda ElasticSearch: http://www.elasticsearch.org/ [4] Fichero thrift para ElasticSearch: https://github.com/elasticsearch/ elasticsearch/blob/master/plugins/ transport/thrift/elasticsearch.thrift

Listado 5: Interactuando con ElasticSearch


01 from thrift import Thrift 02 from thrift.transport import TTransport 03 from thrift.transport import TSocket 04 from thrift.protocol.TBinaryProtocol import TBinaryProtocolAccelerated 05 06 from elasticsearch import Rest 07 from elasticsearch.ttypes import * 08 09 import json 10 11 socket = TSocket.TSocket(localhost, 9500) 12 transport = TTransport.TBufferedTransport(s ocket) 13 protocol = TBinaryProtocolAccelerated(transport) 14 client = Rest.Client(protocol) 15 16 transport.open() 17 18 ## Creamos un ndice 19 request = RestRequest(method=1, uri=/linuxmagazine, 20 headers={}, body=) 21 client.execute(request) 22 23 ## Cargamos un modelo de documento 24 mapping = json.dumps({properties: { 25 titulo : {type : string, store : yes}}}) 26 request = RestRequest(method=2, uri=/linuxmagazine/articulo, 27 headers={}, body= mapping) 28 client.execute(request) 29 30 ## Cargamos un documento 31 articulo = json.dumps({titulo : Thrift, Python y ElasticSearch}) 32 request = RestRequest(method=2, uri=/linuxmagazine/articulo/1, 34 headers={}, 35 body= articulo) 36 respuesta = client.execute(request) 37 38 ## Buscamos la cadena thrift 39 ruta = /linuxmagazine/articulo/_search?q=thrift 40 for i in range(0, 100): 41 request = RestRequest(method=0, 42 uri=ruta, 43 headers={}, 44 body= ) 45 respuesta = client.execute(request) 46 47 print json.loads(respuesta.body)[hit s][hits] 48 49 transport.close() 33

50

Nmero 73

WWW.LINUX- MAGAZINE.ES

Anda mungkin juga menyukai