September 21, 2011
Trabalhando com tarefas em background usando o resque

Fala galera! Vou escrever um pouquinho sobre um recurso muito bacana e ao mesmo tempo muito importante, que é o de trabalhar com tarefas em background.

Cenário

Como exemplo para nosso post, iremos adotar o envio de e-mail em massa, ok? Onde o usuário ao clicar em “disparar e-mails” será disparado cerca de 1000 e-mails.

NOTA: Não irei abordar a aplicação como um todo, irei focar somente no método do envio de e-mail no modelo de mensagem, até porque é o que mais nos interessa.

Problema

Imagine o momento que o usuário clicar em “disparar e-mails”… Dependendo da quantidade disparada, o usuário pode não ter uma experiência muito boa em seu site, pois o mesmo terá de esperar até o final da execução da tarefa. E como todos nós sabemos que alguns usuário são um pouco impacientes(Eu), o que pode acontecer para piorar? Isso mesmo! Ele irá tentar 1000.times { puts “novamente…” } achando que a ação não esta acontecendo.

Solução

Existem outras ferramentas para nos ajudar a resolver este problema, mas neste post irei abordar o resque.

A implementação do resque é bem simples, vamos lá?

Implementação

A primeira coisa a ser feita é a instalação o banco utilizado pelo resque para armazenar tarefas a serem executadas. Então vamos instalar o redis:

#No Mac
brew install redis


#No Ubuntu/Debian
apt-get install redis-server

Após a instalação, vamos instalar a gem do resque, então basta adicionar a gem ao seu arquivo Gemfile e rodar o bundle install.

gem 'resque'
bundle install

Precisamos criar o arquivo lib/tasks/resque.rake:

require 'resque/tasks'
task "resque:setup" => :environment

Vamos criar agora o arquivo de conexão com o redis. Então crie o arquivo config/resque.yml:

development: localhost:6379
test: localhost:6379
staging: redis1.se.github.com:6379
fi: localhost:6379
production: redis1.ae.github.com:6379

ATENÇÃO: Não se esqueça de mudar a configuração no ambiente de produção.

Agora iremos criar o nosso initializer em config/initializers/resque.rb:

rails_root = ENV['RAILS_ROOT'] || File.dirname(__FILE__) + '/../..'
rails_env = ENV['RAILS_ENV'] || 'development'

resque_config = YAML.load_file(rails_root + '/config/resque.yml')
Resque.redis = resque_config[rails_env]
Resque.inline = ENV['RAILS_ENV'] == "test"

Pronto! Vamos ao nosso código agora!

Vamos imaginar que temos um modelo message.rb desta forma:

NOTA: Não irei abordar a configuração do envio do e-mail em si, estou supondo que seu envio de e-mail já esteja funcionando.

class Message < ActiveRecord::Base
  after_create :send_message

  def send_message
    MessageMailer.deliver_message(self)
  end
end

Vamos alterar nossa classe para:

class Message < ActiveRecord::Base
  after_create :async_send_message

  def async_send_message
    Resque.enqueue(AsyncSendMessage, self.id)
  end

  def send_message
    MessageMailer.deliver_message(self)
  end
end

Repare que o método Resque.enqueue(AsyncSendMessage, self.id) irá enfileirar o envio do e-mail, passando como parâmetro a classe do nosso Worker(que iremos criar no próximo passo) e o id da nossa mensagem(que acabou de ser salva).

Vamos ao nosso worker agora! Crie um arquivo em app/workers/async_send_message.rb:

class AsyncSendMessage
  @queue = :async_send_message
  def self.perform(message_id)
    message = Message.find(message_id)
    message.send_message
  end  	
end

Agora tudo faz sentido! Quando nossa mensagem for criada, será chamado o método async_send_message, que irá enfileirar o e-mail na lista de tarefas a serem executadas. Quando chegar a vez do e-mail ser enviado, nosso worker entra em ação, invocando o método send_message.

NOTA: Poderíamos também ao invés de criar o método async_send_message, ter enfileirado o e-mail diretamente no método send_message e ter chamado o método deliver_message diramente do nosso worker.

Testando

Antes de testar, precisamos iniciar o servidor do redis(caso ainda não esteja iniciado). No Ubuntu/Debian ele será automaticamente iniciado na instalação, no caso do Mac, basta executarmos:

sudo redis-server /usr/local/etc/redis.conf

Dessa forma, quase tudo irá funcionar, o usuário irá achar que esta enviando o e-mail naquele momento, mas, o e-mail será enfileirado para o envio. Para executar o que esta na lista de tarefas, temos que deixar rodando:

QUEUE=* rake environment resque:work

Veja que usei *, então todos os workers irão rodar(caso você tenha mais de um), porém se você quiser especificar um somente, basta executar o comando dessa forma:

QUEUE=async_send_message rake environment resque:work

NOTA: Veja, o async_send_message está especificado em @queue dentro de  nosso worker AsyncSendMessage.

É isso ai pessoal, espero que tenha ajudado!

Abraços!


  1. diegoalvareznogueira posted this
Blog comments powered by Disqus