工作者内部的sidekiq perform_in(delay)会忽略延迟

时间:2018-12-16 02:33:34

标签: ruby-on-rails sidekiq aasm

我的应用程序中的用户创建了Transactions,我需要这些交易(以及为在用户在一定时间内没有响应时将交易状态更改为ignored而创建的关联作业)除非用户执行pay操作,否则请自行取消。

在一个示例中,我使用的方法在状态更改为perform_async之后使用approved进行以下调用,然后在未及时响应的情况下取消:

Class Transaction < ApplicationRecord
 #when approved
 def create_worker
  MyWorker.perform_async(self.id)
 end

 #if user responds in time, cancel the jobs and update the record to `paid` etc
 def cancel_worker
  jid = MyWorker.perform_async(self.id)
  MyWorker.cancel! jid
 end
end

根据herehere的建议,我在工作人员内部添加了有关何时取消的其他功能。看起来像这样:

class MyWorker
 include Sidekiq::Worker

 def perform(transaction_id)
  return if paid?
  transaction = Transaction.find transaction_id
  self.class.perform_in(1.minutes, transaction.ignore!)
 end

 def paid?
  Sidekiq.redis { |c| c.exists("paid-#{jid}") }
 end

 def self.cancel! jid
  Sidekiq.redis { |c| c.setex("paid-#{jid}", 86400, 1) }
 end
end

此代码将产生以下终端输出:

2018-12-16T01:40:50.645Z 30530 TID-oxm547nes MyWorker JID-6c97e448fe30998235dee95d INFO: start
Changing transaction 4 approved to ignored (event: ignore!)
2018-12-16T01:40:50.884Z 30530 TID-oxm547nes MyWorker JID-6c97e448fe30998235dee95d INFO: done: 0.239 sec
2018-12-16T01:41:56.122Z 30530 TID-oxm547oag MyWorker JID-b46bb3b002e00f480a04be16 INFO: start
2018-12-16T01:41:56.125Z 30530 TID-oxm547oag MyWorker JID-b46bb3b002e00f480a04be16 INFO: fail: 0.003 sec
2018-12-16T01:41:56.126Z 30530 TID-oxm547oag WARN: {"context":"Job raised exception","job":{"class":"MyWorker","args":[true],"retry":true,"queue":"default","jid":"b46bb3b002e00f480a04be16","created_at":1544924450.884224,"enqueued_at":1544924516.107598,"error_message":"Couldn't find Transaction with 'id'=true","error_class":"ActiveRecord::RecordNotFound","failed_at":1544924516.125679,"retry_count":0},"jobstr":"{\"class\":\"MyWorker\",\"args\":[true],\"retry\":true,\"queue\":\"default\",\"jid\":\"b46bb3b002e00f480a04be16\",\"created_at\":1544924450.884224,\"enqueued_at\":1544924516.107598}"}

因此,这将创建两个作业-一个作业的别名为6c97e448fe30998235dee95d,然后立即将Transaction设置为ignored,然后另一个作业的别名为b46bb3b002e00f480a04be16,该作业会在早期通过返回工作者的perform函数(因为它没有使用与第一个工作相同的jid)。

我可以推测为什么这无法达到我的预期目的的一个原因是,对MyWorker.cancel!的调用无法得到我想要取消的工作人员的通知,而无需先创建一个db迁移持有说吉德。

创建数据库迁移以包含工作人员的jid是确保操作之间可访问jid的首选方法吗? id=true如何到达那里?如上面的错误所述:Couldn't find Transaction with 'id'=true"

1 个答案:

答案 0 :(得分:2)

好,让我们一步一步走。

  1. 此代码:

    self.class.perform_in(1.minute, transaction.ignore!)
    

    传递ignore!方法的返回值(在本例中为true)作为作业的参数,这会导致异常。

    您应确保传递正确的参数:

    self.class.perform_in(1.minute, transaction.tap(&:ignore!).id)
    
  2. 每次调用MyWorker.perform_async(或任何其他执行类方法)时,您都在创建新工作,因此得到相同的jid也就不足为奇了。

    您应该按照建议将初始jid存储在交易表中,然后在付款时取回它以将其取消。否则,作业ID将丢失。一种替代方法是实际使用相同的Redis存储已付​​款标志,但由交易键代替。 c.exists("paid-#{transaction.id}")

  3. 您的代码不会等待1分钟以忽略该事务,它只是立即忽略该事务并将其自身设置为在1分钟后再次执行。

    您可能想打电话

    jid = MyWorker.perform_in(1.minute, transaction.id)
    

    直接来自create_worker方法。


更新

如果按照我的想象,如果您正在使用某种持久状态机,那么“忽略完成就忽略”而忘记取消作业甚至更容易

class Transaction
  # I'm inventing a DSL here
  include SomeStateMachine

  state :accepted do
    event :ignore, to: :ignored
    event :confirm, to: :confirmed
  end
  state :ignored
  state :confirmed

  def create_worker
    # no need to track it
    MyWorker.perform_in(1.minute, id)
  end
end

class MyWorker
  include Sidekiq::Worker

  def perform(id)
    transaction = Transaction.find(id)
    transaction.ignore! if transaction.can_ignore?
  end
end

您可以让您的工作运行,它会很乐意跳过任何不可忽略的交易。