erlang 原生支持rpc

erlang 原生支持rpc(rex)

1.rex 是什么?

rexkernrl进程树第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:callrpc: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的原始支持,但是确带来了另外一个热点问题。

但是反观erlangActor思想,我们会发现,我们在日常的使用中,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

你可能感兴趣的:(erlang 原生支持rpc)