supervisor:start_child/2可以跨节点使用。
您感到困惑的原因只是对执行上下文的混淆(诚然,有时很难保持直截了当)。任何 OTP 生成都涉及三个进程:
请求者的上下文是调用supervisor:start_child/2 的上下文——不是主管本身的上下文。您通常会通过导出一个包装对supervisor:spawn_child/2 的调用的函数来提供主管接口:
do_some_crashable_work(Data) ->
supervisor:start_child(sooper_dooper_sup, [Data]).
这可以从主管模块定义和导出,根据"service manager/supervisor/workers" idiom 在“管理器”类型的进程内部定义,或其他。但是,在所有情况下,除了主管之外的某个进程正在发出此调用。
现在再次仔细查看 supervisor:start_child/2 的 Erlang 文档(here 和 an R19.1 doc mirror,因为有时 erlang.org 出于某种原因遇到困难)。请注意,sup_ref() 类型可以是注册名称、pid()、{global, Name} 或{Name, Node}。当使用pid()、{global, Name} 或{Name, Node} 元组调用时,请求者可以在任何节点上调用任何其他节点上的主管。
不过,主管不会只是随意地开始工作。它有一个child_spec(),它正在关闭,规范告诉主管要调用什么来启动这个新进程。对子模块的第一次调用是在主管的上下文中进行的,并且是一个自定义函数。虽然我们通常将其命名为 start_link/N,但它可以作为启动的一部分做我们想做的任何事情,包括声明要在其上生成的特定节点。所以现在我们得到了这样的结果:
%% Usually defined in the requestor or supervisor module
do_some_crashable_work(SupNode, WorkerNode, Data) ->
supervisor:start_child({sooper_dooper_sup, SupNode}, [WorkerNode, Data]).
具有类似以下内容的子规范:
%% Usually in the supervisor code
SooperWorker = {sooper_worker,
{sooper_worker, start_link, []},
temporary,
brutal_kill,
worker,
[sooper_worker]},
这表示第一个呼叫将是sooper_worker:start_link/2:
%% The exported start_link function in the worker module
%% Called in the context of the supervisor
start_link(Node, Data) ->
Pid = proc_lib:spawn_link(Node, ?MODULE, init, [self(), Data]).
%% The first thing the newly spawned process will execute
%% in its own context, assuming here it is going to be a gen_server.
init(Parent, Data) ->
Debug = sys:debug_options([]),
{ok, State} = initialize_some_state(Data)
gen_server:enter_loop(Parent, Debug, State).
您可能想知道proc_lib 的所有内容是为了什么。事实证明,虽然从多节点系统中的任何位置调用生成以在多节点系统中的任何其他位置启动生成是可能的,但这并不是一种非常有用的方法业务,因此gen_* 行为甚至proc_lib:start_link/N 都没有方法来声明生成新进程的节点。
理想情况下,您需要的是知道如何初始化自己并在运行后加入集群的节点。您的系统提供的任何服务通常最好复制到集群中的其他节点上,然后您只需编写一种选择节点的方法,这样您就可以完全完成启动业务,因为它现在是节点本地的每一个案例。在这种情况下,无论您的普通经理/主管/工作人员代码所做的任何事情都不必更改 - 事情只是发生了,请求者的 PID 恰好在另一个节点上并不重要,即使该 PID 是必须返回哪些结果。
换一种说法,我们真的不想在任意节点上生成工作人员,我们真正想做的是提升到更高的级别并请求另一个节点完成一些工作并不真正关心这是如何发生的。请记住,要基于 {M,F,A} 调用生成特定函数,您正在调用的节点必须能够访问目标模块和函数——如果它已经有代码的副本,为什么它不是调用的副本节点?
希望这个答案能解释的比混淆更多。