zepinos
19k
2015-02-23 14:49:37
10
8894

Vert.x with Cluster 이해하기


Vert.x 에 대해서 이야기 해보고자 합니다. 주로 Vert.x 에서 제공해주는 cluster 모드에 대해서 이야기를 해볼까 합니다. (약간의 Vert.x 에 대한 지식이 있어야 이해가 될 겁니다)


Vert.x 는 Server 모드와 Embeded 모드가 있습니다. Server 모드는 Java 등으로 코드를 작성한 뒤 이를 vertx 실행자를 이용해 실행하는 방식입니다. 소스 코드를 그대로 넣어둬야 하기 때문에 직접 서버를 운영하지 않을 때에는 소스의 외부 유출이 문제가 될 수 있습니다. 반대로 Embeded 모드는 기존 코드에 상속을 받아서 구현하며 기존 코드 속에서 돌아가기 때문에 Vert.x 라이브러리만 포함하면 기존 코드 속에서 돌아갑니다.


하지만, 주로 언급할 cluster 는 Server 모드에서만 동작합니다. vertx 실행 시 -cluster 옵션을 주면 작동합니다.


cluster 는 기존에 많이 알려진 clustering 와 별다른 내용을 포함하진 않습니다. Fail-Over, Load Balancing 의 목적으로 구동되며, 이는 내장된 Hazelcast 을 기반으로 동작하기 때문에 cluster.xml (Hazelcast 의 설정파일과 일치) 을 설정하면 자동으로 연동하게 됩니다.


Vert.x 는 기본적으로 비동기 방식을 이용하고 있고, 다른 클래스의 매서드를 실행하는 것도 직접적인 호출 보다는 비동기 방식으로 호출(EventBus)하는 방식을 이용하기 때문에 Java 파일(혹은 Groovy 파일)을 Verticle 이라는 형태로 호출하게 됩니다. 그런데, 현재 정식 버전인 2.1.x 버전에서는 cluster 에서 이러한 Verticle 을 여러 서버의 것을 사용할 수 있도록 분산처리 해주는 형태이지, 공유 데이터(원래 Hazelcast 의 IMDG 로서의 역활)까지 동기화해서 처리해주지 못합니다.


게다가, NetServer 와 같이 클라이언트와의 통신이 1:1 관계가 아닌 상황에서는 더 상황이 복잡해집니다.


A/B/C 세 개의 서버가 cluster 모드로 설정이 되어 있을 경우, 클라이언트는 A 서버에 접속을 하게 됐을 때 A 에서 이를 처리하고 다시 클라이언트에게 메세지를 되돌려 줄 경우에는 문제가 없으나, 이를 EventBus 을 통해 처리를 할 경우 B 혹은 C 서버에서 이를 처리할 수 있습니다. 이 경우 A 서버에 보낸 메세지 처리 순서도 뒤죽박죽이 될 수 있고(먼저 보낸 메세지가 B 서버에서 처리되는 동안 다음 메세지가 A 서버에서 처리되어 먼저 return 될 수 있습니다) B 나 C 서버에서 처리하는 도중 클라이언트에게 직접 메세지를 보내려고 할 경우 메세지를 보낼 수 없는 문제까지 발생하게 됩니다. 클라이언트가 A 서버에 접속하게 되면 Handler ID 라는 고유값이 생성되는데, 이 고유값을 통해 클라이언트에게 메세지를 보낼 수 있습니다. 하지만 이것은 A 서버만 알 수 있는 값이고, B 서버나 C 서버는 이 값을 공유하지도 않고 클라이언트와 연결도 되어 있지 않기 때문에 메세지는 어둠 속(?)에 사라집니다.


저는 이 문제를 해결하기 위해 메세지를 발송하는 EventBus 을 각 서버별로 고유하게 만들고 클라이언트에게 메세지를 보낼 때 클라이언트가 접속한 서버의 해당 EventBus 로 메세지 내용을 보내 전송하도록 구조를 만들어 처리를 하였습니다. 이 부분은 Vert.x 개발자들도 인지하고 있지만, 왜 이런 구조가 필요한지...중요하게 생각 안하는 것 같더군요.


예를 들어보겠습니다. 1:1 채팅방을 만들 경우 처음에 한 사람이 A 서버에 접속했을 경우 A 서버에서 aaa 라는 Handler ID 을 생성한 뒤, 이 aaa 라는 Handler ID 을 Set 이나 Map(편의상 여기서는 Set 을 쓴다고 하겠습니다) 등에 저장을 합니다. 그리고 다른 한 사람이 C 서버에 접속했을 때 C 서버에서 ccc 라는 Handler ID 을 생성한 뒤 Set 을 뒤져 aaa 와 ccc 을 매칭시켜주는 시스템이라고 가정합시다.

그럼 aaa 가 접속을 했을 경우 먼저 Set 을 뒤집니다. Set 에 대기자가 없다면 aaa 에게 접속을 했으나 대기를 해야한다고 알려줘야 합니다. 그리고 기다립니다.

