erlang 原生支持rpc(rex)
1.rex
是什么?
rex
是 kernrl
进程树第2个启动的进程
主要提供rpc
服务,系统的所有rpc
远程调用,都会经过rex
2.rex
创建
% kernel.erl
Rpc = #{id => rex,
start => {rpc, start_link, []},
restart => permanent,
shutdown => 2000,
type => worker,
modules => [rpc]},
...
3.rex
是否是热点?
所有的rpc
请求都要经过rex
进程,很容易形成热点,
而且是以节点(node)
为单位的堵塞,换句话说,如果N个Node
同时向同一个Node
发起rpc
请求,那么所有的消息都在同一个rex
进程堆积。
是否有替代方案?有,请参照https://github.com/discordapp/gen_rpc.git
4.rpc:call
与 rpc:block_call
的区别
# rpc:call
do_call(Node, Request, infinity) ->
rpc_check(catch gen_server:call({?NAME,Node}, Request, infinity));
do_call(Node, Request, Timeout) ->
Tag = make_ref(),
{Receiver,Mref} =
erlang:spawn_monitor(
fun() ->
%% Middleman process. Should be unsensitive to regular
%% exit signals.
process_flag(trap_exit, true),
Result = gen_server:call({?NAME,Node}, Request, Timeout),
exit({self(),Tag,Result})
end),
% 这里不直接使用 gen_server:call 是因为gen_server:call可能让调用线程崩溃(exit)
% 但是换句话来说,可以使用catch语句捕获异常,防止错误
% 这样子还可以节约一个调度
receive
{'DOWN',Mref,_,_,{Receiver,Tag,Result}} ->
rpc_check(Result);
{'DOWN',Mref,_,_,Reason} ->
%% The middleman code failed. Or someone did
%% exit(_, kill) on the middleman process => Reason==killed
rpc_check_t({'EXIT',Reason})
end.
handle_call({call, Mod, Fun, Args, Gleader}, To, S) ->
handle_call_call(Mod, Fun, Args, Gleader, To, S);
...
handle_call_call(Mod, Fun, Args, Gleader, To, S) ->
%% Spawn not to block the rpc server.
{Caller,_} =
erlang:spawn_monitor(
fun () ->
set_group_leader(Gleader),
Reply =
%% in case some sucker rex'es
%% something that throws
case catch apply(Mod, Fun, Args) of
{'EXIT', _} = Exit ->
{badrpc, Exit};
Result ->
Result
end,
gen_server:reply(To, Reply)
end),
% 在目标Node上新开一个proc来处理rpc请求
{noreply, maps:put(Caller, To, S)}.
% rpc:block_call
handle_call({block_call, Mod, Fun, Args, Gleader}, _To, S) ->
MyGL = group_leader(),
set_group_leader(Gleader),
Reply =
case catch apply(Mod,Fun,Args) of
{'EXIT', _} = Exit ->
{badrpc, Exit};
Other ->
Other
end,
group_leader(MyGL, self()), % restore
% 这个会真正堵塞rex,所以尽量不要使用
{reply, Reply, S};
所以rpc:call
其实算是异步调用,切记不要使用rpc:block_call
,除非遇到特殊情况
5.如何减少rpc
的热点?
- 尽量使用
gen_server:call({PID,Node}, Request, Timeout)
来代替rpc:call(M,F,A)
- 尽量自己建立类似于
rex
的进程,然后使用负载 - 如果使用的是
cast
,请尽量合并请求,减少消息数量
6. 总结
虽然系统提供了rpc
的原始支持,但是确带来了另外一个热点问题。
但是反观erlang
的Actor
思想,我们会发现,我们在日常的使用中,rpc
是一种不符合Actor
思想的做法。
个人认为真正纯粹的Actor
,就是给对面Node
上的proc
发送一个消息,然后等到消息回执就可以了,直接使用gen_server:call
就可以满足,而且不会有任何的热点问题。
7.参考
- https://github.com/erlang/otp/blob/master/lib/kernel/src/rpc.erl
- https://github.com/erlang/otp/blob/master/lib/kernel/src/kernel.erl
- https://github.com/erlang/otp/blob/master/lib/stdlib/src/gen_server.erl