Resque: срочные задания, которые выполняются последовательно для каждого пользователя.

Мое приложение создает задания resque, которые должны обрабатываться последовательно для каждого пользователя, и они должны обрабатываться как можно быстрее (максимальная задержка 1 секунда).

Пример: задание1 и задание2 создаются для пользователя1 и задание3 для пользователя2. Resque может обрабатывать job1 и job3 параллельно, но job1 и job2 должны обрабатываться последовательно.

У меня разные мысли о решении:

  • Я мог бы использовать разные очереди (например, очередь_1... очередь_10) и запускать работника для каждой очереди (например, rake resque:work QUEUE=queue_1). Пользователи назначаются очереди/работнику во время выполнения (например, при входе в систему, каждый день и т. д.)
  • Я мог бы использовать динамические «пользовательские очереди» (например, queue_#{user.id}) и попытаться расширить resque, чтобы только 1 рабочий мог обрабатывать очередь за раз (как указано в Resque: один рабочий процесс на очередь)
  • Я мог бы поместить задания в очередь без восстановления и использовать «метазадание для каждого пользователя» с resque-lock (https://github.com/defunkt/resque-lock), который обрабатывает эти задания.

Есть ли у вас опыт реализации одного из этих сценариев на практике? Или есть другие идеи, над которыми стоит подумать? Буду признателен за любой вклад, спасибо!


person lacco    schedule 07.04.2012    source источник


Ответы (2)


Благодаря ответу @Isotope я наконец пришел к решению, которое, кажется, работает (используя resque-retry и блокировки в Redis:

class MyJob
  extend Resque::Plugins::Retry

  # directly enqueue job when lock occurred
  @retry_delay = 0 
  # we don't need the limit because sometimes the lock should be cleared
  @retry_limit = 10000 
  # just catch lock timeouts
  @retry_exceptions = [Redis::Lock::LockTimeout]

  def self.perform(user_id, ...)
    # Lock the job for given user. 
    # If there is already another job for the user in progress, 
    # Redis::Lock::LockTimeout is raised and the job is requeued.
    Redis::Lock.new("my_job.user##{user_id}", 
      :expiration => 1, 
      # We don't want to wait for the lock, just requeue the job as fast as possible
      :timeout => 0.1
    ).lock do
      # do your stuff here ...
    end
  end
end

Я использую здесь Redis::Lock из https://github.com/nateware/redis-objects (он инкапсулирует шаблон из http://redis.io/commands/setex).

person lacco    schedule 07.06.2012

Я делал это раньше.

Лучшее решение для обеспечения последовательного выполнения подобных действий — поставить конец задания1 в очередь задания2. job1 и job2 могут затем идти либо в одну очередь, либо в разные очереди, это не имеет значения для последовательного, это зависит от вас.

Любое другое решение, такое как постановка в очередь заданий 1+2 одновременно, НО задание запуска задания 2 через 0,5 секунды приведет к условиям гонки, поэтому это не рекомендуется.

Имея job1, запускающий job2, также очень легко сделать.

Если вам нужен другой вариант ради этого: Мое последнее предложение состояло бы в том, чтобы объединить оба задания в одно задание и добавить параметр, если вторая часть также должна запускаться.

e.g.

def my_job(id, etc, etc, do_job_two = false)
  ...job_1 stuff...
  if do_job_two
    ...job_2 stuff...
  end
end
person TomDunning    schedule 11.04.2012
comment
Проблема в том, что job1 не должен явно знать о job2, поскольку они не создаются одновременно (они могут создаваться в разных запросах и т. д.). - person lacco; 15.04.2012
comment
В этом случае я бы использовал resque-retry для job2, чтобы проверить, завершено ли job1, и если позже не будет повторно поставлена ​​в очередь - resque-retry можно настроить на использование либо экспоненциальных отсрочек, либо ограниченных попыток по мере необходимости. - person TomDunning; 17.04.2012
comment
Как job2 узнает, что job1 выполняется для конкретного пользователя? - person lacco; 17.04.2012
comment
1 из 2 способов: 1) добавить флаг пользователю, который запускает job1 при запуске/окончании 2) вы можете запросить redis/resque, чтобы узнать, существует ли задание - person TomDunning; 19.04.2012