그리고 ccc 가 접속했을 경우 마찬가지로 Set 을 뒤집니다. Set 에 대기자 aaa 가 있으므로 자신에게 접속을 했고 aaa 와 채팅을 할 수 있다고 알려줘야 합니다. 뿐만 아니라 aaa 에게도 ccc 와 채팅을 할 수 있다고 알려줘야 합니다.

aaa 나 ccc 가 접속이 끊어졌을 때도 마찬가지 입니다. aaa 가 정상적으로 접속을 끊더라도, 혹은 비정상적으로 접속이 끊어졌더라도 ccc 에게 메세지를 보내줘야 합니다.

그런데, 위에서 나열한 행위들을 이걸 처리하는 것이 A 서버일 수도, C 서버일수도...아니면 엉뚱하게도 B 서버일 수도 있습니다. 왜냐하면 Clustering 의 부하 분산 때문입니다. aaa 의 접속이 끊어졌다는 것은 A 서버에서 이벤트로 받긴 하지만, 일반적으로 이런 메세지를 보내기 위해선 처리해야할 내용이 있을 수 있고, 그런 것을 EventBus 로 보내서 처리한 뒤 거기서 메세지를 보내게 됩니다. 이 때 EventBus 을 통해 A~C 서버 중 한 곳에 무작위로 배치되어 처리하기 때문에 문제가 발생할 수 있습니다. 또한, 하나의 요청에 대해 자신에게 메세지를 되돌려주는건 1회입니다. 자신에게 여러번 메세지를 보내야 할 수도 있고 상대에게도 메세지를 보내줘야 할 경우도 있는데 이런 경우 해당 클라이언트가 접속한 서버를 통해서만 보내야 합니다. 그래서 위에서 언급했듯이 클라이언트에게 메세지를 보내기 위한 특별한 EventBus 을 만들어 둘 필요가 있습니다(제가 고안했습니다. 매우 허접한 형태이니 양해를 바랍니다).


그런데 여기서 문제가 끝나지 않습니다. 3.0.x 버전(아직 M2 까지만 나왔습니다)에서는 기능이 보강이 되었으나 아직 정식 버전인 2.1.x 에서는 해결되지 않은 문제가 있습니다. EventBus 는 상호 사용이 가능하나, 공유 데이터를 다루는 내장 Hazelcast 을 이용한 ConcurrentSet 이나 ConcurrentMap(vertx.sharedData() 을 이용합니다)은 같은 서버의 EventBus 끼리는 데이터를 공유해도 다른 서버에 있는 EventBus 끼리는 데이터를 공유하지 못합니다. 3.0 에서는 이 부분을 해결해놓았으나 이 또한 비동기로만 구현을 해놓아서 원래의 Hazelcast 보다 사용이 불편합니다. 그래서 위의 1:1 채팅방에서 Set 에 aaa 을 넣어놓아도 사실 ccc 는 aaa 을 찾지 못하고 자신도 대기 상태가 되어버리는 문제가 생기게 됩니다.

저는 지금 2.1.5 을 이용중이기 때문에 이 문제를 간단하게 해결하기 위해서 처음 시작되는 Verticle 에 Hazelcast 설정파일(Vert.x 의 cluster.xml)을 다시 읽어 별도의 공유를 생성해서 사용하고 있습니다. 그래서 한 서버에 2 개의 Hazelcast 가 뜨게 되어 있습니다. 원래의 Hazelcast 는 위와 같은 제약이 없으므로 해당 Hazelcast 의 ISet 개체를 통해 쉽게 공유 데이터를 처리할 수 있습니다.


이처럼 Vert.x 의 cluster 는 쉽게 서버 코드를 확장할 수 있게 도와줄 것 같지만, 함정이 숨어 있습니다. 처리하는 매서드를 여러 서버에서 비동기로 처리하기 때문에 서버를 늘리면 늘릴수록 많은 접속(처리시간이 극단적으로 적고 접속량이 많은 경우 유리)을 처리하지만, 잘못 이해할 경우 원인도 모른채 동작이 이상하게 되는 현상을 맞이할 수 있게 됩니다.


이런 부분을 고려하고 Vert.x 을 이용해 프로그래밍을 하신다면 매우 가벼운, Java 로 혹은 Groovy 로 개발할 수 있는 Node,js 을 대체할 수 있는 프로그래밍을 하실 수 있을 겁니다.




------------------------------------------------------------------------------


문맥 상의 잘못된 점과 오해의 소지가 있는 부분 때문에 2015년 02월 24일 오후 2시29분에 내용을 추가하였습니다. 빨간색 글씨 부분입니다.


그리고 이 글은 zepinos(zepinos at nate dot com)에게 저작권이 있고 다른 곳으로 퍼갈 수 없습니다.

