【问题标题】:BackgroundJob triggered multiple times on save ( Rails 5, Sidekiq)BackgroundJob 在保存时触发多次(Rails 5,Sidekiq)
【发布时间】:2021-03-07 16:38:13
【问题描述】:

我正在通过后台作业处理在Cloundinary 上上传 PDF 文件。我从after_save 回调中将它们排入队列。困境是,对于一次更新,我的后台作业会被多次触发。为了解决这个缺陷,我尝试使用around_perform 实现一种方法,以确保我的工作只会被触发一次。但实际上并没有奏效。我想知道你们中是否有人知道如何处理那些不需要的工作电话

这是我的代码

我的after_save回调

回调被放置在我的模型发票和报价单上。

Class Invoice
 after_save :upload_pdf

 def upload_pdf
   UploadPdfJob.perform_later(self.id,'invoice')

   new_notif_paid = Notification.create(user: self.user,
     category: "PDF",
     content: "Your PDF #{self.reference}
            is available ",
     element: "invoice",
     element_id: self.id)
 end

结束

我的工作UploadPDFJob

def perform(id, type)
   create_pdf_copy(id, type)
end


def create_pdf_copy(id, type)

  wicked = WickedPdf.new

  value = type == 'invoice'? Invoice.find(id) : Quote.find(id)
  template_path = type == 'invoice'? 'invoices/show': 'quotes/show.html.erb'
  file_type = type == 'invoice'? 'facture': 'devis'


  pdf_html = ApplicationController.render(
    locals: {
      current_user: value.user,
    },
    assigns: {
      "#{type}": value,
      format: 'pdf'
    },
    template: template_path,
    layout: 'pdf'
  )

  pdf_file = wicked.pdf_from_string(pdf_html,
    page_size: 'A4',
    orientation: "portrait",
    lowquality: true,
    zoom: 0.9,
    dpi: 75
  )

  tempfile = Tempfile.new("#{file_type}-#{value.id}.pdf")

  File.open(tempfile.path, 'wb') do |file|
    file << pdf_file
  end

  tempfile.close


  unless pdf_file.blank?
    value.photo.attach(io: File.open(tempfile.path), filename: "#{file_type}-#{value.id}.pdf")
  end
end

我的around_perform

在这一部分中,我将我的实例放在一个名为 element 的变量中。

这个想法是,如果UploadPdfJob 作业不止一次排队。 PDF 只会上传一次。第一个作业将uploaded设置为true,然后第二个作业将在检查done后退出

  around_perform do |job, block|
    id = job.arguments.first
    element = job.arguments.last == 'invoice'? Invoice.find(id) : Quote.find(id)
    element.with_lock do
      return if element.uploaded
      if block.call
        element.update(uploaded: true)
      else
        retry_job
      end
  end

另外,由于我不想在更新时触发回调,所以我尝试了这种方式。使用名为 start 的变量,它不依赖于我检索到的实例

    around_perform do |job, block|
      id = job.arguments.first
      element = job.arguments.last == 'invoice'? Invoice.find(id) : Quote.find(id)
      start = false
      element.with_lock do
        return if start == true
        if block.call
          start = true
        else
          retry_job
        end
      end
    end

【问题讨论】:

  • 它可能在不同的线程中排队,因此线程局部变量不够好。它也可能是由发生的多次保存触发的。您可能希望在数据库记录或相关表(如 pdf_generated)上创建一个布尔值,然后检查它以确定它是否应该运行。
  • 我们能看到 after_save 回调吗?
  • @Unixmonkey,这正是我的第一个想法。在使用“保存”变量之前,我进行了迁移并使用了属性“上传”。我正在更新我的帖子,以便您可以清楚地看到它
  • @Schwern,我编辑了我的帖子。你现在可以看到了。
  • @DaviGo 是after_save :upload_pdf 吗?回调在哪个类上?得救的东西在哪里?我们需要推断的越少,就越容易回答这个问题。

标签: ruby-on-rails jobs sidekiq


【解决方案1】:

我不得不卷起袖子,但我终于说了算。我使用binding.pry 进行了调试,找到了问题的根源,并找出并更新了触发我工作的代码的每一部分。

另外,为了进一步防止任何不必要的触发,我添加了一个自定义的保护回调,说明保存的哪些参数应该让 Job 运行。

Class Invoice

      after_save :upload_pdf, if: :should_upload?

      def should_upload?
        attributes = self.saved_changes
        %i[title client_id creation_date].any? {|val| attributes.key? val}
      end
    end

【讨论】:

    猜你喜欢
    • 2021-10-21
    • 2017-12-21
    • 2022-07-15
    • 2017-10-12
    • 2017-04-03
    • 2016-05-09
    • 2012-07-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多