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

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

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

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

user.points: 0

user.update!(points: 1000)

sleep 1

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

  1. 避免这两种可能的解决方案的最佳方法是什么?
  2. 一种解决办法是打电话 `UserMailer.notify` 不是用 `user` 示例,但具有用户值
  3. 另一个解决方案,可能是在 `user` 回拨 `after_commit` 那么,有没有另一种方法来解决这个问题,即保持用户示例作为参数,并避免 `after_commit` 回拨?
  4. 谢谢
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 回叫你的模型。

  1. class User < ApplicationRecord
  2. after_commit :send_points_mailer
  3. def send_points_mailer
  4. return unless previous_changes.includes?(:points)
  5. UserMailer.notify(self).deliver_later
  6. end
  7. end

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

smdnsysy

smdnsysy2#

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

相关问题