Sistema de Mensajeria de Colas con ZeroMQ y Python
1. Sistema de Mensajeria de Colas con ZeroMQ y Python
Ernesto Crespo
Twitter: @_seraph1
Email: ecrespo@gmail.com
Blog: http://ernesto-ecrespo.blogspot.com
2. Agenda
● ¿Qué es ZeroMQ?
● Implementar una capa de mensajes con ZeroMQ
● Tipos de transporte
● Patrones de mensajes
● Seleccionar infraestructura
● Patrones de comunicación:
● Patrón Solicitud/Respuesta (Request/Reply)
● Patrón Suscriptor/Publicador(PUB/SUB)
● Patrón PUSH/PULL
● Patrón de mensaje PAR
● Otros ejemplos:
● REQ/REP y PUB/SUB
● Multicast
● Benchmarks
● Licencia
● Referencias
3. ¿Qué es ZeroMQ?
Es una librería asíncrona de alto rendimiento de mensajería
destinado a su uso en aplicaciones distribuidas escalables
o concurrentes.
Es una librería que soporte distintos patrones de comunicación de redes.
ZeroMQ usa colas internamente para el buffer de mensajes para no
Bloquear aplicaciones al enviar mensajes.
Rápido: 8M msg/seg ,30useg de latencia.
Licencia: LGPL
Multiplataforma y multilenguajes(más de 30 lenguajes de programación)
4. Implementar una capa de mensajes con ZeroMQ
Para implementar una capa de mensajes ZeroMQ se necesitan 3 pasos:
1. Seleccionar transporte (ipc,tcp,epgm,inproc y pgm).
2. Seleccionar infraestructura.
3. Seleccionar patrón de mensaje.
5. Tipos de transporte
ZeroMQ soporta varios estilos de capa de transporte:
● tcp://equipo:puerto, procesos en una red TCP.
● inproc://equipo, hilos en un proceso.
● ipc:///tmp/archivo socket Unix que permite una comunicación
entre procesos.
● pgm://interface:dirección:puerto y epgm://interface:dirección:puerto
soporta la librería OpenPGM para comunicación multicast sobre IP
(pgm) y sobre UDP (epgm).
6. Patrones de mensajes
Los patrones básicos de ZeroMQ son:
● Request/Reply: bidireccional, balanceo de carga y basado en estado.
● Publish/Subscribe: publica multiples recipientes en uno.
● Push/Pull (pipeline): distribuye datos a nodos en un pipeline.
● Pair: comunicación exclusiva entre pares.
7. Seleccionar infraestructura
Luego de definir el tipo de transporte es necesario pensar como los
diferentes componentes se conectan.
Tipos de dispositivos:
1. Queue: un forwarder para el patrón req/resp.
2. Forwarder: un forwarder para el patrón PUB/SUB.
3. Streamer: un forwarder para el patrón pipelined.
8. Patrón REQ/REP
Comunicación bidireccional que facilita el balanceo de carga y se
basa en estados.
Este patrón es muy común y es normalmente usado por varios servicios
como: HTTP, POP o IMAP.
9. Servidor REQ/REP
#!/usr/bin/env python
#Se importa zeromq
import zmq
#Se crea la instancia del contexto de zmq.
context = zmq.Context()
#Se define el socket con parámetro respuesta REP.
socket = context.socket(zmq.REP)
#Se asocia la dirección IP y el puerto donde el servidor escucha las peticiones.
socket.bind("tcp://127.0.0.1:5000")
#Se define un contados
c=1
#Se genera un ciclo que sólo finaliza si se recibe la letra q.
while True:
#Se recibe los mensajes.
msg = socket.recv()
#Se consulta si la longitud del mensaje es 1 y es la letra q se termina el ciclo
if len(msg) == 1 and msg == "q":
break
#Se separa los datos que viene en un string separados por :
datos = msg.split(":")
#se realiza una suma con los datos recibidos.
resultado = int(datos[0]) + int(datos[1])
#Se muestra en pantalla el resultado
print "Iteracion: %s ,He recibido: %s, el resultado es: %s " %(c,msg,resultado)
#Se envía el resultado al cliente
socket.send(str(resultado))
#Se incrementa el contador.
c += 1
10. Cliente REQ/REP
#!/usr/bin/env python
#Se importa zeromq y random
import zmq
import random
#Se crea la instancia del contexto
context = zmq.Context()
#Se crea el socket y se para el argumento de petición REQ
socket = context.socket(zmq.REQ)
#Se coencta a la IP y puerto donde escucha el servidor
socket.connect("tcp://127.0.0.1:5000")
#Se genera un ciclo de 1000 repeticiones
for i in range(1000):
#Se crea el string con el mensaje, se pasa 2 aargumentos aleatorios
msg = "%s:%s" %(random.randint(1, 1000),random.randint(1, 1000))
#Se envia el mensaje al servidor
socket.send(msg)
#Se recibe el mensaje del servidor
msg_in = socket.recv()
#Se muestra en patalla los datos y el resultado
print "Iteracion: %s, Enviado: %s, Recibido: %s" %(i,msg,msg_in)
#Si se llea a la iteración 999 se envía la letra q para finalizar
if i == 999: socket.send("q")
11. Patrón PUB/SUB
En este patrón los componentes son pobremente acoplados, son de
gran ayuda para escalar ya que no existe necesidad de preocuparse
de los suscriptores.
Se puede pensar en servicios como XMPP o twitter, donde los mensajes
sólo le llegan a las personas que les toca recibirlos.
12. Servidor Patrón PUB/SUB
#!/usr/bin/env python
#Se importa ZeroMQ
import zmq
#Se importa choice a partir de random
from random import choice
#Se crea la instancia del contexto
context = zmq.Context()
#Se crea el socket pasandole argumento de publicacion PUB
socket = context.socket(zmq.PUB)
#Se asocia la IP y el puerto que va a escuchar.
socket.bind("tcp://127.0.0.1:5000")
#Se importa sleep
from time import sleep
#Se crea una lista de paises y de eventos
paises = ['holanda','brasil','alemania','portugal','argentina','italia','rusia','venezuela']
eventos = ['tarjeta amarilla','tarjeta roja','gol','corner','falta']
13. Servidor Patrón PUB/SUB
#Se crea un contador con valor inicial 1
c=1
#Se crea un ciclo indefinido
while True:
#Se define un mensaje pasando de forma aleatoria un pais y un evento
mensaje = choice( paises) + " " + choice(eventos)
#Se muestra en pantalla el valor del contador y el mensaje.
print "->",c , mensaje
#Se envia el mensaje
socket.send(mensaje)
#Se genera un retardo de 1 seg
sleep(1)
#Se incrementa el contador
c += 1
#Si se llega a 180 se termina el ciclo si no continua.
if c == 180:
break
else:
continue
14. #!/usr/bin/env python
Cliente 1 Patrón PUB/SUB
#Se importa zeroMQ
import zmq
#Se importa sleep a partir de time
from time import sleep
#Se crea la instancia del contexto de zeroMQ
context = zmq.Context()
#Se crea el socket del suscriptor SUB
socket = context.socket(zmq.SUB)
#Se crea la conexion a la IP y puerto del servidor
socket.connect("tcp://127.0.0.1:5000")
#Se define una opcion del socket del suscriptor con argentina y venezuela
socket.setsockopt(zmq.SUBSCRIBE, "argentina")
socket.setsockopt(zmq.SUBSCRIBE, "venezuela")
#Se define el valor inicial de un contador
c=1
#Se crea un ciclo indefinido
while True:
#Se muestra en pantalla el valor del contador y el mensaje recibido
print c, "->",socket.recv()
#Se genera un retardo de 1 seg en cada ciclo
sleep(1)
#Se incrementa el contador en 1
c += 1
#Si el contador llega a 90 se termina el ciclo, si no continua
if c == 90:
break
else:
continue
15. #!/usr/bin/env python
Cliente 2 Patrón PUB/SUB
#Se importa zeroMQ
import zmq
#Se importa sleep a partir de time
from time import sleep
#Se crea la instancia del contexto de zeroMQ
context = zmq.Context()
#Se crea el socket del suscriptor SUB
socket = context.socket(zmq.SUB)
#Se crea la conexion a la IP y puerto del servidor
socket.connect("tcp://127.0.0.1:5000")
#Se define una opcion del socket del suscriptor con brasil y alemania
socket.setsockopt(zmq.SUBSCRIBE, "brasil")
socket.setsockopt(zmq.SUBSCRIBE, "alemania")
#Se define el valor inicial de un contador
c=1
#Se crea un ciclo indefinido
while True:
#Se muestra en pantalla el valor del contador y el mensaje recibido
print c, "->",socket.recv()
#Se genera un retardo de 1 seg en cada ciclo
sleep(1)
#Se incrementa el contador en 1
c += 1
#Si el contador llega a 90 se termina el ciclo, si no continua
if c == 90:
break
else:
continue
16. Emisor, ejemplo Patrón PUSH/PULL
#!/usr/bin/python
#Se importa zeroMQ y random
import zmq
import random
#Se crea la instancia del contexto
context = zmq.Context()
#Se crea el socket con el argumento PUSH
envio =context.socket(zmq.PUSH)
#Se asocia el socket a escuchar todas las IPs y el puerto 5557
envio.bind("tcp://*:5557")
#se muestra que es necesario esperar que arranquen los workers
print "Hay que esperar que los workers se inicien"
#Al dar enter se inicia el proceso de transmision
raw_input()
print "Se inicia la transmision del trabajo..."
#tupla de strings que se van a enviar
cadenas = ['hola', 'aloha','hello','buenas noches','buenas tardes','buenos dias','bienvenido']
#Se crea un ciclo para recorrer la tupla
for i in range(len(cadenas)):
cadena = cadenas[i]
envio.send(cadena)
print "Enviando: {0}".format(cadena)
17. Workers, ejemplo Patrón PUSH/PULL
#!/usr/bin/python
#Se importa ZeroMQ y sleep de time
import zmq
from time import sleep
#Se crea la instancia del contexto
context = zmq.Context()
#Se define el Socket con argumento PULL
recepcion = context.socket(zmq.PULL)
#Se conecta el socket a localhost puerto 5557
#Es el puerto donde origen envia con PUSH los datos
recepcion.connect("tcp://localhost:5557")
#Se crea el socket de envio de los datos procesados con argumento PUSH
envio = context.socket(zmq.PUSH)
#Se conecta el socket a localhost y puerto 5558
envio.connect("tcp://localhost:5558")
#Se genera un ciclo
#donde se recive lo transmitido por origen
#se procesa (se coloca en mayusculas)
#se muestra en pantalla y se envia.
#los ciclos tienen un retardo de 1 seg
while True:
cadena = recepcion.recv()
print "Proceso:{0}".format(cadena)
envio.send(cadena.upper())
sleep(1)
18. Resultado, ejemplo Patrón PUSH/PULL
#!/usr/bin/python
#Se importa ZeroMQ
import zmq
#Se crea la instancia del contexto
context = zmq.Context()
#Se crea el socket PULL que recibe los mensajes de los workers
recepcion = context.socket(zmq.PULL)
#Se asocia el socket a escuchar todas las IPs en el puerto 5558
#el puerto donde los workers envian los mensajes
recepcion.bind("tcp://*:5558")
#Se inicia un ciclo donde se recibe los mensajes
#de los workers y se muestra en pantalla
while True:
mensaje = recepcion.recv()
print "Recibo: {0}".format(mensaje)
19. Patrón Pair
Comunicación exlusiva entre pares.
Lo que se logra es que si otro nodo intenta conectarse no lo logrará si
no está declarado.
Si está declarado, este establece conexión pero saca al nodo anterior.
20. Servidor Patrón Pair
#!/usr/bin/env python
#Importar zmq
import zmq
#Se crea la instancia del contexto
context = zmq.Context()
#Se crea el socket del tipo PAR
socket = context.socket(zmq.PAIR)
#Se asocia a una IP y puerto donde escucha el servidor.
socket.bind("tcp://127.0.0.1:5555")
#Se crea un ciclo.
while True:
#Se recibe un mensaje del cliente
mensaje = socket.recv()
#Se muestra en pantalla
print "Recivo", mensaje
#Se envia de vuelta el mensaje
socket.send(mensaje)
21. Cliente Patrón Pair
#!/usr/bin/env python
#Se importa zmq
import zmq
#Se crea la instancia del contexto
context = zmq.Context()
#Se crea el socket con argumento del tipo de mensaje Par.
socket = context.socket(zmq.PAIR)
#Se conecta al servidor dado la IP y puerto.
socket.connect("tcp://127.0.0.1:5555")
#Se crea un cilo de 100 repeticiones.
for i in range(100):
#Se define el mensaje a pasar
mensaje = "mensaje %s" % i
#Se pasa el mensaje al servidor
socket.send(mensaje)
#Se presenta en pantalla el mensaje
print "Enviando", mensaje
#Se recibe el mensaje de vuelta
msg_in = socket.recv()
#Se presenta en pantalla el mensaje de vuelta
print "Recibido:", msg_in
22. Patrones REQ/REP y PUB/SUB
Emisor
#!/usr/bin/python
#Se importa zeroMQ
import zmq
#Se crea el contexto
context = zmq.Context()
#Se crea el socket con el parametro REQ
socket = context.socket(zmq.REQ)
#Se asocia la IP y el puerto del socket.
socket.connect("tcp://127.0.0.1:4000")
#Se genera los mensajes estilo tuiter y se envia al socket.
for i in ['@_seraph1 Esta es una prueba','@otro viendo el juego', '@_seraph1 otra prueba','@otro otro']:
socket.send(i)
msg_in = socket.recv()
23. Patrones REQ/REP y PUB/SUB
Tuiter
#!/usr/bin/env python
#Se importa zeroMQ
import zmq
#Se importa choice de random
from random import choice
#Se crea el contexto
context = zmq.Context()
#Se define el socket de recepcion con argumento REP
socket_recv = context.socket(zmq.REP)
#Se asocia a una IP y puerto el socket de recepcion
socket_recv.bind("tcp://127.0.0.1:4000")
#Se define el socket de publicacion con argumento PUB
socket = context.socket(zmq.PUB)
#Se asocia la ip y un puerto distinto al anterio socket
socket.bind("tcp://127.0.0.1:5000")
#Se crea un ciclo
while True:
#Se recibe el mensaje del socket de recepcion
msg = socket_recv.recv()
#Se envia el mensaje de recepcion
socket_recv.send(msg)
#Se muestra el mensaje en pantalla
print "Reenvio: {0}".format(msg)
#Se envia el mensaje al socket de publicacion
socket.send(msg)
24. Patrones REQ/REP y PUB/SUB
Receptor 1:
#!/usr/bin/python
#Se importa zeroMQ
import zmq
#Se crea el contexto
context = zmq.Context()
#Se crea el socket de suscripcion
socket = context.socket(zmq.SUB)
#Se asocia ese socket a la IP y puerto donde publica tuiter
socket.connect("tcp://127.0.0.1:5000")
#Se suscribe a escuchar los mensajes de @_seraph1
socket.setsockopt(zmq.SUBSCRIBE, "@_seraph1")
#se crea un ciclo donde se recibe los mensajes
while True:
print "->",socket.recv()
25. Patrones REQ/REP y PUB/SUB
Receptor 2:
#!/usr/bin/python
#Se importa zeroMQ
import zmq
#Se crea el contexto
context = zmq.Context()
#Se crea el socket de suscripcion
socket = context.socket(zmq.SUB)
#Se asocia ese socket a la IP y puerto donde publica tuiter
socket.connect("tcp://127.0.0.1:5000")
#Se suscribe a escuchar los mensajes de @otro
socket.setsockopt(zmq.SUBSCRIBE, "@otro")
#se crea un ciclo donde se recibe los mensajes
while True:
print "->",socket.recv()
26. Multicast
Productor:
#!/usr/bin/env python
#
# producer
#Se importa ZeroMQ
import zmq
#Se crea la instancia de la clase Context.
context = zmq.Context()
#Se define el socket con parametro PUB
socket = context.socket(zmq.PUB)
#Se define unas opciones en el socket
#esta opcion LINGER con valor cero, descarta mensajes no enviados
socket.setsockopt(zmq.LINGER, 0)
#Se conecta al socket a la IP y puerto
#por medio de multicast
socket.connect('epgm://192.168.10.96:5000')
#Se crea un ciclo
#si se envia el texto salir, se envia y luego finaliza el ciclo
#si no, se envia el texto
while True:
mensaje = raw_input("->")
print mensaje
socket.send(mensaje)
if mensaje == "salir": break
socket.close()
27. Multicast
Consumidor:
#!/usr/bin/env python
#Importar zeroMQ
import zmq
#Se crea la instancia de la clase contexto
context = zmq.Context()
#Se define el socket con parametro SUB
socket = context.socket(zmq.SUB)
#Se conecta el socket a la IP y puerto del productor
#con el transporte epgm
socket.connect('epgm://192.168.10.96:5000')
#Se define los textos que se recibe la informacion.
#prueba, hora y salir.
socket.setsockopt(zmq.SUBSCRIBE, 'prueba')
socket.setsockopt(zmq.SUBSCRIBE, 'hora')
socket.setsockopt(zmq.SUBSCRIBE, 'salir')
#Se define un ciclo,
#se recibe la informacion
#Si el texto es salir se muestra en pantalla
#finaliza el ciclo, si no se muestra
#el texto en pantalla
while True:
rcv = socket.recv()
print rcv
if rcv == "salir": beak
29. Licencia
Este documento está licenciado bajo la GNU Free Documentation
License (GFDL). www.gnu.org
Se autoriza la copia y distribución por cualquier medio, siempre
que se realice bajo esta misma licencia, se mencione al autor
original y se incluya esta nota.