在不事先知道N的情况下,在主管中申报N个工人的最佳方式

时间:2012-05-04 19:31:40

标签: erlang otp

我正在开发一个带有1个主管和几个工人的应用程序。这些工作中的每一个都只是打开一个tcp套接字,执行一个listen,然后接受连接,为每个客户端到达时生成一个进程(我不介意监督这些)。

我想在应用程序配置中配置侦听地址,因此我可以根据需要使用尽可能多的地址进行侦听(至少需要1个地址,但可以指定任何数字)。每个地址都是ip(或主机名)和端口地址。到目前为止,没有什么新的。

我的问题是如何申报,启动和监督未知数量的工人。我的解决方案是在管理程序代码中动态生成(在运行时)init / 1函数的结果,如下所示:

-define(
   CHILD(Name, Args),
   {Name, {
       ?MODULE, start_listen, Args
       }, permanent, 5000, worker, [?MODULE]
   }
).

init([]) ->
   {ok, Addresses} = application:get_env(listen),
   Children = lists:map(
       fun(Address) ->
           {X1,X2,X3} = os:timestamp(),
           ChildName = string:join([
               "Listener-",
               integer_to_list(X1),
               integer_to_list(X2),
               integer_to_list(X3)
           ], ""),
           ?CHILD(ChildName, [Address])
       end,
       Addresses
   ),
   {ok, { {one_for_one, 5, 10}, Children }}.

这允许我为每个worker动态生成一个名称,并生成worker定义。所以:

  1. 这个解决方案可以接受吗?在我看来,它并不那么优雅。对于这种用例,是否有任何标准解决方案(或最佳实践等)?

  2. 我知道“simple_one_for_one”策略,它允许动态地将工作人员添加到主管。但它是否也可以用来动态生成工人的名字?是否更好(以任何方式)使用“simple_one_for_one”而不是我自己使用“one_for_one”的解决方案? (再次,特殊情况)。

  3. 提前致谢,对不起这篇长篇文章感到抱歉! :)

2 个答案:

答案 0 :(得分:5)

使用simple_one_for_one

在您的worker的init函数中,您可以在表中注册它们以将名称与其PID相关联,这样您就可以从名称中获取pid。

您可以使用global模块(或gproc!)将名称与pid相关联。当进程终止时,globalgproc会自动删除该名称,因此当主管重新启动子进程时,名称可用。

您可以在主管的参数列表(第2个参数)中传递名称:start_child / 2

使用simple_one_for_one将允许您在管理员初始化后动态添加更多侦听器。

如果您需要此功能,simple_one_for_one是一个很好的解决方案。

如果你坚持使用你的sup函数内部的动态内容解决方案,你可以像这样清理代码,它可能,或者可能不会,看起来更优雅:

-define(
   CHILD(Name, Args),
   {Name, {
       ?MODULE, start_listen, Args
       }, permanent, 5000, worker, [?MODULE]
   }
).

generate_names(Adresses) -> generate_names(Adresses, 1, []).

generate_names([], _, Acc) -> Acc;
generate_names([Addr|Addresses], ID, Acc) -> generate_names(Addresses, ID+1, [{id_name(ID), Addr}|Acc]).

id_name(ID) -> "listener-" ++ integer_to_list(ID).

init([]]) ->
   {ok, Addresses} = application:get_env(listen),
   Children = [?CHILD(Name, Address) || {Name, Address} <- generate_names(Addresses)],
   {ok, { {one_for_one, 5, 10}, Children }}.

或者使用lists:foldl而不是所有的小函数来保持代码简短。

但无论如何我会在init的Args列表中传递地址,而不是在init中调用get_env以保持其纯净。

答案 1 :(得分:0)

从您的代码中,我知道您希望从环境中获取子项的数量。 在rabbitmq开源项目的worker_pool_sup.erl的源代码中,我已经阅读了几乎相似的需求代码,并且代码非常优雅,我认为它对您有所帮助。 3个文件是相关的,worker_pool_sup.erl,worker_pool_worker.erl,work_pool.erl。

以下代码来自worker_pool_sup.erl。

init([WCount]) ->
    {ok, {{one_for_one, 10, 10},
          [{worker_pool, {worker_pool, start_link, []}, transient,
            16#ffffffff, worker, [worker_pool]} |
           [{N, {worker_pool_worker, start_link, [N]}, transient, 16#ffffffff,
             worker, [worker_pool_worker]} || N <- lists:seq(1, WCount)]]}}.

关于如何使用它,以下代码来自worker_pool.erl

get_worker_pid(WId) ->
    [{WId, Pid, _Type, _Modules} | _] =
        lists:dropwhile(fun ({Id, _Pid, _Type, _Modules})
                              when Id =:= WId -> false;
                            (_)               -> true
                        end,
                        supervisor:which_children(worker_pool_sup)),
    Pid.