【问题标题】:Is there way to run code before Sidekiq is restarted in the middle of a job?有没有办法在工作过程中重新启动 Sidekiq 之前运行代码?
【发布时间】:2019-02-05 16:27:14
【问题描述】:

我有一个 Sidekiq 作业,每 4 分钟运行一次。

此作业在再次执行代码之前检查当前代码块是否正在执行

process = ProcessTime.where("name = 'ad_queue_process'").first

# Return if job is running
return if process.is_running == true

如果 Sidekiq 在代码块中途重新启动,则更新作业状态的代码永远不会运行

# Done running, update the process times and allow it to be ran again
process.update_attributes(is_running: false, last_execution_time: Time.now)

除非我运行更新语句来设置is_running = false,否则会导致作业永远不会运行

有什么方法可以在 Sidekiq 重启之前执行代码?

【问题讨论】:

  • 如何重启 Sidekiq?它通常会“优雅地”停止/重新启动,这意味着它将在重新启动/停止之前首先完成所有正在运行的作业:reference 但是如果您在开发中使用 CTRL+C,是的,它会立即关闭,但这是在开发中。如果您想确保“更新”只会在没有错误的情况下提交到数据库中(即仅当 Sidekiq 在工作中间的 dev env 中不是 CTRL+C-ed 时),那么您可以将ProcessTime.transaction do ... end 块中的整个工作
  • 旁注:这无论如何都是错误的方法,容易受到竞争条件的影响。应该使用消息队列在作业完成 后确认消息。另一个(低级别)选项是Mutex/ConditionalVariable。所有其他解决方案迟早会导致竞争条件和两个作业同时并发执行。
  • @RickS 哦,我明白了,您在 Heroku 中使用 Sidekiq。以前没有在那里使用过,但我发现了它中途关闭的原因(可能你的工作需要超过 30 秒才能运行?)。从this doc,它说"Keep in mind that Heroku puts a hard limit of 30 seconds on a process restart, the -t 25 tells Sidekiq to give the jobs 25 seconds to finish before starting the "force shutdown" procedure"
  • @RickS 查看this,似乎 Heroku 向进程发送了“SIGTERM”(大概这也适用于 sidekiq 进程),因为它是“SIGTERM”而不是“SIGKILL” (无法挽回的强制关机),那么我想你仍然可以在你的 perform 方法周围拯救它(ps未经测试),但你可以尝试:def perform; # code here...; rescue SignalException => e; ensure; process.update(...); end
  • @RickS 虽然,在 Heroku 页面上进一步阅读:After Heroku sends SIGTERM to your application, it will wait a few seconds and then send SIGKILL to force it to shut down, even if it has not finished cleaning up. In this example, the ensure block does not get called at all, the program simply exits:。因此,如果您的工作“挂起”/需要很长时间才能关闭,那么我的“救援;确保”解决方案仍然不是完全可靠的,但希望它不会花费很长时间,因为无论如何您只是在 ensure 中执行 update堵塞;仍然不是 100% 可靠,即更新时临时数据库超时

标签: ruby-on-rails ruby sidekiq


【解决方案1】:

更新:

  • 感谢@Aaron,并根据我们的讨论(下面的cmets),ensure 块(由分叉的工作线程执行)只能运行几毫秒,然后主线程强制终止这些工作线程,以便主线程对异常堆栈进行一些“清理”,以避免被 Heroku SIGKILL。因此,请确保您的 ensure 代码应该非常快!

TL;DR:

def perform(*args)
  # your code here
ensure
  process.update_attributes(is_running: false, last_execution_time: Time.now)
