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 (경로)

CentOS(RHEL 등) 에서 최신버전의 MySQL 설치

현재 가장 많이 사용하는 CentOS 6 에는 MySQL 5.1 이 기본 제공됩니다. 특성상 마이너 업그레이드가 진행되어도 MySQL 의 마이너 버전 업그레이드는 이루어지지 않습니다.

그럼, rpm 을 가져와서 설치하거나 소스 설치를 해야만 하는 걸까요?

그건 아닙니다. 친절하게도 yum repository 을 제공합니다.
아래 링크에서 각 메이저 버전에 맞는 repository rpm 을 제공합니다.



http://dev.mysql.com/downloads/repo/yum/



CentOS 6 (RHEL 6 등) 을 위한 파일은 아래와 같습니다.



http://dev.mysql.com/get/mysql-community-release-el6-5.noarch.rpm



이를 설치하기 위해서는



yum install http://dev.mysql.com/get/mysql-community-release-el6-5.noarch.rpm



와 같이 바로 설치를 하면 됩니다. 이후 yum update 후 다시 mysql-server 을 설치하면 최신 버전의 MySQL 을 설치할 수 있습니다.



여담으로, MariaDB 대신에 MySQL 을 설치하는 이유는 MySQL Workbench 때문입니다. MariaDB 가 버전을 10.x 로 올려버리는 바람에 Workbench 에서 알 수 없는 버전이라고 하며 몇가지 기능을 사용할 수 없게 해버리기 때문입니다.

AWS 의 Elastic IP 초기 5 개 제한 상향하기

AWS 에서 EC2 을 신청한 뒤 이를 고정IP 로 만들어두기 위해서 Elastic IP 을 신청하여 EC2 Instanse 와 연결합니다. 그런데, 고정IP 로 모두 발급하기엔 IP 자원이 부족하다는 이유로 초기에는 5 개의 IP 만 발급받을 수 있습니다.


더 많은 IP 을 신청하기 위해서는 별도의 신청 절차를 거쳐야 합니다.

http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/elastic-ip-addresses-eip.html

위 공식 문서에 나와있는데...제일 아래쪽에 있으니(게다가 영어) 사람들이 잘 모르더군요.

https://console.aws.amazon.com/support/home#/case/create?issueType=service-limit-increase&limitType=service-code-elastic-ips-ec2-classic

위 링크에 접속해서 상향 신청을 해야 합니다. 그냥 해주진 않고 필요하다고 판단할 때만 주는 것 같네요.

CentOS 에서 Locale, TimeZone 변경

Locale 변경


/etc/sysconfig/i18n 파일을 열어서 수정



TimeZone 변경


ln -sf /usr/share/zoneinfo/Asia/Seoul /etc/localtime

으로 타임존 설정을 복제하거나 심볼릭 링크

vi /etc/sysconfig/clock

에서

ZONE="Asia/Seoul"
UTC=False

로 변경