Erlang 공부 : Distribunomicon 2부
- 프로그래밍 언어/erlang
- 2024. 1. 7.
참고
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
- 로컬 노드는 lovecraft 노드에게 erlang:node()를 실행해달라고 메세지를 보낸다.
- 이 때, erlang:node()을 처리하는 프로세스는 lovecraft@ferdmbp 노드에서 생성되고 이루어진다. cthulu@ferdmbp는 lovecraft로부터 작업 결과를 기다리지 않는다. 그 대신, 요청에 대한 정보를 담은 RequestID를 Key로 바로 반환한다. (즉, 비동기로 처리된다)
- 만약 작업이 완료될 때까지 기다리고 싶다면 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],[]}
- 이 때는 rpc:multicall()을 이용할 수 있다. erlang:is_alive()는 해당 노드가 살아있으면 true를 응답하는 함수다.
- 응답 결과로 [], []에 값이 반환된다. 위에서는 {[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 ~ 미니언3 노드를 죽인다.
- 다시 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 |