Implementando Amazon SQS con Shoryuken

Sergio Canis
The Cocktail Engineering
3 min readMay 30, 2016

--

Photo by Paul Dufour

Es muy habitual que nuestra App deba interactuar con servicios de terceros. Seguramente estés pensando en APIs de alta disponibilidad como puedan ser Twitter, Facebook, PayPal; en cuyos casos disponen de unos tiempos de respuestas aceptables, pero pensemos en otros servicios que son más inestables, dónde se nos pueden presentar estos casos:

  1. Servicio caído: si no hemos tratado este excepción, nos exponemos a dejar una comunicación abierta con el servidor, a espera del timeout que tengamos programado (si es que hemos configurado algún timeout).
  2. Servicio lento: no es un caso tan problemático como el anterior. Pero sí nos arriesgamos a que un servidor se quedé esperando.

No existe un problema sin solución

Para evitarnos estos problemas no vamos a atacar directamente al servicio, sino que utilizaremos una cola de tareas que se realizarán en segundo plano, a modo de servicio desatendido. Esto tiene dos grandes beneficios:

  1. Dejamos el servidor libre, de cara a atender nuevas peticiones.
  2. Disponemos de un control sobre las tareas que se realizan a ciertos servicios. Es decir, sabemos cuantas tareas se realizan a cierto servicio y cada cuanto tiempo.
  3. Unificamos el interlocutor del servicio. Esto no es un beneficio de este sistema per ser, sino una consecuencia de su implementación.

Caso práctico: Ruby on Rails + Amazon SQS + Shoryuken

Para la cola de prioridades utilizaremos Amazon SQS, que tiene alta disponibilidad, es bastante rápido y sencillo de implementar. Además, el coste no es excesivo. No lo digo yo, lo dice Amazon https://aws.amazon.com/es/sqs/

La solución la construiremos sobre Ruby On Rails, utilizando para ello la gema Shoryuken para adaptarlo, que a parte de disponer de un nombre muy molón, simplifica bastante la implementación.

1. Creación de colas en Amazon SQS

Desde https://aws.amazon.com/es/sqs/ creamos una nueva cola. Podemos crear tantas colas como queramos.

2. Configuración de la App

Debemos crear un fichero shoryuken.yml que colgará de app/config:

aws:
access_key_id: access_key_id
secret_access_key: secret_access_key
region: region
pidfile: pidfile_path
logfile: logfile
concurrency: 25
delay: 1
queues:
— default

Nota: pidfile, logfile son opcionales, pero en caso de errores logfile nos vendrá de perlas, y siempre que despleguemos una App en producción con Shoryuken es bueno saber el pid del proceso, ¿no?

3. Configuración del worker

Creamos un worker, que es el encargado de lanzar las tareas de Shoryuken. El funcionamiento es muy similar a Sidekiq (https://github.com/mperham/sidekiq)

class KenMasters
include Shoryuken::Worker
shoryuken_options queue: tck_queue, auto_delete: true
def perform(sqs_msg, body)
@sqs_msg = sqs_msg
@body = ActiveSupport::JSON.decode body

result = case @body["task"]
when 'check_user'
ExternalService.task_check_user
when 'create_user'
ExternalService.task_create_user
end
puts body
end
end

4. Invocación de las tareas

En la parte de la App que queramos ejecutarlo lanzaremos:

KenMasters.perform_async(task: 'check_user')

Esto lanzará una tarea a la cola de Amazon SQS, con el cuerpo task: ‘check_user’, así el programa seguirá su ejecución sin esperar al servicio de terceros. Una vez lanzado, el Worker debería estar escuchando para ejecutar la tarea

5. Qué empiece la fiesta

Para que el worker esté escuchando siempre, Shoryuken funciona en segundo plano, por lo que hay que lanzar un proceso aparte:

bundle exec shoryuken -R -C config/shoryuken.yml

Bola extra: Comprobar que la tarea ha terminado

Al principio del artículo os vendí que esta implementación era perfecta para la comunicación con servicios de terceros. Pero, ¿cómo sabemos si una tarea ha terminado?

En cualquier caso, siempre debemos guardar el BBDD (o en algún sitio) las peticiones y cabeceras que vayamos haciendo para certificar que la respuesta B es para el usuario B. Aquí va un par de propuestas:

  1. WebSocket: en un escenario ideal, abriríamos un socket de comunicación y en cuanto una tarea terminase, nos enteraríamos. Pero a veces, utilizar esta implementación es matar moscas a cañonazos (o simplemente, algunos navegadores no lo soportan).
  2. Polling: Una vez terminada la tarea, desde el frontal haríamos peticiones a una ruta específica, para comprobar si ha terminado la petición.

--

--