我想把我所有的ActiveJob配置成在失败时重试,我想通过ActiveJob retry_on方法 来实现。
所以我打算在我的ApplicationJob类中配置它,以便在任何错误时重试,也许是这样的:
class ApplicationJob < ActiveJob::Base
retry_on StandardError # other args to be discussed
end
为什么要用ActiveJob retry_on来实现?为什么是StandardError?
许多人在重试时使用后端特定的逻辑,特别是使用Sidekiq。这很好!
我喜欢使用ActiveJob功能的想法:
- 我目前使用resque(稍后会有更多关于重试的挑战),但计划在中期的某个时候改用其他东西。也许是sideqkiq,但也许是delayed_job或good_job。 (仅仅使用DB而不需要redis对我来说是有吸引力的,就像开源一样)。我喜欢这样的想法,即当我切换后端,或尝试不同的后端时,不必重做这个设置。
- 总的来说,我喜欢ActiveJob作为可交换的商品化后端的承诺。
- 我喜欢good_job的理念,如果一个功能可以在ActiveJob层面完成,为什么每个后端都要重新发明轮子?这可以帮助保持单个后端更小,维护成本更低。我认为good_job鼓励你使用ActiveJob retries。
注意,dhh从2018年开始就记录在案,他认为为所有StandardError设置retries是一个坏主意。但我真的不明白为什么!他说 "你应该知道为什么要重试,代码应该记录这些知识"--但是这么多ActiveJob后端都提供了 "重试所有作业 "的功能,这在我看来是一个既定的共同需求和最佳实践,为什么你不应该单独用ActiveJob来做呢?
dhh认为ActiveJob重试可能是针对特定的目标重试,而后端重试应该用于通用的普遍重试?老实说,我不认为自己会做很多有针对性的重试,让你所有的工作都是空闲的**(很重要**!ActiveJob的最佳实践!),让它们在任何错误时都重试,在我看来是一种更有效地利用开发者时间的方法,至少对一个相对简单的应用程序来说是足够的。
我有一种情况,重试是至关重要的,那就是当我有一个相当长的运行作业(比如它需要超过60秒的运行时间;我有一些不可避免的!),而运行作业的机器需要重新启动。这可能会打断工作。 如果它只是自动重试--放回队列中,由重启后的或其他作业工作者主机再次运行,那就很方便了 否则,它只是坐在那里失败,永远不会再运行,需要手动操作。自动重试将几乎在无形中处理它。
Resque和Resque Scheduler
Resque默认不支持未来计划的工作。你可以用Resque-scheduler插件添加它们。但是我有一个也许是不理性的愿望来避免这一点--Resque和它的生态系统在不同的时间有不同的维护/放弃,而且我(也许是不理性的)不愿意复杂化我的Resque栈。
而且我需要未来的重试调度吗?对于我最重要的用例,如果我只重试一次,立即重试,wait: 0 ,就完全没有问题。当然,这不会照顾到所有潜在的用例,但这是一个好的开始。
我想即使没有resque支持未来的计划,我也可以摆脱:
retry_on StandardError, wait: 0
唉,这实际上是行不通的,它最终还是会被转换为一个future-schedule的调用,除非你安装了resque-scheduler,否则会被Rails捆绑的resque_adapter拒绝。
当然,Resque可以在语义上处理wait:0,如果代码愿意通过排队一个普通的resque job....。 我不知道这是否是一个好主意但是,对Rails绑定的resque_adapter的这个简单补丁将使它愿意接受 "已安排 "的作业,当需要安排的时间实际上是 "现在 "时,只是正常地安排它们,同时仍然对未来安排的尝试提出。对我来说,它使retry_on.... wait: 0 ,只用普通的resque就可以了。
注意:retry_onattempts 计数包括第一次运行
因此,我想只重试一次,就尝试了这样的方法:
# Will never actually retry
retry_on StandardError, attempts: 1
我的工作实际上从来没有这样重试过!看起来,attempts 计数包括第一次非错误运行,工作将被运行的总次数,包括在任何 "重试 "之前的第一次!因此,尝试1意味着 "永不重试",而且什么也不做。哎呀。如果你真的想只重试 一次,在我的Rails 6.1应用程序中,这就是我所做的:
# will actually retry once
retry_on StandardError, attempts: 2
(我认为这意味着默认情况下,attempts: 5 ,实际上意味着你的工作总共可以运行5次--一次原始时间和4次重试。我想这就是原意吧?)
注意:job_id在重试过程中保持不变,万幸的是
顺便说一句,我检查了一下,至少在Rails 6.1中,ActiveJob#job_id在重试时保持不变。如果作业运行了一次,又重试了两次,那么每次都会有相同的job_id,你会在日志中看到三行Performing ,有相同的job_id。
吁!我想这是正确的做法,所以我们可以很容易地将这些作为日志中相同作业的重试关联起来。如果我们在某处保留job_id,以便回过头来检查它是否成功或失败或其他什么,那么它在重试时就能保持一致。
很高兴这就是ActiveJob正在做的事情。
记录不是很好,但可以自定义
Rails会自动记录重试的情况,看起来像这样的一行:
Retrying TestFailureJob in 0 seconds, due to a RuntimeError.
# logged at `info` level
最终,当它认为它的attempts 已经用尽时,它将会说一些类似的话:
Stopped retrying TestFailureJob due to a RuntimeError, which reoccurred on 2 attempts.
# logged at `error` level
但这并不包括工作编号,这使得它很难与关于这个工作的其他日志行相关联,也很难通过你的日志文件跟踪这个工作的整个过程。
这也与其他默认的ActiveJob日志行不一致,其中包括:
- 文本中的作业ID
- 标签(Rails标签日志系统)中的作业ID和字符串
"[ActiveJob]"。由于Rails代码只在执行/enqueue周围应用这些的方式,重试/丢弃相关的日志行显然最终没有包括在内。 - 异常信息不只是有类时的类。
你可以在非常紧凑的ActiveJob::LogSubscriber类中看到所有内置的ActiveJob日志记录。而且你可以看到重试的日志行与eg perform 有点不一致。
也许这种不一致持续了这么久,部分原因是很少有人真正使用ActiveJob重试,他们都还在使用他们的后端特定功能? 我确实尝试了向Rails提交PR,以获得至少一致的格式(我的PR不做标记),不确定它是否会有进展,我认为盲目的PR到Rails通常不会有进展。
同时,在尝试了一堆不同的东西之后,我想我找到了合理的方法,即使用ActiveSupport::Notifications/LogSubscriber API来定制重试相关事件的日志,而对于其他的事件则不从Rails那里进行处理? 请看我的解决方案。
(感谢BigBinary博客在google上的显示,让我在弄清ActiveJob重试日志的工作方式上有了一个头绪。)
(注:还有这个:github.com/armandmgt/l… 但我不确定它的工作/维护情况如何。它似乎只定制了Activejob的异常报告,而不是重试和其他事件。如果制作一个最新的activejob-lograge,适用于所有的ActiveJob日志,将每个事件表达为键/值,并使用lograge格式化设置来输出,这将是一个有趣的项目。我想我们已经看到了我们如何做到这一点,就像我们在上面做的那样,用一个自定义的日志订阅者来做!)
警告:ApplicationJob配置不会对电子邮件起作用
你可能会认为,既然我们在ApplicationJob 上配置了retry_on 。 所有我们的bg作业现在已经被设置为重试了。
Oops!不是deliver_later emails。
Good_job README解释说,ActiveJob的邮件不从ApplicationMailer降生。(我很好奇这是否有什么好的理由,如果有的话似乎也不错!)
good_job的README提供了一种方法来配置内置的Rails mailer超类以进行重试。
你也可以尝试在该邮件超级类上设置delivery_job ,以使用一个自定义的投递任务(再次感谢BigBinary的指点)......也许是一个子类的默认类,以正常投递邮件,但让你设置一些自定义的选项,如retry_on? 不知道这是否在任何方面更可取。