利用Sidekiq的批量功能快速排队作业

32 阅读2分钟

我们经常需要在同一时间排队等候大量相同类型的作业。也许这是一个针对所有一种对象的数据迁移,或者是一个数据处理步骤,如果它被并行化,就会运行得更快。

我们关注的焦点往往是潜在的大队列或通过所有工作所需的时间。但是,我们还必须考虑另一部分:首先要排查所有的工作。

而不是...

...在大量的Active Record对象中循环。

Customer.all.each do |customer|
  DeliverEmailJob.perform_async(customer)
end

...或者更好的是使用find_each ,如果你有超过几百个工作要排队的话。

Customer.find_each do |customer|
  DeliverEmailJob.perform_async(customer)
end

使用...

...直接使用Sidekiq。

Customer.all.in_batches do |relation|
  array_of_args = relation.ids.map { |x| [x] }
  DeliverEmailJob.perform_bulk(array_of_args)
end

或者,如果使用6.3.0之前的Sidekiq版本。

Customer.all.in_batches do |relation|
  array_of_args = relation.ids.map { |x| [x] }
  Sidekiq::Client.push_bulk('class' => DeliverEmailJob, 'args' => array_of_args)
end

为什么?

这样可以最大限度地减少到Redis的往返次数。你不需要为网络上的每个enqueing动作进行单独调用,而只需进行一次。即使在使用像Redis这样为速度而优化的工具时,这也是一个大问题。

这也有一个好处,就是最大限度地减少了内存的使用量。通过pluckingids,或者使用ids 方法,而不是在Active Record模型的数组上循环,你使用的是不太复杂的Ruby对象,从而减少内存。

Sidekiq的作者建议每次批量enqueue的作业数量限制为1000个,这就是perform_bulk 方法的默认值。即便如此,你也节省了999次往返Redis的时间。

为什么不呢?

你可能非常理智地认为,在用户的网络请求中,你永远不会排队大量的作业,那么我们为什么要加快这个速度?你的客户不一定会因为较慢的方法而受到影响。然而,你可能是在命令行或计划任务中排队作业,所以你现在是在浪费你的时间!"。

6.3.0之前的批量API版本有点麻烦,给你留下了把方法搞乱的空间。我建议更新到最新版本,然后使用.perform_bulk 语法。

另外,这个功能需要你直接使用Sidekiq而不是通过Active Job。我之前提出了很多直接使用Sidekiq的理由,主要是提高速度和灵活性。

建议

如果您在生产中使用Sidekiq,您应该购买专业版的许可证。额外的可靠性使作业不会因崩溃而 "丢失",这已经是一个足够的理由,但还有其他额外的功能,包括作业删除(按作业ID和作业种类)和网络仪表板的增强。