Concrete Logo
Hamburger button

Resque

  • Blog
  • 11 de Junho de 2012
Share

Resque é uma biblioteca para processamento de jobs (tarefas) de forma assíncrona (em background). É escrito em Ruby, utiliza o Redis para armazenar as tarefas a serem executadas e foi criado pelo pessoal do Github.

Redis é um banco NoSQL do tipo chave-valor que, basicamente, armazena todos os seus dados em memória. Isso o torna ridiculamente rápido.

O Resque pode ser usado em diversos casos onde o processamento não precisa ser síncrono. Alguns exemplos:

    – Processamento e redimensionamento de imagens.
    – Uploads para a Amazon S3.
    – Construção de gráficos.
    – Atualização de índices de busca.
    – Processamento de planilhas.

Ele compreende três partes:

    1) Uma biblioteca Ruby para criar, consultar e processar jobs.
    2) Rake tasks para iniciar e parar os workers (que fazem o processamento dos jobs).
    3) Uma interface web amigável para monitoramento.

 

Usando o Resque com Rails

Vou tomar como exemplo uma aplicação Rails, que é provavelmente o cenário mais comum – o que não significa que você não possa usar Resque com outros frameworks Ruby.

Setup

Como de costume, você precisa adicionar a gem resque ao seu Gemfile e então rodar bundle install. Depois, sugiro criar um arquivo de configuração config/resque.yml com o host e porta do Redis para cada ambiente:

    [sourcecode language=”ruby” light=”true”]
    development: localhost:6379
    test: localhost:6379
    production: localhost:6379
    [/sourcecode]

Para fazer o load do arquivo de configuração, crie um initializer em config/initializers/resque.rb:

    [sourcecode language=”ruby” light=”true”]
    resque_config = YAML.load_file("#{Rails.root}/config/resque.yml")
    Resque.redis = resque_config[Rails.env]
    Resque.redis.namespace = "resque:#{Rails.application.class.parent_name}"
    [/sourcecode]

Veja que na última linha foi definido um namespace, que no caso é o nome da aplicação. Isso é interessante principalmente para o ambiente de desenvolvimento, onde você pode ter mais de uma aplicação usando a mesma instância do Redis. Sem esse namespace configurado, os jobs podem acabar “se misturando” entre as aplicações.

O Resque possui uma interface web onde podemos acompanhar as filas de tarefas e sua execução. Essa interface é uma aplicação Sinatra, ou seja, é Rack-based. Sendo assim, podemos montar essa interface dentro do config/routes.rb da nossa aplicação Rails:

    [sourcecode language=”ruby” light=”true”]
    require ‘resque/server’

    UploadsApp::Application.routes.draw do
    mount Resque::Server => "/internal/resque"
    end
    [/sourcecode]

Quando levantarmos nossa aplicação, a interface do Resque ficará disponível em /internal/resque.

Agora precisamos adicionar o caminho app/jobs ao autoload do Rails. Edite o arquivo config/application.rb e adicione esse caminho na variável config.autoload_paths:

    [sourcecode language=”bash” light=”true”]
    config.autoload_paths += %W(#{config.root}/app/jobs)
    [/sourcecode]

Nossos jobs ficarão nesse diretório e serão carregados automaticamente sempre que referenciados.

Para finalizar, precisamos carregar as Rake tasks do Resque. Crie uma nova task em lib/tasks/resque.rake com o seguinte conteúdo:

    [sourcecode language=”ruby” light=”true”]
    require ‘resque/tasks’
    task "resque:setup" => :environment
    [/sourcecode]

Isso vai carregar as tasks do Resque e garantir que ele seja inicializado após o carregamento do nosso ambiente pelo Rails.

 

Criando Jobs

Um job do Resque é uma simples classe Ruby que responde ao método perform

    [sourcecode language=”ruby” light=”true”]
    class MyJob
    @queue = :low_priority

    def self.perform
    # faz alguma coisa
    end
    end
    [/sourcecode]

Essa classe pode ter uma variável de instância @queue definindo para qual fila esse job será enviado.

Como exemplo, vamos imaginar um job que recebe o caminho para uma planilha qualquer e delega o parseamento dessa planilha para uma classe xpto. Cada linha dessa planilha representa um produto que deve ser inserido no banco. Nosso job ficaria assim:

    [sourcecode language=”ruby” light=”true”]
    # app/jobs/spreadsheet_parser_job.rb
    class SpreadsheetParserJob
    @queue = :spreadsheets

    def self.perform(filename)
    spreadsheet = XptoParser.new(filename)

    spreadsheet.rows do |row|
    Product.create!(:sku => row.sku, :name => row.name, :price => row.price)
    end
    end
    end
    [/sourcecode]

Dentro de um job Resque, o contexto é a sua aplicação. Ou seja, você pode user seus models e bibliotecas livremente.

 

Adicionando Jobs na fila do Resque

Adicionar novos jobs na fila do Resque é simples. Basta passar o job e seus argumentos para o método Resque.enqueue.

Voltando ao nosso exemplo, para adicionar um job na fila após o upload da planilha, você poderia fazer algo assim no seu controller:

    [sourcecode language=”ruby” light=”true”]
    class UploadsController < ApplicationController
    def create
    @upload = Upload.create!(params[:upload])

    Resque.enqueue(SpreadsheetParserJob, @upload.filename)
    end
    end
    [/sourcecode]

Um aspecto importante é que você só pode passar argumentos que possam ser convertidos para o formato JSON, pois é nesse formato que o Resque armazena esses argumentos no Redis.

Se você precisa que o job receba um objeto, use o id do objeto como argumento e então recupere esse objeto novamente no job.

 

Testes

Escrever testes para os jobs do Resque é como escrever testes para qualquer outra parte da aplicação. Uma boa prática é colocar os testes dentro de spec/jobs, pois criamos os jobs em app/jobs.

É importante observar que toda vez que o método Resque.enqueue for chamado, o Resque tentará conectar no Redis mesmo no ambiente de testes. Existem duas formas de resolver isso:

    a) criar um stub do método Resque.enqueue ou
    b) definir Resque.inline = true no seu spec_helper (caso você use Rspec como suite de testes).

Quando definimos Resque.inline como true, o Resque não irá adicionar o job na fila e irá executar o job de forma síncrona.

 

Rodando Workers

Os workers são os responsáveis por processar as filas de jobs. Cada worker é, basicamente, uma nova instância da sua aplicação e processará um job por vez.

Para iniciar um novo worker, basta rodar o seguinte comando:

    [sourcecode language=”bash” light=”true”]
    QUEUE=nome_da_fila bundle exec rake resque:work
    [/sourcecode]

A variável de ambiente QUEUE define qual fila esse worker irá processar. Para iniciar um worker que processa qualquer fila:

    [sourcecode language=”bash” light=”true”]
    QUEUE=* bundle exec rake resque:work
    [/sourcecode]

Por padrão, o intervalo de execuçào de jobs é 5 segundos. Para alterar esse valor, você pode definir a variável de ambiente INTERVAL dessa forma:

    [sourcecode language=”bash” light=”true”]
    INTERVAL=1 QUEUE=* bundle exec rake resque:work
    [/sourcecode]

 

Links

Para entender melhor como o Resque funciona, você pode ler o README e ver os exemplos no repositório do projeto.

Vale também checar alguns projetos como resque-scheduler (permite que você agende jobs no futuro) e resque-status (para acompanhar o andamento de jobs).