Erlang 공부 : Distribunomicon

    참고

     


    1. Alone in the dark

    단일 서버에서 실행되는 소프트웨어는 그 서버가 다운될 경우 기능하지 못한다. 고가용성을 확보하기 위해 여러 서버에 동일한 어플리케이션을 배포할 수 있다. 그러나 이렇게 확보되는 '고가용성'은 한 대의 서버가 다운되었을 때, 이를 적절히 처리할 수 있어야만 확보할 수 있다.

    • 많은 노드가 서로 통신하는 방법
    • 통신의 Serialize, Deserialize 하는 방법
    • 다중 프로세스의 개념을 많은 노드로 확장하는 방법
    • 네트워크 장애를 모니터링 하는 방법

    얼랭을 이용해 분산 시스템에 필요한 몇 가지 피쳐를 작성할 수 있다. 그러나 여기서 가장 중요한 부분은 '충돌이 발생하면 어떻게 되는가'라는 부분이다. 얼랭에서 완전한 '솔루션'을 제공해주지는 않지만, 시스템에 문제가 발생했을 때 Falut Tolerance를 확보할 수 있는 '도구'를 제공해주고, 우리는 이것을 이용해 고가용성의 분산 시스템을 구축할 수 있다.

     


    2. This is my Boomstick

    • Erlang에서는 VM 1개가 노드 1개다. 필요하다면 컴퓨터 1대에 수십 개의 노드를 띄울 수 있다. 
    • 노드에 이름을 지정하면서 노드를 시작하면, 노드는 erlang 클러스터의 일부에 참여한 각 컴퓨터에서 실행되는 EPMD (Erlang Port Mapper Damoen)이라는 Application에 연결된다. 
    • EPMD는 NameServer 역할을 한다.
      • 노드는 OS에게 IP + Port 정보를 요청해서 받음. 
      • 노드는 IP / Port를 확보한 후, 로컬 호스트에서 실행중인 EPMD에 등록 요청함. 
      • EPMD는 노드의 이름(설정한 이름)과 IP/Port를 함께 저장함. 
      • 만약 새로운 노드가 시작할 때, 그 노드의 이름이 EPMD에 등록된 노드 이름과 동일하다면 에러 발생시킴.
      • https://www.erlang.org/doc/man/epmd.html

    EPMD에 노드가 등록된 시점부터 다른 노드에 연결할 수 있게 된다.

    • 노드끼리 연결되면, 서로 모니터링하기 시작하여 연결이 끊어진 노드를 식별할 수 있음. (죽은 것을 의미하진 않음) 
    • 노드끼리 연결되면, 각 노드가 알고 있던 노드로도 모두 연결됨. (Mesh 형태) 

    얼랭 노드의 연결은 다음 특징을 가진다.

    얼랭 노드 : Mesh 형태

    그림으로 살펴보면 다음과 같다.

    1. 좀비 아포칼립스에 생존자 4명이 있다.
    2. Bill은 Zoey가 살아있는 것을 앎. Rick은 Daryl이 살아있는 것을 앎. 
    3. Bill과 Rick이 만나서 정보를 교환함. Bill은 Daryl이 살아있는 것을 알게 됨. Rick은 Zoey가 살아있는 것을 알게됨. 

    위 상황처럼 노드끼리 알게 되면, 각 노드가 알고 있던 모든 다른 노드들도 자연스럽게 알게 되고, 그물망처럼 구성되기 때문에 'Mesh' 형태라고 이야기한다.

    Mesh형태는 Fault Tolearnce 측면에서는 좋지만, 확장성 측면에서는 나쁘다. 얼랭 클러스터에서 각 노드 간에 수많은 커넥션 / Chatter들을 가지기 때문이다. 누군가 죽거나 추가되면 빠르게 알아채지만, 너무 지방방송이 많기 때문에 확장시에 불리하다. 

     

    2.1 노드 연결 후 → 완전히 독립됨. 

    노드가 연결된 후에 노드는 완전히 독립적으로 동작한다. 그것은 자원 관점에서도 마찬가지다.

    • 각 노드마다 독립적인 ETS Table을 사용함. 
    • 각 노드마다 독립적인 Process Registry를 사용함 (atom에 Pid를 등록한다거나 하는)
    • 연결된 노드 중 하나가 죽어도, 다른 노드는 다운되지 않음. 

    네트워크 관점에서는 다음 동작 특성을 보인다.

    • 얼랭 로컬 프로세스가 다른 서버의 얼랭 프로세스에게 일반 메세지를 보낼 수 있음 (! 연산자 이용)

    이게 가능한 것은 다른 서버에서 동작하는 특정 노드에 등록된 프로세스에 접근할 수 있는 방법을 얼랭에서 제공하기 때문이다. 이 통신은 로컬 프로세스끼리 통신하는 것과 동일하게 동작한다. 예를 들어 curl 같은 명령어를 쓴다거나, 자바에서 Serializable 같은 인터페이스를 구현하지 않아도 된다. 또한 네트워크를 통해 다른 서버의 프로세스에 Link, Monitor를 등록할 수 있게 된다.

    정리하면 다음과 같다. 

    • 각 노드마다 독립적인 ETS Table을 사용함. 
    • 각 노드마다 독립적인 Process Registry를 사용함 (atom에 Pid를 등록한다거나 하는)
    • 연결된 노드 중 하나가 죽어도, 다른 노드는 다운되지 않음. 
    • 얼랭 로컬 프로세스가 다른 서버의 얼랭 프로세스에게 일반 메세지를 보낼 수 있음 (! 연산자 이용)
    • 리모트로 통신은 로컬에서 통신하는 것과 동일함.
    • 리모트 프로세스에 Link + Monitor를 등록할 수 있음. 

     

     

     

    Fallacies of Distributed Computing

    분산 컴퓨팅에서 문제가 될 수 있는 8가지 주요 가정을 생각해냈음.

     

    신뢰할 수 있는 네트워크

    ???

     

    There is no Latency

    네트워크 지연이 발생해서 아주 작은 메세지라도 속도가 느려질 수 있음. 그런데 이 속도가 느려질 것을 고려하지 않고, 빠른 대답이 올 경우를 기대하고 처리할 수 있는데 이것은 값비싼 실수가 될 수 있음.

     

    Erlang의 모델은 이러한 문제를 잘 해결함.

    • 격리된 프로세스
    • 비동기 메세지
    • 타임아웃
    • 프로세스 실패 가능성을 항상 염두에 두고 로컬 어플리케이션을 설정하는 방식

    이런 방식으로 Erlang 모델을 구현하기 때문에 안정적인 분산 시스템을 구축하는데에 적응이 거의 필요하지 않다. 

     

    Bandwidth is Infinite

    • 어떤 이유로 노드 간에 대량의 메세지를 보내야 하는 경우 매우 주의해야 함. 여러 노드에 걸친 얼랭 배포 및 통신 방식은 특히 대용량 메세지에 민감함. 
    • 두 노드가 서로 연결되어있으면 모든 통신이 단일 TCP 연결을 통해 이루어지는 경향이 있음. 일반적으로 두 프로세스 간에 메세지 순서를 유지하려고 하기 때문에 메세지는 연결을 통해 순차적으로 전송됨. (하나의 대용량 메세지가 있으면, 다른 모든 메세지에 대한 채널이 차단될 수 있음).
    • erlang은 노드끼리 서로 살아있는지 체크하기 위해 HeartBeat을 전송함.
      • Heartbeat은 두 노드 간에 일정한 간격으로 전송되는 작은 메세지임.
      • Heartbeat도 일반 메세지와 동일한 채널을 통해 전송됨. 
    • 대용량 메세지가 Heartbeat을 지연시킬 수 있음. 

    이걸 해결 하기 위해서 노드 간에 전송하는 메세지 자체는 가능한한 최소화해야한다. 예를 들어 아이템이 옮겨진 경우에, 플레이어 X의 전체 인벤토리를 반복해서 전송하는대신 'X가 아이템 Y를 찾았습니다!'라는 메세지를 보내는 방식으로 하는 것이 좋다. 

     

    The Network is Secure

    • 내가 받은 메세지가 신뢰할 수 있다고 믿는 것은 매우 위험하다. (누가 패킷을 가로채서 이상한 메세지를 보낼 수도 있음) 
    • 그런데 분산 Erlang에 이것은 가정에 불가함. 

    ???

    Topology Doesn't Change

    • 여러 서버에서 실행되도록 만들어진 분산 어플리케이션을 처음 설계할 때, 서버 수 + 고정된 호스트 네임 + 고정 IP를 이용할 것을 생각할 수 있다.
    • 하드웨어가 고장나거나, 서버를 옮기거나 하면 이런 네트워크 토폴로지는 언제든지 바뀔 수 있다. 따라서 이런 것들을 하드 코딩으로 작성하면 네트워크 변화를 처리할 수 없다.

    얼랭 노드는 모두 이름 + 호스트 이름을 가지고 있고, 이 이름은 계속 변경될 수 있다. 따라서 노드의 이름 + 호스트의 이름도 모두 변경될 것을 고민해야한다. 따라서 이런 것들을 하드코딩하면 안된다. 얼랭은 노드 이름을 잊어버려도, 특정 프로세스를 찾을 수 있는 라이브러리가 제공됨.

    There is Only One Administrator

    코드의 주인은 하나의 관리자만 있는 것이 아니다. 예를 들어 여러 개발자, 여러 팀에서 해당 서버를 관리하거나 의존할 수도 있다. 예를 들어 특정 서버를 업데이트 했을 때, 이 서버에 의존하는 다른 팀의 서버가 문제를 겪을 수도 있다. 이런 것들은 어떠한 언어도 해결할 수 없고 개발자 자체가 개선해야하는 문제다. 

     

     

    Erlang은 연결할 수 없는 노드는 죽은 노드로, 연결 가능한 노드는 살아있는 노드로 간주하는 기본 결정을 내렸습니다. 이는 치명적인 장애에 매우 신속하게 대응하려는 경우에 적합한 비관적 접근 방식으로, 일반적으로 네트워크가 시스템의 하드웨어나 소프트웨어보다 장애가 발생할 가능성이 적다고 가정하며, 원래 Erlang이 사용된 방식을 고려할 때 합리적입니다. 노드가 여전히 살아있다고 가정하는 낙관적 접근 방식은 네트워크가 하드웨어나 소프트웨어보다 장애가 발생할 가능성이 높다고 가정하기 때문에 클러스터가 연결이 끊긴 노드를 다시 통합할 때까지 더 오래 기다려야 하므로 충돌 관련 조치가 지연될 수 있습니다.

    -> 즉, Erlang 노드끼리 통신할 수 없는 문제는 네트워크가 아니라 소프트웨어 / 하드웨어 문제라고 판단한다. 즉, 해당 PC가 꺼져있거나 서버 프로세스가 종료된 경우라고 가정하는 것이다.

     

    여기서 한 가지 의문이 생깁니다. 비관적인 시스템에서 죽은 줄 알았던 노드가 갑자기 다시 살아나서 죽지 않은 것으로 밝혀지면 어떻게 될까요? 데이터, 연결 등 모든 면에서 클러스터로부터 고립된 채 나름대로의 삶을 살았던 죽은 노드가 살아 돌아와 우리를 놀라게 할 수 있습니다. 매우 성가신 일이 일어날 수 있습니다.

    -> 이 경우에 다음과 같은 문제가 발생할 수 있음.

    서로 다른 두 데이터 센터에 2개의 노드가 있는 시스템이 있다고 가정해 보겠습니다. 이 시스템에서 사용자는 자신의 계정에 돈을 가지고 있으며, 각 노드에 전체 금액이 보관되어 있습니다. 그런 다음 각 트랜잭션은 데이터를 다른 모든 노드에 동기화합니다. 모든 노드가 정상적으로 작동하면 사용자는 계정이 비어 더 이상 아무것도 판매할 수 없을 때까지 계속 돈을 쓸 수 있습니다.

    소프트웨어는 잘 작동하지만 어느 순간 노드 중 하나가 다른 노드와 연결이 끊어집니다. 상대방이 살아있는지 죽었는지 알 수 있는 방법이 없습니다. 두 노드 모두 여전히 대중의 요청을 받고 있지만 서로 통신할 수 없는 상태일 수 있습니다.

    일반적으로 취할 수 있는 두 가지 전략은 모든 트랜잭션을 중지하거나 중지하지 않는 것입니다. 첫 번째 전략을 선택하면 제품을 사용할 수 없게 되어 손실을 입을 위험이 있습니다. 두 번째 방법의 위험은 계정에 1000달러가 있는 사용자가 이제 1000달러의 거래를 수락할 수 있는 서버 두 대를 보유하게 되어 총 2000달러를 잃게 된다는 것입니다! 우리가 무엇을 하든 제대로 하지 않으면 돈을 잃을 위험이 있습니다.

    서버 간에 데이터가 손실되지 않고 넷스플릿 중에도 애플리케이션을 계속 사용할 수 있게 하면 이 문제를 완전히 피할 수 있는 방법이 없을까요?

     

    My Other CAp is Theorem.

    앞선 질문에 대한 대답은 '없음'이다. 

    CAP 이론을 읽어봐야함. Consistency, Availability, Partition Tolerance. 

     

    Consistency (일관성)

    쿼리에 응답할 수 있는 노드가 2개든, 1000개든 상관없이 시스템이 주어진 시간에 계좌에 있는 금액을 정확히 동일하게 확인할 수 있는 능력을 의미함. 즉, 모든 노드가 같은 데이터를 가질 수 있도록 해야함. 

    Availability (가용성)

    시스템에 특정 데이터를 요청하면 응답을 받을 수 있따는 것을 의미함. 

     

    Partition Tolerance

    파티션 Tolerance는 일반적으로 시스템의 일부가 더 이상 서로 통신할 수 없는 경우에도, 시스템이 계속 작동하는 것을 의미함. 이것의 핵심은 시스템이 구성 요소 간에 메세지가 손실될 수 있는 상황에서도 작동할 수 있다는 것이다.

    CAP 정리는 이 세 가지 중에서 단 두 가지만 가질 수 있고, 한 가지는 지킬 수 없음을 의미함. 따라서 개발자는 AP, CP 중에 하나만 가능하다는 것이다. 

     

     

     

     


    3. Dead or Dead Alive

    가장 어려운 문제는 노드가 죽거나, 네트워크가 불안정해지는 것과 관련이 있다. 이 경우, 노드가 살았는지 죽었는지를 알 수 있는 좋은 방법이 없기 때문이다. 

    • 하드웨어 오류
    • 어플리케이션 Crash
    • 네트워크 Latency
    • 네트워크 Down

    만약 노드가 응답하지 않는다면 어떤 문제가 있는 것일까? 다음 경우를 고려해볼 수 있지만, 어떤 것도 확정적이지 않다. 하드웨어 오류인 경우, 특정 노드가 완전히 죽었다고 볼 수 있다. 그러나 특정 노드가 네트워크 Down을 경험하고 있다면, 그 노드는 살아있고, 혼자 격리된 채 열심히 자신의 업무를 수행하고 있는 상태가 될 것이다. 

    • 연결되지 않은 노드는 죽은 노드다.
    • 이 결정은 하드웨어 오류가 네트워크 오류보다 더 많이 발생할 것이라고 보는 것임. (네트워크 오류가 많다면, 노드가 살아있을 것이라고 생각하기 때문에) 

    이 상황이 발생하면 얼랭은 비관적인 관점으로 바라본다. 비관적 관점은 노드가 죽었다고 가정하고, 그 즉시 시스템을 복구하려고 한다. 낙관적인 관점으로 '프로세스가 살아있지만 통신 투절'이라고 고려한다면, 연결이 끊긴 노드가 복구될 때까지 오래 기다려야 하므로 조치가 지연될 수 있다. 

     

    3.1 죽은 것으로 처리한 노드가 다시 살아나면? 

    얼랭은 비관적 관점으로 프로세스를 처리하기 때문에 연결되지 않은 노드는 죽은 노드로 본다. 그런데, 사실 그 노드가 네트워크 문제로 응답을 못하는 상태였으며, 네트워크가 회복되서 '살아있던 것'으로 판명되면 어떻게 될까? 가장 큰 문제는 각 노드 간의 '데이터 정합성'이 다를 수 있다. 

    아래 경우를 고려해보자.

    • 2개의 데이터 센터에 2개의 서버가 존재함. (총 2개의 서버) 
    • 각 서버는 사용자 계정의 잔액 정보를 가지고 있음.
    • 거래 발생 시, 트랜잭션이 일어남. 트랜잭션은 각 서버에 사용자 계정 잔액 정보를 동기화함. 

    이런 경우일 때, 데이터 센터 사이에 네트워크 파티셔닝이 발생했지만, 각 데이터 센터가 사용자로부터 요청은 받을 수 있는 상태를 고려하면 어떻게 될까? 이 때 생각해 볼 수 있는 방법은 두 가지 중 하나다.

    트랜잭션 중지

    트랜잭션을 중지하면, 그 기간동안 서비스는 동작하지 않는다. 

     

    트랜잭션 계속 사용

    각 사용자는 최대 2배만큼 잔액을 더 쓸 수 있다. 예를 들어 A 데이터 센터에 먼저 요청을 다 보내서 잔액을 0으로 만든 후, B 데이터 센터에 요청을 보내기 시작해 잔액을 0으로 만들 수 있다. 

     

    이처럼 네트워크 파티셔닝이 발생했을 때, 데이터를 유실하지 않으면서 어플리케이션을 계속 사용할 수 있는 방법은 없을까? 

     


    4. My Other Cap is a Theorem.

    '네트워크 파티셔닝이 발생했을 때, 데이터를 유실하지 않으면서 어플리케이션을 계속 사용할 수 있는 방법은 없을까?"에 대한 질문은 '없다'이다. 모든 서버가 죽지 않은 상채로 네트워크 파티셔닝이 발생했을 때, 모든 서버를 살려둔채로 동시에 데이터 정합성을 맞출 수 있는 방법은 없다. 

    이 아이디어를 CAP Theorem이라고 한다. CAP은 Consistency / Availability / Partition Tolearnce의 약자인데, 이 세 가지를 모두 만족하는 시스템은 존재하지 않고 최대 2가지까지만 만족할 수 있음을 의미한다. 

    • Consistency
      • 1000개의 노드에서 특정 데이터 조회 요청을 했을 때, 반환되는 모든 데이터가 정확히 같은 것을 의미한다. 이런 것들은 '트랜잭션' 같은 것들에 의해서 달성된다. 
    • Availability
      • 가용성은 '시스템에 특정 데이터를 요청하면 응답 받을 수 있다'라는 것을 의미한다. 
    • Partition Tolerance
      • 모든 서버가 정상적으로 살아있는데, 네트워크 문제로 몇몇 서버가 서로 통신할 수 없는 경우에도 시스템이 정상적으로 잘 동작하는 것을 의미한다. 

     

    네트워크 파티셔닝을 차단하는 것은 원천적으로 불가능하다. 따라서 개발자는 시스템을 구축할 때, Consistency / Availability 둘 중에 하나를 포기해야만 한다. 

     

     

    4.1 시나리오를 통해 살펴보기.

    위 그림은 다음을 의미한다.

    1. Bill + Zoey는 한 팀이다.
    2. Rick + Daryl은 한 팀이다.
    3. A 망루는 Bill + Zoey에게 연락할 수 있고, B 망루는 Rick + Daryl에게 연락할 수 있다. 
    4. A 망루와 B 망루는 서로 통신할 수 있다.
    5. 4명은 모두 금요일 새벽에 특정 장소에서 만나기로 했으나 비가 오면서 Bill / Zoey는 분리되었다. 
    6. 비가 많이 오게 되면서 A 망루 - B 망루는 서로 통신할 수 없게 되었다.

    이 때, Bill + Zoey가 분리되었고, 이 때문에 4명이 만날 새로운 약속 장소 + 시간을 정하고 싶은 경우를 가정해보자. 

    Bill + Zoey는 A 망루를 통해서 소통이 가능하지만, A 망루와 B 망루는 소통할 수 없는 상태이기 때문에 A 망루의 데이터 처리 내용이 B 망루에도 정상적으로 전달되리라는 보장이 없다. 이처럼 네트워크 파티셔닝이 발생했을 때, 선택할 수 있는 것을 고려해보면 다음과 같다.

    CP : Availability 포기 

    이 때, 모든 망루는 새로운 만남장소 + 시간을 정하는 것을 거절하는 형식으로 CP를 유지하면서 A를 포기할 수 있다. 이 때, 각 망루 + 사람이 가지고 있는 정보는 동일하다. 그러나 데이터 업데이트가 거절되기 때문에 Availability가 포기된다. 

    AP : Consistency 포기

    이 때, 망루는 요청이 올 때 마다 데이터를 업데이트해주고 공유해준다. 그러나 망루끼리 데이터 공유가 안되기 때문에 각 망루에게 데이터를 전달받은 사람들은 서로 다른 약속장소를 가지게 된다. 

     

    4.2 네트워크 파티셔닝 이후의 문제 해결 방법

    CP는 데이터 정합성이 유지되기 때문에 네트워크 파티셔닝 이후 처리해야 할 일이 없다. 그러나 AP의 경우, 각 노드마다 가진 데이터가 다르기 때문에 해결해야 할 문제가 존재한다. 이 문제들은 일반적으로 아래 같은 전략을 통해서 처리된다. 

    • Last Write Wins : 마지막으로 쓴 값이 이긴다. 타임스탬프가 꺼지거나, 정확히 같은 시간에 일이 발생할 수 있다는 문제가 있음. 
    • winner can be picked randomly : 승자는 랜덤하게 선택될 수 있음. 
    • Vector 시계를 이용한 Last Write Wins or First Write Wins : Wall Clock은 시스템마다 다를 것이기 때문에 Vector 시계를 이용해 시간을 동기화 할 수 있음. 
    • 데이터 충돌 처리를 어플리케이션에게 넘기는 방법. 

    가장 간단한 방법 중 하나로는 쿼럼(Quorum) 시스템을 이용하는 것이다. 시스템마다 다르지만 전체 노드의 15% / 50% / 75%가 동의했을 때 데이터를 수정할 수 있도록 하는 시스템이다.

     


    5. Setting up an Erlang Cluster

     

    • 얼랭은 노드를 찾고 메세지를 전송할 수 있도록 각 얼랭 노드(VM)에 이름을 붙인다. 형식은 Name@Host임.
    • 얼랭의 각 노드는 고유한 이름을 가져야 함.
    • 얼랭의 노드 이름은 두 가지가 존재함.
      • Long Name : aaa.bbb.ccc 같은 형태
      • Short Name : aaa 같은 형태
    • Long Name 노드와 Short Name은 서로 통신할 수 없음. 

     

    5.1 짧은 이름 / 긴 이름 선택 

    # Short Name : aaa 형태
    $ erl -sname short_name@domain
    
    # Long Name : aaa.bbb.ccc 형태
    $ erl -name long_name@some.domain

    VM을 시작할 때, VM 이름을 Short Name / Long Name을 선택할 수 있다. 위 커맨드를 참고하면 된다. 

     

    5.2 실습

    $ erl -sname ketchup
    $ erl -sname fries
    • 2개의 노드를 시작한다. (ketchup / fries 노드)
    (ketchup@ferdmbp)1> net_kernel:connect_node(fries@ferdmbp).
    true
    • ketchup 노드를 fries 노드에 연결한다. 
    # 현재 노드 알려줌
    (ketchup@ferdmbp)2> node().
    ketchup@ferdmbp
    
    # 연결된 노드들 알려줌. 
    (ketchup@ferdmbp)3> nodes().
    [fries@ferdmbp]
    • node() 호출 시, 현재 노드가 무엇인지 알려줌. 
    • nodes() 호출 시, 현재 노드와 연결된 노드들을 알려줌.
    # 쉘 프로세스를 shell atom에 등록 @ ketchup 노드
    (ketchup@ferdmbp)4> register(shell, self()).
    true
    
    # 쉘 프로세스를 shell atom에 등록 @ fries 노드
    (fries@ferdmbp)1> register(shell, self()).
    true

    다른 노드의 프로세스에 메세지를 보내기 위해서는 register()를 이용해 프로세스의 이름을 등록해줘야한다. 

    % 다른 노드에게 요청 보내기
    % 요청 보낼 때는 {ProcessName, NodeName}
    (ketchup@ferdmbp)5> {shell, fries@ferdmbp} ! {hello, from, self()}.
    {hello,from,<0.52.0>}
    
    % 보낸 요청 확인하기
    (fries@ferdmbp)2> receive {hello, from, OtherShell} -> OtherShell ! <<"hey there!">> end.
    <<"hey there!">>
    • {ProcessName, NodeName} 형식으로 ! 연산자를 이용해 메세지를 전송하면, 다른 노드에 있는 Process에게 메세지가 전송된다. 

    위에서 볼 수 있듯이 노드가 네트워크를 타고 다른 노드에게 메세지를 전송했는데, 이 메세지가 전송되는 과정에서 발생해야 할 직렬화 / 역직렬화는 자동으로 처리되는 것을 볼 수 있다. 

     

    5.3 다른 노드 Monitor / Link도 동작함. 

    서로 다른 노드에 있는 프로세스들도 Monitor / Link 기능을 같이 사용할 수 있다. 예를 들어 A 노드와 B 노드의 프로세스가 Link로 묶여있다면, 하나의 프로세스가 죽었을 때 전파된다. 

    % 시스템 프로세스 설정
    (fries@ferdmbp)3> process_flag(trap_exit, true).
    false
    
    % 다른 쉘과 link
    (fries@ferdmbp)4> link(OtherShell).
    true
    
    % 다른 쉘 모니터 설정
    (fries@ferdmbp)5> erlang:monitor(process, OtherShell).
    #Ref<0.0.0.132>
    • 현재 쉘 프로세스를 시스템 프로세스로 설정.
    • OtherShell 프로세스에 대해서 Link, Monitor 각각 설정. 
    (fries@ferdmbp)6> flush().
    Shell got {'DOWN',#Ref<0.0.0.132>,process,<6349.52.0>,noconnection}
    Shell got {'EXIT',<6349.52.0>,noconnection}
    ok

    이 때, OtherShell 프로세스를 강제 종료해보자. 그리고 현재 쉘 프로세스에서 flush()를 호출해보면 두 메세지가 전송되는 것을 확인할 수 있다. 

    • 'DOWN' → Link에 의해서 전송된 메세지
    • 'EXIT' → Monitor에 의해 전송된 메세지 

    그런데 이 때, 신기한 것이 있다. 원래 Pid는 <0.92.0> 같은 형식을 보았는데 <0.0.0.132> / <6349.52.0>이라는 PID가 있다. 분산 얼랭에서는 Pid도 달라진다. 

     

    5.4 PID 형식

    (fries@ferdmbp)7> OtherShell.
    <6349.52.0>

    위에서 볼 수 있듯이 분산 얼랭에서는 로컬에서만 사용하던 얼랭과 비교했을 때, PID가 다르다는 것을 알 수 있다. 

    <생성된 노드, 몇번째 생성된 프로세스인지, 몇번째 생성된 프로세스인지>
    <0, 9, 0>

    엄밀하게 말하면 PID의 각 자리수는 다음과 같다. 여기서 두번째 카운터가 너무 큰 숫자가 되면, 두번째 카운터가 초기화 되면서 세번째 카운터가 증가한다. 

    (fries@ferdmbp)8> term_to_binary(OtherShell).
    <<131,103,100,0,15,107,101,116,99,104,117,112,64,102,101,
    114,100,109,98,112,0,0,0,52,0,0,0,0,3>>
    
    
    <<131,103,100,0,15>> : 특정 포멧을 나타냄. (얼랭에서 인코딩 할 때 사용한)
    <<107,101,116,99,104,117,112,64,102,101,114,100,109,98,112>> : 기존 Node 이름을 알려줌.
    % 검증해보기
    H1 = binary_to_list(<<107,101,116,99,104,117,112,64,102,101,114,100,109,98,112>>).
    > "ketchup@ferdmbp"
    
    <<0,0,0,52>, <0,0,0,0> : 2개의 카운터. 
    <3> : PID가 오래된 노드 / 죽은 노드에서 온 것인지 구분하기 위한 토큰값.

    위의 내용은 pid()를 통해 생성된 PID 값을 의미하고, 실제 PID의 의미는 다음과 같다.

     

    5.5 원격 노드에 프로세스 생성

    (ketchup@ferdmbp)6> spawn(fries@ferdmbp, fun() -> io:format("I'm on ~p~n", [node()]) end).
    I'm on fries@ferdmbp
    <6448.50.0>

    spawn()을 이용하면 원격 노드에서도 프로세스를 생성할 수 있다. 여기서 다음 사실을 볼 수 있다.

    원격 노드에서 프로세스가 되었지만, io:format()의 출력은 로컬 노드에서 이루어졌다. 이것은 원격 노드의 프로세스에서 생성되는 출력도 네트워크를 통해 리디렉션 될 수 있다는 것을 의미한다. 

     

     


    6. Cookie

    얼랭 클러스터는 메시 클러스터 형태로 구축된다는 것을 알 수 있다. 이것은 특정 노드가 클러스터에 접속하면, 클러스터에 있는 모든 노드에 접속하게 되는 것을 의미한다. 

    때때로 클러스터를 분할해서 실행하고 싶을 때가 있는데, 한 노드가 실수로 다른 클러스터에 접속하면 두 클러스터가 합쳐지는 결과가 발생한다. 이런 휴먼 에러를 막기 위한 장치가 존재하는데, 그것이 '쿠키'다. '쿠키'는 노드가 연결될 수 있도록 공통으로 가지고 있어야 할 '고유한 값'이기 때문이다. '클러스터의 잠재적 이름'이라고 표현하는 것도 무난할 것 같다.

     

    6.1 노드에 쿠키 전달하고 간단한 실습

    얼랭에서 노드를 시작할 때, -setcookie 명령어를 이용해 사용할 쿠키를 전달할 수 있다. 

    % 1번 노드 생성
    $ erl -sname salad -setcookie 'myvoiceismypassword'
    ...
    (salad@ferdmbp)1>
    
    % 2번 노드 생성
    $ erl -sname mustard -setcookie 'opensesame'
    ...
    (mustard@ferdmb)1>
    
    % 노드끼리 연결 → 다른 쿠키를 가지고 있어 실패함. 
    (salad@ferdmbp)1> net_kernel:connect_node(mustard@ferdmbp).
    false
    1. salad / mustard 노드를 생성할 때 각각 다른 쿠키를 전달한다. 
    2. salad → mustard 노드로 접속 시도해본다. 결과는 False를 받는다. 

    접속 실패에 대한 정보는 꽤 간단하게 제공된다. 접속 실패 정보는 '접속 대상'이 되려던 노드에서 더 자세히 나타난다.

    % mustard@ferdmbp
    
    =ERROR REPORT==== 10-Dec-2011::13:39:27 ===
    ** Connection attempt from disallowed node salad@ferdmbp **

    mustard 노드에서는 salad 노드로부터 접속 시도가 있었으나 에러로 처리되었다며, 경고 리포트가 생성된다. 

     

    6.2 쉘에서 쿠키 설정하기

    얼랭은 쉘에서 쿠키와 관련된 작업을 할 수 있도록 몇몇 메서드들을 제공해준다. 아래 메서드를 이용해서 해볼 수 있다.

    • erlang:set_cookie/2
    • erlang:get_cookie/0
    (salad@ferdmbp)2> erlang:get_cookie().
    myvoiceismypassword
    
    (salad@ferdmbp)3> erlang:set_cookie(mustard@ferdmbp, opensesame).
    true
    
    (salad@ferdmbp)4> erlang:get_cookie().
    myvoiceismypassword
    
    (salad@ferdmbp)5> net_kernel:connect_node(mustard@ferdmbp).
    true
    
    (salad@ferdmbp)6> erlang:set_cookie(node(), now_it_changes).
    true
    
    (salad@ferdmbp)7> erlang:get_cookie().
    now_it_changes

    각각은 다음을 의미한다.

    1. erlang:get_cookie()를 이용해 현재 사용하고 있는 쿠키를 불러온다.
    2. erlang:set_cookie(OtherNode, NewCookie)를 이용해 특정 노드에 접속할 때만 사용할 쿠키를 지정한다.
    3. 특정 노드에게만 해당 쿠키를 사용하도록 2번에서 설정했기 때문에 사용하는 쿠키는 변하지 않음. 
    4. set_cookie()로 동일한 쿠키를 사용하도록 설정했기 때문에 salad → mustard로 정상 연결 되었다. 
    5. set_cookie(self(), NewCookie)를 이용해 내가 사용하는 쿠키를 바꿀 수 있고, get_cookie()를 이용해보면 쿠키 설정값이 바뀐 것을 알 수 있다. 

     

    6.3 로컬에 저장된 쿠키

    얼랭을 시작할 때 -setcookie를 이용해서 쿠키를 설정할 수 있다. 만약 -setcookie를 설정하지 않았다면 홈 디렉토리에 있는 .erlang.cookie라는 파일에 저장된 값을 이용한다. 

     


    7. Remote Shell

    Ctrl + G -> 
    --> r mustard@ferdmbp
      --> j
        1  {shell,start,[init]}
        2* {mustard@ferdmbp,shell,start,[]}
      --> c

    다음 방식을 이용해 얼랭 쉘에서 원격 노드의 얼랭 쉘을 얻어서 작업할 수 있다. 사용하는 방법은 'Ctrl + G' → 'r 노드이름' → j (프로세스 선택) → c(로 접속)

    이 방식은 아래 경우에 굉장히 유용하다. 

    • -noshell 옵션으로 실행중인 노드에서 무언가를 변경해야하는 경우

     


    8. Hidden Nodes

    얼랭은 노드가 다른 노드에게 spawn(), connect(), 혹은 메세지를 전송하는 것만으로 하나의 클러스터로 접속된다. 이것은 꽤나 성가신 일이다.

    이것을 방지하기 위해 -hidden이라는 커맨드라인 인자를 이용할 수 있다. 이렇게 생성된 얼랭 노드는 다음 특성을 가진다.

    • 클러스터의 특정 노드에 메세지를 보내더라도, 메세지를 받은 노드하고만 연결됨.  (클러스터의 다른 노드는 Hidden 노드를 모름)

    간단한 실습은 다음과 같다. 

    % 히든 노드 생성 (Olive 노드)
    $ erl -sname olives -hidden 
    ...
    (olives@ferdmbp)1> net_kernel:connect_node(mustard@ferdmbp).
    true
    
    % Olive 노드에 공식적으로 연결된 노드는 없음. 
    (olives@ferdmbp)2> nodes().
    []
    
    % Olive 노드에 Hidden 노드를 살펴보면 mustard.
    (olives@ferdmbp)3> nodes(hidden).
    [mustard@ferdmbp]
    • 히든노드를 생성하고 다른 노드에 연결하면 연결이 정상적으로 이루어지는 것을 볼 수 있다.
    • nodes()로 연결된 노드를 살펴보면 없다고 나오지만, nodes(hidden)으로 살펴보면 mustard 노드가 연결된 것을 볼 수 있다. 

    메세지를 보내는 쪽 관점에서도 저 노드는 Hidden 노드가 되는 것이다.

    (mustard@ferdmbp)1> nodes().
    [salad@ferdmbp]
    (mustard@ferdmbp)2> nodes(hidden).
    [olives@ferdmbp]
    (mustard@ferdmbp)3> nodes(connected).
    [salad@ferdmbp,olives@ferdmbp]

    메세지를 받은 Mustard 노드 쪽에서도 마찬가지로 Olive와 Hidden 노드로 구현되어있다. 

     


    9. 분산 얼랭의 방화벽 뚫기

    서로 다른 곳에 있는 얼랭 노드끼리 통신을 하기 위해서 방화벽을 통과해야 할 수도 있다. 이럴 때는 다음과 같이 하면 된다.

    • 4396 포트를 연다. EPMD 용으로 정식 등록된 포트이며, 모든 OS에서 사용가능함. 
    • 얼랭은 노드 간 연결에 임의의 포트 번호를 할당함. → 포트 번호 지정 가능함.

    포트 번호를 지정하는 방법은 설정 파일을 전달하거나, erl 커맨드로 전달하는 방법이 있다. 

    % 커맨드로 전달하는 방법
    erl -name left_4_distribudead \
        -kernel inet_dist_listen_min 9100 \
        -kernel inet_dist_listen_max 9115

    얼랭에서 사용할 포트를 커맨드로 전달하는 방법이다. 

    % 파일 ports
    
    [{kernel,[
      {inet_dist_listen_min, 9100},
      {inet_dist_listen_max, 9115}
    ]}].
    
    % erl -name the_army_of_darknodes -config ports

    혹은 Config 파일을 만들고, 해당 파일을 이용하도록 설정할 수도 있다. 

     

     


    10. 

     

     

     

     

     

     

     

    요약

     

    • 각 노드마다 독립적인 ETS Table을 사용함. 
    • 각 노드마다 독립적인 Process Registry를 사용함 (atom에 Pid를 등록한다거나 하는)
    • 연결된 노드 중 하나가 죽어도, 다른 노드는 다운되지 않음. 
    • 얼랭 로컬 프로세스가 다른 서버의 얼랭 프로세스에게 일반 메세지를 보낼 수 있음 (! 연산자 이용)
    • 리모트로 통신은 로컬에서 통신하는 것과 동일함.
    • 리모트 프로세스에 Link + Monitor를 등록할 수 있음. 
    • 로컬에서 리모트 노드에 프로세스 생성할 수 있음.
      • 리모트 노드의 출력 결과는 로컬 노드에서 출력됨. 이것은 네트워크 리디렉션을 통해서임. 
    • 얼랭은 노드가 다른 노드에게 spawn(), connect(), 혹은 메세지를 전송하는 것만으로 하나의 클러스터로 접속된다
    • -hidden을 이용해서 불필요한 노드 클러스터 연결을 막을 수 있음.

     

    ㅇㄹㅇㄹ

    댓글

    Designed by JB FACTORY