end
  • 上面的ensure 总是被调用,无论方法“成功”还是引发异常。我测试了这个:看到这个repl code,然后点击“运行”

  • 换句话说,即使信号是SIGTERM(正常关闭信号),它也总是在SignalException 上调用,但ONLY EXCEPTSIGKILL 上(强制无法挽救的关闭) )。您可以通过检查我的repl code 来验证此行为,然后将Process.kill('TERM', Process.pid) 更改为Process.kill('KILL', Process.pid),然后再次单击“运行”(您会注意到puts 不会被调用)

  • 看着Heroku docs,我引用:

    当 Heroku 将关闭一个测功机(重启或新部署等)时,它首先向测功机中的进程发送一个 SIGTERM 信号。

    Heroku 向您的应用程序发送 SIGTERM 后,它将等待几秒钟然后发送 SIGKILL 以强制它关闭,即使它还没有完成清理。在这个例子中,根本没有调用 ensure 块,程序直接退出

    ... 这意味着将调用 ensure 块,因为它是 SIGTERM 而不是 SIGKILL,除非关闭需要很长时间,这可能是由于(某些原因我可以想想ATM):

    • perform 代码(或堆栈中的任何 ruby​​ 代码;甚至是 gem)中的某些东西也拯救了 SignalException,甚至拯救了根 Exception 类,因为 SignalExceptionException 的子类) 但需要很长时间清理(即清理 connections 到 DB 或其他东西,或者挂起你的应用程序的 I/O 东西)

    • 或者,您自己的ensure 块需要很长时间。即在执行process.update_attributes(...) 时,由于某种原因数据库临时挂起/网络延迟或超时,那么update 可能根本不会成功!并且会用完时间,其中从我上面的引述来看,在SIGTERM 之后几秒钟后,应用程序将被 Heroku 发送SIGKILL 强制停止。

...这一切都意味着我的解决方案仍然不完全可靠,但在正常情况下应该可以工作

【讨论】:

  • 我正在为这个问题尝试一些解决方案,而这个特殊的解决方案似乎很有希望。但我发现,一旦 Sidekiq 在工作线程上引发Sidekiq::Shutdown,它不会在exit 处理进程之前等待线程完成。这意味着使用此解决方案,您的 ensure 块完成和进程退出之间存在竞争。我非常希望这个解决方案能够正常工作,但如果没有某种调整,您的 ensure 块必须非常 快才能完成。我现在正在解决这个问题......
  • @Aaron 是的。你是对的,正如我所说,ensure 块应该运行得非常快(并且任何挽救中断(TERM 信号)异常的 ruby​​ 代码也应该运行得非常快)。看看这个docs,似乎在 Sidekiq::Shutdown 被引发之前的总时间是在 8 秒之后(因为 Heroku 在第 10 秒发送 SIGKILL)。所以上面的 ensure 块应该在大约 2 秒内完成,但假设应该在 1 秒内完成,这样堆栈中更高的任何 ruby​​ 代码都只能有机会挽救异常)
  • 但是,所说的 8 秒限制是 Sidekiq 的东西,因为会发生什么(据我了解,如果我错了,请纠正我)。 begin; Process.kill('TERM', Process.pid); rescue Exception; raise_sidekiq_shutdown_after_8_seconds do # then Sidekiq waits for the job to finish; end; rescue Sidekiq::Shutdown => # oh no! the job didn't finish after 8 seconds!, so me (Sidekiq) only has 2 seconds to do internal cleanup (which includes pushing back the job to the retry queue); ensure; # then finally your ensure code gets run here! and thus it should be fast! #end
  • 抱歉上一条消息中的拼写错误:“这样堆栈中更高的任何 ruby​​ 代码都可以 [也] 有机会挽救异常”。另外,P.S.我在上面的评论中所说的未经测试! Sidekiq 进程 ruby​​ 代码我没看过,也没有做过任何调试。但以上所有只是通过我过去的观察和假设。所以,我很想知道事实,也很想知道更多关于你的发现! :)
  • 我一定会让你知道的!到目前为止,我能想到的最好的事情是猴子补丁 sidekiq 在终止进程之前等待线程(至少几秒钟)。但这让我很紧张。感谢您与我一起思考这个问题!
【解决方案2】:

处理 sidekiq 关闭异常

class SomeWorker
  include Sidekiq::Worker

  sidekiq_options queue: :default

  def perform(params)
    ...

  rescue Sidekiq::Shutdown
    SomeWorker.perform_async(params)
  end
end

【讨论】:

  • 嘿,根据本指南rubydoc.info/github/mperham/sidekiq/Sidekiq/Shutdown 它说“这是回滚数据库事务所必需的,否则 Ruby 的 Thread#kill 将提交....不要在您的工作人员中挽救此错误”所以我不要认为这是最好的解决方案
  • 但你可以在处理后重新提升它
猜你喜欢
  • 1970-01-01
  • 2022-01-10
  • 2019-07-24
  • 1970-01-01
  • 1970-01-01
  • 2016-11-08
  • 2021-08-08
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多