2015년 4월 16일 목요일

Vert.x 3 에서 달라지는 점

거창하게 제목을 달았지만, 그리 거창한 것은 아닙니다.
제가 2.x 을 쓰다가 3.x 로 교체를 하고 있는데, 작업 중 느껴지는 바뀐 점들을 정리하는 것입니다.






1. deployWorkerVerticle() 이 사라지고 deployVerticle() 로 통합되었습니다. DeploymentOptions 을 이용해 옵션을 설정해서 deploy 시 옵션 내용을 적용하는 방식입니다.


2. Vertice 대신에 AbstractVerticle 등을 상속받아 사용해야 하며, BusModBase 가 사라졌습니다. BusModBase 의 sendOK() 나 sendError() 등으로 reply() 을 처리했다면 이를 모두 수정해야 합니다.


3. EventBus.registerHandler() 가 EventBus.consumer() 로 변경되었습니다. 사용법은 대동소이합니다.


4. Logger 가 사라졌습니다. 예제에서도 System.out.println() 을 이용하는 걸 봐선, 없어진게 맞을 것 같습니다.


5. Event Loop 에서 처리 시간이 지연될 경우 지속적인 경로 로그 출력이 되고 블록 상태가 풀리는 데도 시간이 좀 걸리는 것 같습니다. 사정상 MyBatis 으로 아주 금방 처리되는 Stored Procedure 을 호출하는 걸 만들어봤는데 정말 짜증나는 상황이 연출되었습니다. 오래 걸릴 코드가 있을 경우 vertx.executeBlocking() 을 이용해 비동기 형태로 처리하게 만들어서 이를 해결할 수도 있고, 그게 아니라면 Worker 로 Verticle 을 Deploy 해서 사용해야 합니다.


6. MySQL 등의 DB 연결 및 질의를 위한 라이브러리가 기본으로 포함되었습니다. 하지만, 기능이 아직은 미약하고, 모두 비동기 방식으로 사용하기 때문에 질의를 순차적으로 여러번 보내야 할 경우 정말 짜증나는 경우가 생길 수 있습니다. 게다가 아직 Stored Procedure 을 제대로 실행하질 못하고, 결과 역시 당연히 못받아옵니다.


7. HttpServer 을 쉽게 사용할 수 있게 해주는 RouteMatcher 가 Router/Route 등으로 변경되었습니다.


8. Vertx.sharedData() 에서 이용할 수 있었던 공유 개체가 ConcurrentMap 등에서 LocalMap 와 ClusterWideMap(AsyncMap) 와 같이 나뉘어졌으며 ClusterWideMap 은 비동기 형태로 가져오며 Lock, Counter 을 이용할 수 있도록 되어 있습니다. 더 이상 Cluster 모드를 위한 외부 라이브러리 이용이 필요 없습니다.


9. EventBus 역시 Cluster 에서 동일하게 사용할 수 있는 기능이 포함되었습니다. EventBus 가 Cluster 에서 모두 등록이 완료될 때 이벤트를 발생해주도록 개선되었습니다.


10. DNS Client 가 추가되었습니다.


11. 내부에서 많이 사용하게 되는 JsonObject 등의 기능이 늘어났습니다. 특히 JsonArray 에서 포지션을 이용한 조회만 제공했던 2.x 버전과 달리 삭제까지 제공하게 되었습니다. stream() 역시 생겼는데, 아쉽게도 내부 개체들은 JsonObject/JsonArray 에서 HashMap/ArrayList 로 바뀌서 저장됩니다.








지금까지 작업하면서 제가 느낀 차이점들입니다. 더 많은 것들이 있지만 일단 큰 것들은 이정도네요. 잘못된 내용이나 지적사항은 언제라도 연락주십시오.

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

2015년 2월 23일 월요일

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 을 대체할 수 있는 프로그래밍을 하실 수 있을 겁니다.



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

2015년 2월 9일 월요일

SELinux 에서 권한 재설정

그냥 cp 을 할 경우 SELinux 의 권한 정보가 같이 복사되지 않습니다.

그럴 경우...

restorecon -rv (경로)