用于指定对象的Sidekiq队列

时间:2018-08-31 12:04:53

标签: ruby-on-rails-4 sidekiq worker

我使用此工作程序进行处理

class CreateOrUpdateContactWorker
    include Sidekiq::Worker
    sidekiq_options retry: 2, queue: 'contact_updater', concurrency: 1

    sidekiq_retries_exhausted do |msg|
        Airbrake.notify(error_message: "Contact update failed", session: { msg: msg })
    end

    def perform(user_id, changed_fields, update_address = false)
        ContactUpdater.create_or_update_contact(user_id, changed_fields, update_address: update_address)
    end
end

在用户模型中,我有after_commit回调

def update_mautic_contact
    CreateOrUpdateContactWorker.perform_async(id, previous_changes.keys, ship_address_changed || false)
end

问题是用户同时更新两次,因为到create_or_update_contact需要一些时间。如何限制仅用于指定用户的线程?每个任务将一一执行以指定user_id。

2 个答案:

答案 0 :(得分:0)

我不知道您是否将create_or_update_contact作为基础结构的一部分,但是您所描述的是竞争状况。要解决此问题,您需要互斥/锁定关键路径redis

这里的竞争状况发生在两个异步工作程序/进程之间,因此您不能只使用简单的红宝石互斥锁/锁。您需要使用中央锁存储/保持器的分布式互斥锁。这:https://github.com/kenn/redis-mutex应该为您完成,但是您将需要class CreateOrUpdateContactWorker include Sidekiq::Worker sidekiq_options retry: 2, queue: 'contact_updater', concurrency: 1 sidekiq_retries_exhausted do |msg| Airbrake.notify(error_message: "Contact update failed", session: { msg: msg }) end def perform(user_id, changed_fields, update_address = false) RedisMutex.with_lock("#{user_id}_create_or_update_contact") do ContactUpdater.create_or_update_contact(user_id, changed_fields, update_address: update_address) end end end 数据库。

基本上,您的代码将类似于:

1_create_or_update_contact

因此,如果您同时有两个user_id = 1的用户更新,则第一个获取名为redis的锁/互斥锁的用户将首先执行,并将阻塞另一个调用,直到完成为止,然后第二个调用将开始。

这将解决您的问题:)我认为redis是必要的,有用的和少数的。无需使用{{1}},我几乎无法想到我的任何Rails项目。

答案 1 :(得分:0)

我是通过Redis实现的,但是没有任何宝石。我在执行工作程序之前使用过条件:

def update_mautic_contact
    if Rails.current.get("CreateOrUpdateContactWorkerIsRunning_#{id}")
        Redis.current.set("CreateOrUpdateContactWorkerIsRunning_#{id}", true)
        CreateOrUpdateContactWorker.perform_in(1.minutes, id, changed_fields)
    else
        Redis.current.set("CreateOrUpdateContactWorkerIsRunning_#{id}", true)
        CreateOrUpdateContactWorker.perform_async(id, changed_fields)
    end
end

和内部工作人员:

class CreateOrUpdateContactWorker
    include Sidekiq::Worker
    sidekiq_options retry: 2, queue: 'contact_updater', concurrency: 1

    sidekiq_retries_exhausted do |msg|
        Airbrake.notify(error_message: "Contact update failed", session: { msg: msg })
    end

    def perform(user_id, changed_fields, update_address = false)
        ContactUpdater.create_or_update_contact(user_id, changed_fields, update_address: update_address)
        Redis.current.del("CreateOrUpdateContactWorkerIsRunning_#{user_id}")
    end
end