使用Sidekiq执行异步作业时使用过时数据

时间:2015-05-14 23:01:12

标签: ruby-on-rails ruby asynchronous sidekiq

为了异步处理事件并创建活动源,我使用的是Sidekiq和Ruby on Rails的全局ID。

这适用于大多数类型的活动,但是其中一些活动需要的数据可能会在作业执行时发生变化。

这是一个完全构成的例子:

class Movie < ActiveRecord::Base
  include Redis::Objects
  value :score # stores an integer in Redis

  has_many :likes

  def popular?
    likes.count > 1000
  end
end

每次更新电影时,Sidekiq工作人员都会执行工作:

class MovieUpdatedWorker
  include Sidekiq::Worker

  def perform(global_id)
    movie = GlobalID::Locator.locate(global_id)
    MovieUpdatedActivity.create(movie: movie, score: movie.score) if movie.popular?
  end
end

现在,想象一下Sidekiq落后了,在它有机会完成工作之前,电影的score在Redis中更新,一些用户不喜欢这部电影,而popular方法现在返回false

Sidekiq最终使用更新的数据。

我正在寻找安排工作的方法,同时确保在执行作业时所需的数据不会改变。一些想法:

1 /手动传递所有必需的数据并相应地调整工作人员:

MovieUpdatedWorker.perform_async(
  movie: self,
  score: score,
  likes_count: likes.count
)

这可能会有效,但需要重新实现/复制所有依赖scorepopular?等数据的方法(想象一下应用程序的数量远远超过这两个/三个可移动部分)。

由于序列化对象在Redis中占用了大量空间,因此无法很好地扩展。

2 /记录传递给工作人员的记录上的一些方法:

MovieUpdatedWorker.perform_async(
  global_id,
  stubs: { score: score, popular?: popular? }
)

class MovieUpdatedWorker
  include Sidekiq::Worker

  def perform(global_id, stubs: {})
    movie = GlobalID::Locator.locate(global_id)

    # inspired by RSpec
    stubs.each do |message, return_value|
      movie.stub(message) { return_value }
    end

    MovieUpdatedActivity.create(movie: movie, score: movie.score) if movie.popular?
  end
end

这不起作用,但您可以想象处理实际记录的便利性,不必重新实现现有方法,以及处理实际数据。

您是否看到其他策略来“冻结”对象数据并异步处理它们?你怎么看待这两个?

1 个答案:

答案 0 :(得分:0)

我不会说数据是陈旧的,因为你实际上已经拥有它的最新版本,只是它不再流行。听起来你真的想要陈旧的版本。

如果您不希望数据发生更改,则需要以某种方式对其进行缓存。您可以这样说,直接将数据传递给作业,或者您可以在数据库中添加某种形式的数据版本,并将引用传递给旧版本。

我认为传递Redis所需的数据是一种合理的方式。您可以仅序列化您真正关心的属性,例如得分。