Ruby on rails 如何防止许多sidekiq作业超过API调用限制
我正在开发一个RubyonRails应用程序。我们有很多sidekiq员工,他们可以同时处理多个工作。每个作业都将调用Shopify API,Shopify设置的调用限制为每秒调用2次。我想同步它,这样在给定的时间内只有两个作业可以调用API。 我现在的做法是:Ruby on rails 如何防止许多sidekiq作业超过API调用限制,ruby-on-rails,redis,synchronization,sidekiq,distributed-system,Ruby On Rails,Redis,Synchronization,Sidekiq,Distributed System,我正在开发一个RubyonRails应用程序。我们有很多sidekiq员工,他们可以同时处理多个工作。每个作业都将调用Shopify API,Shopify设置的调用限制为每秒调用2次。我想同步它,这样在给定的时间内只有两个作业可以调用API。 我现在的做法是: # frozen_string_literal: true class Synchronizer attr_reader :shop_id, :queue_name, :limit, :wait_time def initi
# frozen_string_literal: true
class Synchronizer
attr_reader :shop_id, :queue_name, :limit, :wait_time
def initialize(shop_id:, queue_name:, limit: nil, wait_time: 1)
@shop_id = shop_id
@queue_name = queue_name.to_s
@limit = limit
@wait_time = wait_time
end
# This method should be called for each api call
def synchronize_api_call
raise "a block is required." unless block_given?
get_api_call
time_to_wait = calculate_time_to_wait
sleep(time_to_wait) unless Rails.env.test? || time_to_wait.zero?
yield
ensure
return_api_call
end
def set_api_calls
redis.del(api_calls_list)
redis.rpush(api_calls_list, calls_list)
end
private
def get_api_call
logger.log_message(synchronizer: 'Waiting for api call', color: :yellow)
@api_call_timestamp = redis.brpop(api_calls_list)[1].to_i
logger.log_message(synchronizer: 'Got api call.', color: :yellow)
end
def return_api_call
redis_timestamp = redis.time[0]
redis.rpush(api_calls_list, redis_timestamp)
ensure
redis.ltrim(api_calls_list, 0, limit - 1)
end
def last_call_timestamp
@api_call_timestamp
end
def calculate_time_to_wait
current_time = redis.time[0]
time_passed = current_time - last_call_timestamp.to_i
time_to_wait = wait_time - time_passed
time_to_wait > 0 ? time_to_wait : 0
end
def reset_api_calls
redis.multi do |r|
r.del(api_calls_list)
end
end
def calls_list
redis_timestamp = redis.time[0]
limit.times.map do |i|
redis_timestamp
end
end
def api_calls_list
@api_calls_list ||= "api-calls:shop:#{shop_id}:list"
end
def redis
Thread.current[:redis] ||= Redis.new(db: $redis_db_number)
end
end
我使用它的方式是这样的
synchronizer = Synchronizer.new(shop_id: shop_id, queue_name: 'shopify_queue', limit: 2, wait_time: 1)
# this is called once the process started, i.e. it's not called by the jobs themselves but by the App from where the process is kicked off.
syncrhonizer.set_api_calls # this will populate the api_calls_list with 2 timestamps, those timestamps will be used to know when the last api call has been sent.
然后当一个工作想要打电话的时候
syncrhonizer.synchronize_api_call do
# make the call
end
问题
这样做的问题是,如果某个作业由于某种原因无法返回到api\u调用列表,那么它所执行的api\u调用
,这将使该作业和其他作业永远卡住,或者直到我们注意到并且我们再次调用set\u api\u调用
。这个问题不仅会影响到那个特定的商店,还会影响到其他商店,因为sidekiq的工作人员在使用我们的应用程序的所有商店之间共享。有时我们会注意到,直到有用户打电话给我们,我们才发现它被卡住了好几个小时,而它应该在几分钟内完成
问题
我最近才意识到Redis并不是共享锁定的最佳工具。所以我在问,
还有其他适合这项工作的好工具吗???
如果不是在Ruby世界,我也想向其他人学习。我对技术和工具都感兴趣。因此,每一点都有帮助。您可能希望重新构造代码并创建一个微服务来处理API调用,它将使用本地锁定机制并强制工作人员等待套接字。它还增加了维护微服务的复杂性。但是,如果您赶时间,那么Ent速率限制看起来也很酷。Sidekiq Enterprise有一个速率限制API,正是为了解决这个问题而设计的。