Erlang:如何将客户端进程/函数转移到服务器?

6
我的情景如下 - 我有一个包含函数foo()的客户端C,该函数执行一些计算。
我想让一个名为S的服务器执行这个函数,但是它并不知道foo()的存在,并将结果发送回客户端。
我正在尝试确定在Erlang中执行此操作的最佳方法。 我正在考虑:
- 热代码更换 - 即“升级” S 中的代码,使其具有函数foo()。 执行并将结果发送回客户端。 - 分布式方式,在所有适当注册的节点上进行操作,以类似于S! C:foo()的方式“发送”函数以处理/节点S
是否还有我没有考虑的其他方法(或语言特性)?
感谢您的帮助!
2个回答

3

如果计算函数是自包含的,即不依赖于客户端C上的任何其他模块或函数,那么您需要做的就是使用fun(功能对象)。可以通过网络发送fun并由远程机器应用,在fun内部,发送方嵌入了他们的地址和获取答案的方法。因此,执行者可能只看到一个fun,他们可能会或可能不会给出参数,但在fun中,发送方已经强制使用一种方法,使答案自动发送回来。 fun是许多任务的抽象,并且可以作为参数移动。在客户端,您可以编写以下代码:
%% 在客户端的某个地方
%% 客户端运行在 node() == 'client@domain.com' 上
-module(client). -compile(export_all). -define(SERVER,{server,'server@domain.com'}).
give_a_server_a_job(Number)-> ?SERVER ! {build_fun(),Number}.
build_fun()-> FunObject = fun(Param)-> Answer = Param * 20/1000, %% 这里进行计算 rpc:call('client@domain.com',client,answer_ready,[Answer]) end, FunObject.
answer_ready(Answer)-> %%% 使用 Answer 做各种有趣的事情.... io:format("\n\t答案在这里:~p~n",[Answer]).

然后服务器有如下代码:

%%% 在服务器的某个地方
%%% 服务器运行在 node() == 'server@domain.com' 上
-module(server). -compile(export_all).
start()-> register(server,spawn(?MODULE,loop,[])). loop()-> receive {Fun,Arg} -> Fun(Arg), %% 服务器执行工作 %% 工作自动发送答案回到客户端 loop(); stop -> exit(normal); _ -> loop() end.
以这种方式,作业执行者不需要知道如何发送回复,任务本身知道它将如何将答案发送回发送任务的人!我在几个项目中使用了这种通过网络发送功能对象的方法,非常酷!!!
#### 编辑 #####
如果您有一个递归问题,您可以使用funs来处理递归。然而,您需要在客户端和/或服务器上至少有一个库函数来协助递归操作。创建一个函数,它应该在客户端和服务器的代码路径中。

另一种选择是动态地从服务器向客户端发送代码,然后使用库:Dynamic Compile erlang 从客户端加载和执行erlang代码。使用动态编译,这里是一个例子:
1> String = "-module(add).\n -export([add/2]). \n add(A,B) -> A + B. \n".
"-module(add).\n -export([add/2]). \n add(A,B) -> A + B. \n"
2> dynamic_compile:load_from_string(String).
{module,add}
3> add:add(2,5).
7
4>

我们看到的是一段模块代码,它被编译并从字符串动态加载。如果允许该库在服务器和客户端上使用,则每个实体都可以将代码作为字符串发送,然后在另一个实体上动态加载和执行。该代码可以在使用后卸载。让我们看一下斐波那契函数以及如何将其发送并在服务器上执行: %% 这是我们要转换为字符串的常规斐波那契代码: -module(fib). -export([fib/1]).
fib(N) when N == 0 -> 0; fib(N) when (N < 3) and (N > 0) -> 1; fib(N) when N > 0 -> fib(N-1) + fib(N-2). %% 转换为字符串格式后,代码如下: StringCode = " -module(fib).\n -export([fib/1]). \nfib(N) when N == 0 -> 0;\n fib(N) when (N < 3) and (N > 0) -> 1;\n fib(N) when N > 0 -> fib(N-1) + fib(N-2). \n". %% 然后客户端会将此字符串发送到服务器,服务器会动态加载并执行代码
send_fib_code(Arg)-> {ServerRegName,ServerNode} ! {string,StringCode,fib,Arg}, ok.
get_answer({fib,of,This,is,That}) -> io:format("Fibonacci (from server) of ~p is: ~p~n",[This,That]). %%% 在服务器上 loop(ServerState)-> receive {string,StringCode,Fib,Arg} when Fib == fib -> try dynamic_compile:load_from_string(StringCode) of {module,AnyMod} -> Answer = AnyMod:fib(Arg), %%% 将答案异步发送回客户端 %%% 因为通道不同,不能让客户端等待 rpc:call('client@domain.com',client,get_answer,[{fib,of,Arg,is,Answer}]) catch _:_ -> error_logger:error_report(["Failed to Dynamic Compile & Load Module from client"]) end, loop(ServerState); _ -> loop(ServerState) end.
那段粗糙的代码可以展示我想要表达的内容。但是,请记得卸载所有无用的动态模块。同时,你可以有一种方法,在加载之前检查服务器是否已经加载了这样的模块。我建议你不要复制粘贴上面的代码。看看它并理解它,然后编写自己的版本来完成任务。成功!

1
@Muzayya-Joshua 谢谢,这个答案非常有帮助。请注意:在客户端代码中,rpc:call后面多了一个逗号。如果我想将递归函数发送到服务器,以斐波那契数列为例,该怎么办?或者,如果我没有自包含的函数,并且想将整个调用堆栈移动到服务器上,该怎么办?感谢您的所有帮助。 - Eitan
2
如果您想要一个递归的fun(),您可能会喜欢Erlang y-combinator:http://bc.tech.coop/blog/070611.html - MatthewToday
@Muzayya-Joshua 关于您的原始代码/答案:如果客户端和服务器从同一个目录运行,传递Fun函数可以正常工作。但是,如果客户端和服务器不在同一个目录中(现实场景),我会得到“在节点'server@...'上进程<0.39.0>中出现错误,退出值为:{undef,[{#Fun<client.0.118839689>,[5]},{server,loop,0}]}” 的错误信息。我的问题是:服务器似乎正在寻找客户端来执行该函数。为什么会发生这种情况? - Eitan
如果出现这个错误,意味着你的函数依赖于另一个模块。该函数不是自包含的,例如考虑这两个函数:fun(X) -> 2 + X endfun(X) -> X + some_module:func(X) end。后者不是自包含的函数,因此无论将其发送到哪里,都必须在代码路径中包含模块some_module,但前者的函数可以在任何地方执行,甚至在远程机器上也可以。 - Muzaaya Joshua

2

如果您执行 S ! C:foo(),它将在客户端计算来自模块 C 的函数 foo/1 并将其结果发送给进程 S。这似乎不是您想要做的事情。您应该执行类似以下的操作:

% In client

call(S, M, F, A) ->
  S ! {do, {M, F, A}, self()},
  receive
    {ok, V} -> V
  end.

% In server

loop() ->
  receive
    {do, {M, F, A}, C} ->
      C ! {ok, apply(M, F, A)},
      loop()
  end.

但在实际应用场景中,你需要做更多的工作,例如标记客户端消息以执行选择性接收(make_ref/0),在服务器中捕获错误并将其发送回客户端,从客户端监控服务器以侦测服务器宕机,添加一些超时等等。看一下 gen_server:call/2rpc:call/4,5 的实现方式,这就是为什么有OTP可以帮助你避免大部分问题的原因。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接