我使用此工作程序进行处理
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。
答案 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