自动重启动态添加的 children 个主管
Automatic restarting of dynamically added children of a supervisor
根据 Erlang/OTP 手册,如果我使用 supervisor:start_child
将 child 添加到主管并且 supervisor 崩溃,child不会自动重启
(http://www.erlang.org/doc/design_principles/sup_princ.html#id73986)
是否有一个简单的解决方法,或者我是否必须以某种方式手动保存动态添加的 children 并自行管理重启? (我的supervisor上面有个supervisor,所以这个是可以的,虽然不是很优雅。)
您的主管和其他主管一样是流程,不同之处在于它们是系统流程(所有 trap_exit
业务)。当它死去时,它所持有的内部状态也随之消失——噗!
这是一件好事
主管死亡的恢复与工人死亡的恢复相同(毕竟主管是另一个主管的工人)。您可能 运行 遇到的情况是您的监督树的 结构 与您的需求不完全一致。如果你需要工作在他们的主管去世后继续存在,那么这些任务比你放置的更接近程序的崩溃核心——这意味着它们应该是更高级别的 children链或(更有可能)您高估了它们对系统的重要性。
我开始担心确保流程持续存在的最常见原因是我赋予它太多责任。每当我发现自己在问像 "how can I make sure a child is restarted by a supervisor if the supervisor crashes" 这样的问题时,我都会停下来几分钟,仔细考虑我为什么要问这个问题——这个 总是 让我发现了一个架构问题(并且修复它总是偶然地使系统其余部分的其他事情变得更加明智)。
现实生活中的例子:
业务服务器中有一个流程模块,原来是"just the client connection"。它发展成为管理与客户端的网络连接、内部 Erlang 值和外部协议值之间的转换,以及表示系统内客户端的存在(存在、审核 activity、授权、聊天等)。由于审计日志记录,我开始想和你一样的事情:如果 sup 死了,我该如何关闭客户已经触及的东西等等?
然后发生了被忽略的明显恼人的事情:从多个设备同时登录成为一项要求。 Multi-device 当有多个相同的 "client" 时,登录很奇怪,依此类推(而不是单个客户端进程使用多个连接进程)。将这些任务分成不同的 进程 (不仅仅是模块)极大地简化了事情并使状态恢复结构更加明显和清晰。
附录
OP 问了 "So why the difference then between static and dynamic children in this regard?" 好问题。为什么 do 我们有静态 child 定义,动态监督命令像 supervisor:start_child/2
and supervisor:delete_child/2
and those weird simple_one_for_one
supervisors?
关键在于您的用例。假设我有一个游戏服务器需要总是有一个可用的大厅,这样玩家就可以登录、聊天、查看军械库网站、在论坛上发布菜鸟问题和anti-developer咆哮,并且通常在与实际游戏仅次要相关的其他方式上浪费时间。我们从不 想要一次崩溃使它们全部崩溃,但也许我们确实希望能够告诉不同的 运行ning 服务在网络上侦听或停止按需接受连接。然而,实际的游戏领域存在于它们自己独立的监督树分支中——如果其中一个出现故障,我们不希望它带走其他所有东西,我们当然也不希望失去整个集群.
那么我们将如何构建它呢?所有基本服务都将直接写入主管树 child 定义中——除非我们手动引起它,否则那里没有动态。每当我们启动系统时,它们就会弹出。因为我们可能有任意多个游戏领域,虽然领域定义在内部结构为主要静态定义的监督树,每个 realm-level 监督者是 child 的 simple_one_for_one 监督者管理所有领域(因此,如果该主管倒下,然后噗!每个人都回到大厅,可能很生气)。可以根据我们的命令、设置文件或数据库数据或这些的组合来启动领域。
不过,延迟 外部网络服务 的启动可能是一件好事。在启动系统时,我们可能有一些重要的启动任务,无论如何,监听器可能必须在集群中的 不同节点 上启动。为了避免直接通过网络连接对系统造成压力,请给我们时间检查系统或 运行 测试,并有机会将系统设置为某种特定模式(基准测试、测试、锦标赛或其他任何模式) ) 我们可能希望延迟启动外部网络服务。也就是说,我们强制系统等待我们发送给它的命令,然后它才能为未洗过的群众打开狂欢节的大门。我们会将命令包装在一些我们可以从 shell 或网络(如 waste_of_youth:tempt_souls(Node, Port, Cert)
)访问的简单调用中,但结果是 supervisor:start_child/2
的序列调用 - 那些是动态的。
那么如果网络服务管理员死了怎么办?连接变得糟糕!他们不会再出现,直到我们告诉他们(不仅是活动连接,还有监听器——它们可能已经独立于活动连接而死,这取决于主管崩溃的原因),因为系统就是这样设计的。但是,如果这是一个问题,我们可以做很多事情来缓解这种情况。我们可以有一个进程,其工作是了解和监控某些特定服务的最后状态,例如外部网络——如果它发生意外变化,它会自行启动调用。但这通常不是您想要的——大多数情况下,当您需要自动重新启动服务时,您希望在启动时将永久服务的静态定义读入系统。
上面我提到了我们可能希望在启动时将服务器置于不同的模式。将 child 定义与其余代码分开很方便,这样当我告诉它 "start in game test mode" 时,它会加载一些不同的主管定义(并且会坚持下去,除非我们搞砸了) .如果我告诉它 "start in production mode" 也许我们加载 child 包含静态定义的永久网络服务处理程序的定义。一套适用于各种场合的主管定义——这样您就可以轻松创建服务配置文件。这些动态主管命令让您可以在服务状态之间手动切换,或者将该切换过程委托给您在代码中某处定义的命令。
根据 Erlang/OTP 手册,如果我使用 supervisor:start_child
将 child 添加到主管并且 supervisor 崩溃,child不会自动重启
(http://www.erlang.org/doc/design_principles/sup_princ.html#id73986)
是否有一个简单的解决方法,或者我是否必须以某种方式手动保存动态添加的 children 并自行管理重启? (我的supervisor上面有个supervisor,所以这个是可以的,虽然不是很优雅。)
您的主管和其他主管一样是流程,不同之处在于它们是系统流程(所有 trap_exit
业务)。当它死去时,它所持有的内部状态也随之消失——噗!
这是一件好事
主管死亡的恢复与工人死亡的恢复相同(毕竟主管是另一个主管的工人)。您可能 运行 遇到的情况是您的监督树的 结构 与您的需求不完全一致。如果你需要工作在他们的主管去世后继续存在,那么这些任务比你放置的更接近程序的崩溃核心——这意味着它们应该是更高级别的 children链或(更有可能)您高估了它们对系统的重要性。
我开始担心确保流程持续存在的最常见原因是我赋予它太多责任。每当我发现自己在问像 "how can I make sure a child is restarted by a supervisor if the supervisor crashes" 这样的问题时,我都会停下来几分钟,仔细考虑我为什么要问这个问题——这个 总是 让我发现了一个架构问题(并且修复它总是偶然地使系统其余部分的其他事情变得更加明智)。
现实生活中的例子:
业务服务器中有一个流程模块,原来是"just the client connection"。它发展成为管理与客户端的网络连接、内部 Erlang 值和外部协议值之间的转换,以及表示系统内客户端的存在(存在、审核 activity、授权、聊天等)。由于审计日志记录,我开始想和你一样的事情:如果 sup 死了,我该如何关闭客户已经触及的东西等等?
然后发生了被忽略的明显恼人的事情:从多个设备同时登录成为一项要求。 Multi-device 当有多个相同的 "client" 时,登录很奇怪,依此类推(而不是单个客户端进程使用多个连接进程)。将这些任务分成不同的 进程 (不仅仅是模块)极大地简化了事情并使状态恢复结构更加明显和清晰。
附录
OP 问了 "So why the difference then between static and dynamic children in this regard?" 好问题。为什么 do 我们有静态 child 定义,动态监督命令像 supervisor:start_child/2
and supervisor:delete_child/2
and those weird simple_one_for_one
supervisors?
关键在于您的用例。假设我有一个游戏服务器需要总是有一个可用的大厅,这样玩家就可以登录、聊天、查看军械库网站、在论坛上发布菜鸟问题和anti-developer咆哮,并且通常在与实际游戏仅次要相关的其他方式上浪费时间。我们从不 想要一次崩溃使它们全部崩溃,但也许我们确实希望能够告诉不同的 运行ning 服务在网络上侦听或停止按需接受连接。然而,实际的游戏领域存在于它们自己独立的监督树分支中——如果其中一个出现故障,我们不希望它带走其他所有东西,我们当然也不希望失去整个集群.
那么我们将如何构建它呢?所有基本服务都将直接写入主管树 child 定义中——除非我们手动引起它,否则那里没有动态。每当我们启动系统时,它们就会弹出。因为我们可能有任意多个游戏领域,虽然领域定义在内部结构为主要静态定义的监督树,每个 realm-level 监督者是 child 的 simple_one_for_one 监督者管理所有领域(因此,如果该主管倒下,然后噗!每个人都回到大厅,可能很生气)。可以根据我们的命令、设置文件或数据库数据或这些的组合来启动领域。
不过,延迟 外部网络服务 的启动可能是一件好事。在启动系统时,我们可能有一些重要的启动任务,无论如何,监听器可能必须在集群中的 不同节点 上启动。为了避免直接通过网络连接对系统造成压力,请给我们时间检查系统或 运行 测试,并有机会将系统设置为某种特定模式(基准测试、测试、锦标赛或其他任何模式) ) 我们可能希望延迟启动外部网络服务。也就是说,我们强制系统等待我们发送给它的命令,然后它才能为未洗过的群众打开狂欢节的大门。我们会将命令包装在一些我们可以从 shell 或网络(如 waste_of_youth:tempt_souls(Node, Port, Cert)
)访问的简单调用中,但结果是 supervisor:start_child/2
的序列调用 - 那些是动态的。
那么如果网络服务管理员死了怎么办?连接变得糟糕!他们不会再出现,直到我们告诉他们(不仅是活动连接,还有监听器——它们可能已经独立于活动连接而死,这取决于主管崩溃的原因),因为系统就是这样设计的。但是,如果这是一个问题,我们可以做很多事情来缓解这种情况。我们可以有一个进程,其工作是了解和监控某些特定服务的最后状态,例如外部网络——如果它发生意外变化,它会自行启动调用。但这通常不是您想要的——大多数情况下,当您需要自动重新启动服务时,您希望在启动时将永久服务的静态定义读入系统。
上面我提到了我们可能希望在启动时将服务器置于不同的模式。将 child 定义与其余代码分开很方便,这样当我告诉它 "start in game test mode" 时,它会加载一些不同的主管定义(并且会坚持下去,除非我们搞砸了) .如果我告诉它 "start in production mode" 也许我们加载 child 包含静态定义的永久网络服务处理程序的定义。一套适用于各种场合的主管定义——这样您就可以轻松创建服务配置文件。这些动态主管命令让您可以在服务状态之间手动切换,或者将该切换过程委托给您在代码中某处定义的命令。