在Heroku上重启后,长时间运行的delayed_job作业保持锁定状态

M. *_*ord 14 ruby ruby-on-rails heroku delayed-job

重新启动Heroku工作程序时(无论是在命令上还是作为部署的结果),Heroku都会SIGTERM向工作进程发送.在这种情况下delayed_job,SIGTERM 信号被捕获,然后工作人员在当前作业(如果有)停止后停止执行.

如果工人需要很长时间才能完成,那么Heroku将会发送SIGKILL.在这种情况下delayed_job,这会在数据库中留下一个锁定的作业,不会被另一个工作人员接收.

我想确保工作最终完成(除非出现错误).鉴于此,最好的方法是什么?

我看到两个选择.但我想获得其他意见:

  1. 修改delayed_job以在收到时停止处理当前作业(并释放锁定)SIGTERM.
  2. 找出(程序化的)方法来检测孤立的锁定作业,然后解锁它们.

有什么想法吗?

Ale*_*eth 28

在SIGTERM上彻底中止工作

现在,一个更好的解决方案内置于delayed_job中.使用此设置通过在初始化程序中添加此项来在TERM信号上引发异常:

Delayed::Worker.raise_signal_exceptions = :term
Run Code Online (Sandbox Code Playgroud)

使用该设置,作业将在heroku发出针对非合作进程的最终KILL信号之前正确清理并退出:

您可能需要在SIGTERM信号上引发异常,Delayed :: Worker.raise_signal_exceptions =:term将导致worker引发SignalException,导致正在运行的作业中止并被解锁,这使得该作业可供其他工作人员使用.此选项的默认值为false.

可能的值raise_signal_exceptions是:

  • false- 不会引发异常(默认)
  • :term - 只会在TERM信号上引发异常,但INT将等待当前作业完成.
  • true - 将在TERM和INT上引发异常

从3.0.5版开始提供.

请参阅:https://github.com/collectiveidea/delayed_job/commit/90579c3047099b6a58595d4025ab0f4b7f0aa67a

  • 这是如何处理Mailer与SMTP服务器通信的问题,已完全发送请求并且服务器已收到它,然后在Ruby关闭连接并包装响应之前引发`SignalException`?好像你会再次运行这份工作.似乎必须特别强调使工作100%原子化. (3认同)
  • 如果没有对两个系统进行重大的重新设计,那么就不可能将这种分布式系统通信"原子化".我把它放在"狗屎发生"的范畴内.有时会发送多封邮件.这个解决方案可能是最好的解决方案. (3认同)

duk*_*ave 12

TLDR:

把它放在你的工作方法的顶部:

begin
  term_now = false
  old_term_handler = trap 'TERM' do
    term_now = true
    old_term_handler.call
  end
Run Code Online (Sandbox Code Playgroud)

确保每十秒至少调用一次:

  if term_now
    puts 'told to terminate'
    return true
  end
Run Code Online (Sandbox Code Playgroud)

在你的方法结束时,把这个:

ensure
  trap 'TERM', old_term_handler
end
Run Code Online (Sandbox Code Playgroud)

说明:

我遇到了同样的问题并且发现了这篇Heroku文章.

这个工作包含一个外循环,所以我按照文章添加了一个trap('TERM')exit.然而,delayed_job 选择那个failed with SystemExit并将任务标记为失败.

随着SIGTERM现在我们被困trap 工人的处理程序不被调用,而是立即重新启动作业,然后得到SIGKILL一个几秒钟后.回到原点.

我尝试了一些替代方案exit:

  • A return true将作业标记为成功(并将其从队列中删除),但如果队列中有另一个作业正在等待,则会遇到同样的问题.

  • 调用exit!将成功退出作业和工作程序,它不允许工作程序从队列中删除作业,因此您仍然有"孤立锁定作业"问题.

我的最终解决方案是我在答案的顶部给出的解决方案,它由三部分组成:

  1. 在我们开始潜在的长作业之前,我们'TERM'通过执行一个新的中断处理程序trap(如Heroku文章中所述),我们使用它来设置term_now = true.

    但是,我们也必须抢old_term_handler在这延迟在职职工代码集(由返回trap),并记住call它.

  2. 我们仍然必须确保我们将控制权返回到Delayed:Job:Worker有足够的时间进行清理和关闭,因此我们应该term_now至少每隔十秒检查一次(return如果是)true.

    您可以return truereturn false根据您是否希望将工作视为成功.

  3. 最后,记住删除处理程序并在完成后安装回处理程序至关重要Delayed:Job:Worker.如果你没有这样做,你将保留对我们添加的那个的悬空引用,如果你在其上添加另一个(例如,当工人再次启动这个工作时),可能会导致内存泄漏.


Ist*_*yer 5

该网站的新手,因此无法评论Dave的帖子,而需要添加一个新答案。

Dave的方法存在的问题是我的任务很长(长达8小时的分钟),并且根本没有重复性。我不能每10秒“确保打电话给”。另外,我尝试了Dave的答案,并且无论我返回什么(真或假),该作业始终从队列中删除。对于如何将工作保持在队列中,我还不清楚。

请参阅此请求请求。我认为这可能对我有用。请随时对此发表评论并支持请求请求。

我目前正在尝试使用陷阱,然后获取退出信号...到目前为止,还没有运气。