sidekiq邮件程序在保存模型之前对数据库的作业访问

iaqfqrcu  于 2021-09-29  发布在  Java
关注(0)|答案(2)|浏览(274)

可能标题不是不言自明的,情况是:


# user.points: 0

user.update!(points: 1000)

UserMailer.notify(user).deliver_later. # user.points = 0 => Error !!!!
``` `user` 示例进行更新,然后使用 `user` 作为参数,并且在电子邮件中更改不存在:user.points=0而不是1000
但是,有一个 `sleep 1` 就在那之后 `user_update` 发送电子邮件时会更新更改,因此电子邮件作业似乎比将数据更新到数据库要快。

user.points: 0

user.update!(points: 1000)

sleep 1

UserMailer.notify(user).deliver_later. # user.points = 1000 => OK

避免这两种可能的解决方案的最佳方法是什么?
一种解决办法是打电话 `UserMailer.notify` 不是用 `user` 示例,但具有用户值
另一个解决方案,可能是在 `user` 回拨 `after_commit` 那么,有没有另一种方法来解决这个问题,即保持用户示例作为参数,并避免 `after_commit` 回拨?
谢谢
j0pj023g

j0pj023g1#

记住,sidekiq使用redis作为媒介,在单独的进程中运行rails应用程序的副本。当你打电话的时候 deliver_later ,它实际上并没有“通过” user 去做 Postman 的工作。它生成一个线程,该线程在redis中将作业排队,并传递一个 user 属性,包括id。
当邮件程序作业在sidekiq进程中运行时,它会从数据库中加载用户的新副本。如果交易包含您的 update! 在主rails应用程序尚未完成提交的情况下,sidekiq从数据库中获取旧记录。所以,这是一个比赛条件。
( update! 如果没有隐式事务,则已将其自身 Package 起来,因此在您自己的事务中 Package 它是多余的,并且无助于竞争条件,因为嵌套的activerecord事务仅在最外层事务提交时提交。)
在紧要关头,你可以用一些类似黑客的东西来拖延工作 .deliver_later(wait_until: 10.seconds.from_now) ,但最好的办法是将邮件通知放入 after_commit 回叫你的模型。

class User < ApplicationRecord
  after_commit :send_points_mailer

  def send_points_mailer
    return unless previous_changes.includes?(:points)

    UserMailer.notify(self).deliver_later
  end
end

模特的 after_commit 回调保证在提交最终事务后运行,因此,就像从轨道上使用核武器一样,这是唯一可以确定的方法。

smdnsysy

smdnsysy2#

你没有提到,但我猜你在用activerecord?如果是这样,您可能需要确保在安排sidekiq作业之前刷新数据库事务。
https://api.rubyonrails.org/v6.1.4/classes/activerecord/transactions/classmethods.html

相关问题