使用Sidekiq处理Active Job并遇到ActiveJob::DeserializationError错误

8

我正在尝试使用Sidekiq运行以下工作。

当不排队(perform_now)时,该工作可以正常执行,但在使用Sidekiq调用(perform_later)时失败。

AddEmployeesToRoomJob.perform_now room  ## works fine
AddEmployeesToRoomJob.perform_later room  ## breaks in Sidekiq

错误:

AddEmployeesToRoomJob JID-da24b13f405b1ece1212bbd5 INFO: fail: 0.003     sec
2016-08-20T14:57:16.645Z 19456 TID-owmym5fbk WARN:     {"class":"ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper","wrapped"    :"AddEmployeesToRoomJob","queue":"default","args":    [{"job_class":"AddEmployeesToRoomJob","job_id":"0ba5bd30-e281-49a7-a93f-    6e50445183ac","queue_name":"default","priority":null,"arguments":    [{"_aj_globalid":"gid://dragonfly/Room/1"}],"locale":"en"}],"retry":true,    "jid":"da24b13f405b1ece1212bbd5","created_at":1471704675.739077,"enqueued    _at":1471705036.6406531,"error_message":"Error while trying to     deserialize arguments: Couldn't find Room with     'id'=1","error_class":"ActiveJob::DeserializationError","failed_at":14717    04675.946183,"retry_count":4,"retried_at":1471705036.644416}
2016-08-20T14:57:16.645Z 19456 TID-owmym5fbk WARN:     ActiveJob::DeserializationError: Error while trying to deserialize     arguments: Couldn't find Room with 'id'=1
2016-08-20T14:57:16.645Z 19456 TID-owmym5fbk WARN:     /Users/tamlyn/.rvm/gems/ruby-2.2.3/gems/activerecord-    5.0.0.1/lib/active_record/relation/finder_methods.rb:357:in     `raise_record_not_found_exception!'

我的工作 AddEmployeesToRoomJob类 < ApplicationJob 队列名称:默认队列

  def perform(room)
    employees = Employee.all
    if employees.length > 0
      employees.each do |employee|
        UserRoom.create(user: employee, room: room)
      end
    end
  end
end

我的想法 我不明白为什么在执行方法时,它找不到我传递的房间。好像在将任务压入队列/转换为JSON时,该变量以某种方式丢失了?

Sidekiq文档说

“不幸的是,这意味着如果[房间]记录在作业入队之后删除但在调用执行方法之前,则异常处理会有所不同。”

他们提出了一种解决方法,但我不知道那如何帮助我:

rescue_from ActiveJob::DeserializationError do |exception|
    # handle a deleted user record
end

非常感谢您提供的任何帮助!


3
无法找到 ID 为 12345 的 modelname。 - Mike Perham
4个回答

7

我自己也遇到了这个问题,发现discard_on方法非常有用。大多数情况下,在已删除或从未创建的记录上执行作业是非常不必要的。

示例:

class ApplicationJob < ActiveJob::Base
  discard_on ActiveJob::DeserializationError do |job, error|
    Rails.logger.error("Skipping job because of ActiveJob::DeserializationError (#{error.message})")
  end
end

正是我想要的。谢谢! - Paul Danelli

6

我认为将Room对象传递给Sidekiq工作进程并不是一个好主意。 我通常会传递数据库对象的主键,然后重新查询。 请尝试此方法。

AddEmployeesToRoomJob.perform_later room.id

def perform(room_id)
  room = Room.find(room_id)
  employees = Employee.all
    if employees.length > 0
      employees.each do |employee|
        UserRoom.create(user: employee, room: room)
      end
    end
  end
end

@TamlynR 如果这个答案解决了你的问题,能不能麻烦把它设为答案呢?如果没有,告诉我还需要什么。 - kcdragon
为什么需要这个呢? - Breno
1
这不是必需的,但它可以防止很多问题,比如在对象排队后被删除或修改,而在处理之前。在大多数情况下,您希望对对象的最新版本进行操作。 - kcdragon

4
如果你能确保已提交模型,就可以将其传递给作业。在提交当前事务之前排队作业是一个经典错误。
你可以在这里找到更多信息: https://github.com/mperham/sidekiq/wiki/FAQ#why-am-i-seeing-a-lot-of-cant-find-modelname-with-id12345-errors-with-sidekiq 在我们的项目中,我们编写了一个模型关注点,以便能够添加动态的提交后代码块。
module AfterCommitOnce
  extend ActiveSupport::Concern

  included do
    after_commit :execute_after_commit_handlers
  end

  def after_commit_once(&block)
    @after_commits = @after_commits || []
    @after_commits << block
  end

  private

  def execute_after_commit_handlers
    @after_commits = @after_commits || []
    @after_commits.each do |ac|
      ac.call
    end
    @after_commits = []
  end
end

将这个关注点添加到您的Employee模型中,以便在提交所有更改到数据库后指定要执行的作业:

...
employee.after_commit_once do
   AddEmployeesToRoomJob.perform_later
end
employee.save!

0

不建议将Ruby对象作为参数发送给Sidekiq工作者。您可以将ID作为参数发送,并在perform方法中初始化对象。如果想要发送对象,则可以将Ruby对象转储为其他格式,例如Json /二进制/ yml,如下所示。

object.to_json 

或者

Marshal.dump(object)

在使用 perform 方法内的对象之前,您可以按以下方式将对象反序列化为 Ruby 对象。

JSON.parse(serialized_object)

或者

Marshal.load(serialized_object)

这些是针对您的问题的解决方案,但并非理想的解决方案。


1
谢谢,这真的很有趣。 - Tamlyn R

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接