Erlang 공부 : Distribunomicon 2부

    참고

     


    10 . The Calls from Beyond

    앞선 장에서 분산 얼랭에서 필요한 기초적인 부분을 모두 공부했다. 아래에서는 알면 좋을만한 도구를 더 공부하려고 한다. 

     


    10.1 얼랭 쉘에서 분산 노드 실행하기

    net_kernel:start([Name, Type, HeartbeatInMilliseconds])
    
    % for example
    
    net_kernel:start([romero, shortnames, 15000]).

    다음 명령어를 이용하면 얼랭 쉘에서 새로운 분산 노드를 실행할 수 있다. 얼랭은 각 분산 노드들이 살아있는 상태를 유지하도록 'HeartBeat'을 보내는데, 기본값은 15초다. 즉, 얼랭은 15초마다 연결 유지를 위해 'tick'이라는 메세지를 보낸다. 

    (romero@ferdmbp)2> net_kernel:set_net_ticktime(5).

    HeartBeat Interval * 4의 시간을 Tick Time이라고 한다. Heartbeat Interval 마다 내가 보낸 'tick'에 대한 메세지의 응답을 받기까지 기다리는 Timeout 시간이다. 만약 이 시간동안 메세지를 받지 못하면, 서로 연결이 끊어진 것으로 본다. 

     


    10.2 global 모듈

    global 모듈은 새로운 프로세스 대체 저장 역할을 한다. 주된 역할은 클러스터의 모든 노드에 데이터를 자동으로 전파 / 복제하고 노드 장애를 처리하고, 노드가 다시 온라인 되었을 때 발생하는 데이터 충돌에 대한 해결 전략을 지원한다. 

    • global:register_name(Name, Pid) : 전역적으로 사용할 이름의 Pid 등록. 
      • 이 때, 등록한 Pid는 딱 한 노드에만 존재함. 이 이름으로 요청을 보내면, 모든 노드들은 메세지 라우팅을 통해서 해당 노드의 Pid에게만 요청을 전달함.
    • global:unregister_name(Name) : 전역적으로 사용할 PID 제거. 
    • global:whereis_name(Name) : 전역적으로 등록된 프로세스 이름으로 PID 검색
    • global:re_register_name(Name, Pid) : 이미 등록된 프로세스 이름이 있는데, 그 프로세스가 죽은 경우에 사용한다. 해당 이름에 새로운 PID를 등록하는 것이고, 기존 프로세스가 죽었다면 성공하고, 아니라면 실패한다. 
    • global:send(Name, Message) : 글로벌 PID로 메세지 전송.

     


    10.2.1 이름 충돌이 발생한 경우

    두 노드가 클러스터를 이루기 전에 동일한 이름으로 글로벌 PID 등록한 경우가 있다고 가정해보자. 이 때, 두 노드가 하나의 클러스터를 이루게 되면 이름 충돌이 발생한다. 이를 위해 얼랭은 기본적으로 다음 3가지를 지원한다

    • fun global:random_exit_name/3 : 프로세스를 랜덤으로 죽임. 기본값임.
    • fun global:random_notify_name/3:  살아남을 프로세스를 무작위로 하나 선택하고, 패배한 프로세스에게 {global_name_conflict_, Name} 메세지를 전송함. 
    • fun global:notify_all_name/3 : 두 PID의 등록을 모두 취소하고, 두 프로세스에게 {global_name_conflict, Name, OtherPid} 메세지를 전송하여 스스로 해결하도록 함. 
    5> Resolve = fun(_Name,Pid1,Pid2) ->
    5>     case process_info(Pid1, message_queue_len) > process_info(Pid2, message_queue_len) of
    5>         true -> Pid1;
    5>         false -> Pid2
    5>     end
    5> end.
    #Fun<erl_eval.18.59269574>
    
    6> global:register_name({zombie, 12}, self(), Resolve).
    yes

    유저가 원하는 Pid 충돌 함수를 만들어서 제공할 수도 있다. 그리고 이 모든 것들은 global:register_name/3을 호출할 때, Fallback Function으로 지원해서 처리한다. 

     

     


    10.3 rpc 모듈

    원격 노드에서 함수를 실행하도록 해서 병렬 프로그래밍을 용이하게 만들어주는 모듈이다. 

     


    10.3.1 동기식 함수 실행 

    % rpc:call(Node, Module, Function, Args)
    (cthulu@ferdmbp)1> rpc:call(lovecraft@ferdmbp, lists, sort, [[a,e,f,t,h,s,a]]).
    [a,a,e,f,h,s,t]

    rpc()를 실행할 때, 모듈이름과 MFA 형식으로 제공하면 함수를 실행하고 결과를 받을 수 있다. 또한 Timeout을 인자로 전달할 수 있고, 동기식으로 동작한다. 

     


    10.3.2 비동기식 함수 실행

    (cthulu@ferdmbp)3> Key = rpc:async_call(lovecraft@ferdmbp, erlang, node, []).
    <0.45.0>
    (cthulu@ferdmbp)4> rpc:yield(Key).
    lovecraft@ferdmbp
    1. 로컬 노드는 lovecraft 노드에게 erlang:node()를 실행해달라고 메세지를 보낸다. 
    2. 이 때, erlang:node()을 처리하는 프로세스는 lovecraft@ferdmbp 노드에서 생성되고 이루어진다. cthulu@ferdmbp는 lovecraft로부터 작업 결과를 기다리지 않는다. 그 대신, 요청에 대한 정보를 담은 RequestID를 Key로 바로 반환한다. (즉, 비동기로 처리된다)
    3. 만약 작업이 완료될 때까지 기다리고 싶다면 rpc:yield(Key)를 호출하면 된다. 원격 노드는 작업이 완료되면 결과를 특별한 얼랭 프로세스(RPC 서비스 프로세스)로 전송하는데, rpc:yield(Key)를 호출하면 RPC 서비스 프로세스에서 RequestID로 결과를 찾아서 반환한다. 
    (cthulu@ferdmbp)11> rpc:nb_yield(Key2).
    timeout
    
    (cthulu@ferdmbp)12> rpc:nb_yield(Key2, 1000).
    timeout

    yield()를 할 때, 특정 시간까지만 결과를 기다리고 포기할 것이라면 rpc:nb_yield()를 이용하면 된다.

     


    10.3.3 비동기식 함수 실행. (결과는 필요없음) 

     rpc:cast(Node, Mod, Fun, Args)

    결과가 필요없을 경우, 위처럼 rpc:cast()를 호출하면 된다. 그런데 rpc:cast() 만으로는 한계가 있는데, 비동기로 메세지를 전송해야 할 노드가 수천개가 되는 경우가 예시가 된다. 이 때, rpc:cast()를 수천 번 호출할 수는 없다. 

    % 현재 연결된 노드 확인
    (cthulu@ferdmbp)14> nodes().
    [lovecraft@ferdmbp, minion1@ferdmbp, minion2@ferdmbp, minion3@ferdmbp]
    
    % 연결된 노드 전체에 erlang:is_alive() 호출 요청.
    (cthulu@ferdmbp)15> rpc:multicall(nodes(), erlang, is_alive, []).
    {[true,true,true,true],[]}
    1. 이 때는 rpc:multicall()을 이용할 수 있다. erlang:is_alive()는 해당 노드가 살아있으면 true를 응답하는 함수다. 
    2. 응답 결과로 [], []에 값이 반환된다. 위에서는 {[true, true, true, true, []}의 값이 반환된다.

    왼쪽 리스트는 응답 결과를 의미하고, 오른쪽 리스트는 응답하지 않은 노드를 의미한다. 

    % minion1 ~ minion3 노드 제거
    (cthulu@ferdmbp)16> rpc:eval_everywhere([minion1@ferdmbp, minion2@ferdmbp, minion3@ferdmbp], init, stop, []).
    abcast
    
    % 다시 호출
    (cthulu@ferdmbp)17> rpc:multicall([lovecraft@ferdmbp, minion1@ferdmbp, minion2@ferdmbp, minion3@ferdmbp], erlang, is_alive, []).
    {[true],[minion1@ferdmbp, minion2@ferdmbp, minion3@ferdmbp]}
    1. 미니언1 ~ 미니언3 노드를 죽인다.
    2. 다시 multicall()을 이용해 erlang:is_alive()를 호출하도록 하면, 좌측 리스트에는 하나의 true만 반환된다. 반면 우측 리스트에는 응답하지 않은 3개의 노드가 생성되는 것을 볼 수 있다.

     


    요약

    • 글로벌 모듈은 이름 충돌 / 노드 다운을 감지하는 속도가 다소 느림. 
    • rpc:call()을 이용하면 원격 노드에 동기식 함수 호출을 요청할 수 있다. 
    • rpc:async_call()을 이용하면 원격 노드에 비동기식으로 함수 실행을 요청할 수 있음. 
      • 이 때, rpc:async_call()의 결과로 RequestID를 반환함.
      • 원격 노드는 요청이 완료되면, RPC 서비스 프로세스라는 특별한 프로세스로 실행 결과를 전송함.
      • rpc:yield()를 호출하면 RPC 서비스 프로세스에서 RequestID에 대한 메세지를 찾아서 반환함. 
    • rpc:cast() / rpc:multicall()을 이용하면 비동기로 많은 원격 노드에 동시에 여러 요청을 전송할 수 있음. 

     

    '프로그래밍 언어 > erlang' 카테고리의 다른 글

    얼랭 가드절의 패턴매칭  (0) 2024.01.21
    erlang 공부 : The Count of Applications  (2) 2024.01.07
    Erlang 공부 : Distribunomicon  (0) 2024.01.02
    Erlang : ETS Table  (0) 2023.12.31
    erlang 공부 : Building OTP Applications  (1) 2023.12.29

    댓글

    Designed by JB FACTORY