我有DynamicSupervisor
以restart: :transient
开始孩子。默认情况下,如果子项异常退出,则由主管重新启动。
但是,按照设计,如果孩子在3次重启后失败,主管本身将退出。来自文档:
https://hexdocs.pm/elixir/Supervisor.html#module-exit-reasons-and-restarts
Notice that supervisor that reached maximum restart intensity will exit
with :shutdown reason. In this case the supervisor will only be restarted
if its child specification was defined with the :restart option set to :permanent
(the default).
由于杀死主管也会杀死其他孩子(正在进行的后台工作),我想避免这种情况。
问题是:达到max_restarts
后,如何杀死失败的子进程,保留主管及其他子女?
使用Elixir 1.6 / OTP 20。
更新:StackOverflow上的I found this answer,基本上表明顶级DynamicSupervisor为每个子项启动DynamicSupervisor;顶级将通过restart: :permanent
或:temporary
启动子级主管。这是一个很好的解决方法,但如果有其他解决方案,我会感兴趣。
答案 0 :(得分:6)
DynamicSupervisor
遵循与常规Supervisor
相同的重启政策,并且它的工作方式有充分理由。我们需要理解为什么会这样,而不是试图解决这个问题。
主管监视其子节点,如果意外故障导致其中任何一个故障,它将以已知的初始状态重新启动它。理解重启限制背后的基本原理的关键在于意外失败的定义。
这里出乎意料并不意味着在将未经测试的代码推送到生产之前你没有考虑过的事情。这种情况只发生在极少数情况下,在正常测试期间难以模拟,这种情况很难再现,并且不会经常发生。
即使在5秒内默认限制为3次重启,也很难捕获此类故障。实际上,这种限制对于实时系统来说过于保守。我认为这对于在开发早期捕获错误非常有用。当一个错误导致一个进程立即关闭或者在启动后很快关闭时,它不会花很长时间才能达到3次重启并导致其主管死亡。此时你应该查找bug并修复它。
假设您测试代码并且仍在观察流程定期死亡,您可能会遇到其他类型的故障 - 预期。我强烈建议阅读Fred Hebert的文章It's About the Guarantees,其中详细介绍了应该使用主管的方式以及他们应该提供的保证。一个非常简短的删节版本:
监督流程在初始化阶段提供保证,而不是尽力而为。这意味着当您为数据库或服务编写客户端时,除非您准备好说它始终可用,否则您不应该在初始化阶段建立连接。无论发生什么事。
如果您确实要求在进程的init()
回调中建立与数据库的连接,那么无法连接则确实意味着该进程无法正常运行并且应该死掉。当它由主管重新启动但它仍然失败时,这确实意味着整个监督树无法正常运行并且应该死亡。这将继续递归,直到到达根管理器并且整个系统发生故障。
现在,Elixir为开箱即用的各种问题提供了很多解决方案。在某种程度上,这是非常好的,但它也经常使这些问题隐形,让新手不知道它们的存在。例如,当无法建立与数据库的连接时,Ecto依赖于底层的db_connection来提供默认的指数退避。 db_connection’s docs中描述了此行为。
回到你的问题,此时应该清楚的是,必须采用另一种方法来进行经常失败的过程,并且这不是造成它的错误。您需要确认其失败是预期的,并在您的代码中明确处理它。
也许,您的流程取决于偶尔可能无法使用的外部服务。在这种情况下,您需要使用断路器。有一篇用Erlang编写的名为fuse的文章,作者在comment on Hacker News中很好地描述了这一点。
Netflix有blog post在其API中展示断路器的使用,每天接收数十亿的请求。这是一个令人难以置信的规模,现在它甚至更大,因为那篇文章是从2011年开始的!
如果那仍然不是您遇到的那种失败,那么,您可能运行了不可靠的不受信任的代码?将其包装在try-rescue块中并将错误作为值返回,而不是依赖于主管为您神奇地处理它们。
我希望这会有所帮助。