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
参考emqx的rpc的gen_rpc依赖 priestjim/gen_rpc: A scalable RPC library for Erlang-VM based languages (github.com)
{gen_rpc, {git, "https://github.com/emqx/gen_rpc", {tag, "2.8.1"}}}