快速将多个项目(1000 /秒)添加到sidekiq队列?

时间:2013-12-18 21:25:58

标签: ruby-on-rails redis sidekiq

我意识到sidekiq有一个push_bulk选项,但我目前受到redis延迟的限制,所以通过push_bulk传递多个项目仍然不够快(只有大约50 / s)。

我试图像这样增加redis连接的数量:

redis_conn = proc {
  Redis.new({ :url => Rails.configuration.redis.url })
}

Sidekiq.configure_client do |config|
  Sidekiq.configure_client do |config|
    config.redis = ConnectionPool.new(size: 50, &redis_conn)
  end
  config.client_middleware do |chain|
    chain.add Sidekiq::Status::ClientMiddleware
  end
end

然后触发单独的线程(Thread.new)以实际对各种对象执行perform_async。有趣的是,任何不是第一个线程的线程都不会被抛入sidekiq队列,就像它们被完全忽略一样。

有谁知道更好的方法吗?

编辑:这是我尝试的push_bulk方法实际上更慢:

  user_ids = User.need_scraping.pluck(:id)
  bar = ProgressBar.new(user_ids.count)
  user_ids.in_groups_of(10000, false).each do |user_id_group|
    Sidekiq::Client.push_bulk(
      'args'  => user_id_group.map{ |user_id| [user_id] },
      'class' => ScrapeUser,
      'queue' => 'scrape_user',
      'retry' => true
    )
  end

谢谢!

2 个答案:

答案 0 :(得分:11)

您想要使用push_bulk。您受限于将元素写入redis队列支持sidekiq的延迟/往返时间。

当您真正要删除额外的网络往返时,您正在使用多个线程/连接来克服网络速度缓慢。

假设您正在尝试将带有UserWorker的20k user_id个工作入队:

您可以通过以下方式将单个作业入队:

UserWorker.perform_async(user_id)

...映射到:

Sidekiq::Client.push('class' => UserWorker, 'args' => [user_id] )

因此,20k user_ids的push_bulk版本为:

# This example takes 20k user_ids in an array, chunks them into groups of 1000 ids,
# and batch sends them to redis as a group.

User.need_scraping.select('id').find_in_batches do |user_group|

  sidekiq_items = user_group.map {|user| { 'class' => UserWorker, 'args' => [user.id] } }
  Sidekiq::Client.push_bulk(sidekiq_items)
end

这会将20k redis呼叫转换为20次redis呼叫,平均往返时间为5ms(乐观),即1秒对100秒。您的里程可能会有所不同。

修改 评论者似乎对Sidekiq / Redis客户端批量排队数据的行为感到困惑。

Sidekiq::Client.push_bulk()方法将一系列作业排入队列。它将这些转换为Sidekiq作业有效负载哈希值,然后调用SideKiq::Client.raw_push()将这些有效负载传递给redis。请参阅来源:https://github.com/mperham/sidekiq/blob/master/lib/sidekiq/client.rb#L158

SideKiq::Client.raw_push()获取Sidekiq哈希有效负载列表,将它们转换为JSON,然后执行redis MULTI命令,结合两个redis命令。首先,它将目标队列添加到活动队列列表(redis SADD),然后将所有作业有效负载推送到目标队列redis列表对象(redis LPUSH)。这是一个redis命令,在一个redis原子组中一起执行。

如果这仍然很慢,您可能还有其他问题(网络速度慢,redis服务器过载等)。

答案 1 :(得分:4)

@Winfield的回答是正确的,他对延迟绝对正确。但是,正确的语法实际如下:

User.need_scraping.select('id').find_in_batches do |user_group|
  Sidekiq::Client.push_bulk({ 'class' => UserWorker, 'args' => user_group.map {|user| [user.id] } })
end

也许它在最近的Sidekiq中发生了变化(我太懒了,不能检查),但现在这是正确的语法。