6
4
  • 댓글 10

  • 믿음
    4k
    2015-02-23 15:36:41
    zepinos 님, 좋은 글 감사드립니다. ^^
    0
  • Dev_Ruler
    61
    2015-02-24 13:29:14

    좋은 글 감사합니다.

    0
  • 하마
    6k
    2015-02-24 13:57:50

    "aaa 가 정상적으로 접속을 끊더라도, 혹은 비정상적으로 접속이 끊어졌더라도 ccc 에게 메세지를 보내줘야 합니다.

    그 런데, 이걸 처리하는 것이 A 서버일 수도, C 서버일수도...아니면 엉뚱하게도 B 서버일 수도 있습니다. 왜냐하면 Clustering 의 부하 분산 때문입니다. 자신에게 메세지를 되돌려주는건 1회입니다. 자신에게 여러번 메세지를 보내야할때도 있고 상대에게도 메세지를 보내줘야 할 경우도 있는데 이런 경우 해당 클라이언트가 접속한 서버를 통해서만 보내야 합니다. 그래서 위에서 언급했듯이 클라이언트에게 메세지를 보내기 위한 특별한 EventBus 을 만들어 둘 필요가 있습니다. "


    1. A서버에서 aaa 가 접속을 우아하게 끊었을경우, 그 closing 이벤트가 A,B,C 어디에서 일어날지 모른다건가요?  확실합니까? 

    2. 엉뚱하게도 B서버에서 closing  처리를 할경우 , B서버에서 aaa 가 접속한 A서버 나

        ccc 가 접속한 C 서버에게  각각의 EventBus (해당목적을 위해 만든)  를 통해서 알려준다는 

       얘기로 이해했는데 맞는지요.

     3. aaa 는 접속이 끊겨서 메세지도 못보내고 통신불가인데 A 서버로 여러번 메세지를 보내는 

       경우는 어떤경우가 있나요.

       

    글 잘읽었습니다.


    p.s 

    빠른 피드백 감사합니다. (_._)      

    1
  • zepinos
    19k
    2015-02-24 14:27:16

    저 부분은 저의 실수이네요. close 이벤트는 A 서버에서 일어납니다. 저기서 의도한 바는 A 서버에서 이벤트가 발생하였으나 ccc 에게 메세지를 보내야 하는데 그건 C 서버에서 보내야지만 제대로 갈 수 있다는 것입니다. 그리고, 접속이 끊어졌다는 이벤트는 A 서버의 org.vertx.java.core.net.NetSocket.closeHandler() 에서 발생합니다만, 일반적으로 이에 대한 메세지 발송 등을 또다시 eventBus 을 통해 보내게 되므로(다른 작업 해야 하니까) 이게 이벤트 버스를 타고 다른 서버로 가서 처리가 될 수 있다는 것이기도 하지요.


    그리고 접속이 끊긴 aaa 에게 여러번 보내는 것은 아닙니다. 접속이 끊겼을 때의 처리는 ccc 에게 한 번 혹은 여러번 보낼 수 있는 것이죠. aaa 가 접속이 끊어졌다...로 한 번, 너의 마지막 대화는 몇 분간 했다...등의 정보를 보내는 것 한 번...다른 사용자와 자동 매칭 중이니 기다려라 한 번...다른 사용자와 매칭되었다 한 번...이런 식으로 여러번 "ccc" 에게 보낼 수 있죠. 그건 서버에서 어떤 작업을 하느냐에 따라 몇 번이고 보낼 수 있는 것이니까요. 문맥 상 위의 내용은 끊긴 사용자에게 여러번 보낸다가 아니라 위에서 언급한 aaa 가 접속하고 ccc 가 접속하고 Set 을 뒤지고 접속이 끊어지는 등의 모든 행위에 대한 내용입니다.


    끊어지는 상태에서의 처리는 매우 지엽적인 "예제" 이므로 하나의 예로만 봐주시면 고맙겠습니다. 물론 오류 내용에 대해서는 계속 피드백을 받아서 수정하겠습니다.

    1
  • sehaeng
    6
    2015-02-26 11:04:48
    @zepinos cluster 로 이용시에만 생기는 문제인건가요? Embeded 방식으로 여러대를 띄워서 메시지를 주고받는데 문제없이 사용하고 있어서요. vert.x 2.0.2버전을 사용중입니다.
    0
  • zepinos
    19k
    2015-02-26 12:11:55

    sehaeng 님//네. 위의 문제는 -cluster 로 서버 모드로 실행했을 때에만 발생하는 문제입니다. Embeded 모드에서는 서버 간에 분산 처리가 안됩니다(맞나? ^^;;;).

    0
  • 쥬드노
    827
    2015-03-16 09:26:47
    조만간 clojure도 지원된다고 하니 기대가 큽니다.        
    0
  • 쥬드노
    827
    2015-03-16 09:27:14

    아 벌써 되는군요 ㅋㅋㅋ

    0
  • 골드만삭스
    1k
    2015-03-18 19:40:30

    좋은 자료 잘 보았습니다. 감사합니다.

    0
  • deuxksy
    51
    2016-01-07 20:23:52

    이번 Vertx 3 버전 으로 작업할때 많은 도움이 되었습니다 감사합니다.

    0
  • 로그인을 하시면 댓글을 등록할 수 있습니다.