美文网首页erlang
erlang 原生支持rpc

erlang 原生支持rpc

作者: Alking | 来源:发表于2019-12-10 00:27 被阅读0次

    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.参考

    相关文章

      网友评论

        本文标题:erlang 原生支持rpc

        本文链接:https://www.haomeiwen.com/subject/cfkwgctx.html