如果子级达到max_restarts,则阻止DynamicSupervisor关闭

Svi*_*len 4 elixir erlang-otp

我有一个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).
Run Code Online (Sandbox Code Playgroud)

由于杀死主管也会杀死其他孩子(正在进行的后台工作),我想避免这种情况.

问题是:到达后max_restarts,我怎样才能杀死失败的子进程,保留主管及其他孩子?

使用Elixir 1.6/OTP 20.

更新:在StackOverflow上找到了这个答案,它基本上表明顶级DynamicSupervisor为每个孩子启动一个DynamicSupervisor; 顶级将用restart: :permanent或启动儿童监督员:temporary.这是一个很好的解决方法,但如果有另一个解决方案,我会感兴趣.

Ale*_*lik 7

DynamicSupervisor坚持与常规相同的重启政策,Supervisor并且它的工作方式有充分理由.我们需要理解为什么它是这样的,而不是试图解决这种行为.

了解主管的目的

主管监视其子节点,如果意外故障导致其中任何一个故障,它将以已知的初始状态重新启动它.理解重启限制背后的基本原理的关键在于意外故障的定义.

这里出乎意料并不意味着在将未经测试的代码推送到生产之前你没有想过的东西.这种情况只发生在极少数情况下,这些情况在正常测试期间难以模拟,这种情况很难再现,并且不会经常发生.

即使在5秒内默认限制为3次重启,捕获此类故障也很困难.实际上,这种限制对于实时系统来说过于保守.我认为这对于在开发早期捕获错误非常有用.当一个错误导致一个进程立即关闭或者在启动后很快关闭时,它不会花很长时间才能达到3次重启并导致其主管死亡.此时,您应该查找错误并修复它.

一种不同的失败方式

假设您测试代码并且仍在观察流程定期死亡,那么您可能会遇到一种不同类型的故障 - 一种预期的故障.我强烈建议阅读弗雷德赫伯特的文章"关于担保",其中详细介绍了监督者应该使用的方式以及他们应该提供的保证.一个非常简短的删节版本:

监督过程在初始化阶段提供保证,而不是尽力而为.这意味着当您为数据库或服务编写客户端时,您不需要在初始化阶段建立连接,除非您已经准备好说它无论发生什么都将始终可用.

如果你确实要求在进程的init()回调中建立与数据库的连接,那么连接失败就意味着进程无法运行并且应该死掉.当它由主管重新启动但它仍然失败时,这确实意味着整个监督树无法正常运行并且应该死亡.这将以递归方式继续,直到到达根管理程序并且整个系统发生故障.

现在,Elixir为开箱即用的各种问题提供了很多解决方案.在某种程度上,这是非常好的,但它也经常使这些问题隐形,让新手不知道它们的存在.例如,当无法建立与数据库的连接时,Ecto依赖于底层的db_connection来提供默认的指数退避.db_connection的文档中描述了此行为.

那你该怎么办?

回到你的问题,在这一点上应该清楚的是,必须采用另一种方法来处理一个经常失败的过程而且它不是导致它的错误.您需要确认其失败是预期的,并在您的代码中明确处理它.

也许,您的流程取决于偶尔可能无法使用的外部服务.在这种情况下,您需要使用断路器.有一篇用Erlang编写的名为fuse的文章,作者在Hacker News的评论中很好地描述了它.

Netflix有一篇博客文章,展示了在其API中使用断路器,每天接收数十亿的请求.这是一个令人难以置信的规模,现在它甚至更大,因为那篇文章是从2011年开始的!

如果那仍然不是您遇到的那种失败,那么,您可能会运行不可靠的不受信任的代码?将其包装在try-rescue块中并将错误作为值返回,而不是依赖于主管为您神奇地处理它们.

我希望这有帮助.

  • 感谢阿列克谢的详细回复,这是一本有趣的读物,但它没有抓住重点;我不担心进程失败,并且很高兴进程在重新启动后被终止;我的问题特别是关于保持主管和其他儿童进程正常工作的解决方案,而有些可能会崩溃。有关示例,请参阅另一个问题的链接答案。 (2认同)
  • @Svilen如果您要坚持使用主管,请让孩子成为临时孩子,然后让另一个进程监视他们,并计算重新启动的次数。通过显式调用主管的“ start_child()”函数来重新启动。您是否看过https://github.com/pouriya-jahanbakhsh/director作为标准主管的替代方法?它看起来像您要找的东西。 (2认同)