기고문2014.07.05 15:09

본 내용은 월간 마이크로소프트웨어 2014년 7월호에 기고된 내용입니다 :)


--


배달음식 주문 중계 서비스 '철가방'의 핵심 기능중 하나인  실시간 알림(PUSH)을 구현하기 위해 Zookeeper와 Vert.x를 적용한 사례에 대해 이야기합니다.



배달음식 주문 중계 서비스의 정의


배달음식 주문 중계 서비스란, 일상생활에서 여러분들이 많이 사용하고 있는 ‘배달의 XX’, ‘요X요’ 등의 스마트폰 어플리케이션을 떠올려 보면 쉽게 알 수 있습니다. 일반 소비자가 스마트 디바이스(스마트폰, 스마트TV 등) 또는 웹과 같은 인터넷 사용이 가능한 매체를 통해 음식 주문을 발생 시키면, 해당 정보를 배달음식 가맹점에게 전달하여, 음식 주문 거래가 성립케 하는 서비스입니다.


전통적으로, 일반 소비자가 배달음식 가맹점과 가장 쉽게 커뮤니케이션할 수 있는 방법은 전화를 이용하는 것 이였습니다. 하지만 전화를 통한 커뮤니케이션 방식에서 일반 소비자는 배달음식 가맹점 연락처를 미리 알고 있어야 한다는 불편함을, 배달음식 가맹점에는 일반 소비자에게 해당 가맹점을 홍보하기 위한 마케팅 비용의 부담이 발생하게 됩니다.


스마트 디바이스를 활용한 배달음식 주문 중계 서비스는 이처럼 전화를 기반으로 하는 서비스에서 일반 사용자와 배달음식 가맹점 양측 모두가 지니는 불편과 부담을 최소화하기 위한 대체 서비스입니다. 물론 최근 유명한 배달음식 주문 중계 스마트폰 어플리케이션을 운영하는 업체에서 주문 중계 수수료에 대한 논란이 있었던 만큼 시장 상황이 그렇게 녹녹치 만은 않고, 앞으로 해결해야할 과제가 아주 많은 분야이기도 합니다.



배달음식 주문 중계 서비스 구조


배달음식 주문 중계 서비스에 대한 포괄적인 이야기는 여기까지 하고, 주문 중계 서비스를 구축하기 위한 기술적 제반 사항에 대해 이야기 해보도록 하겠습니다.


배달음식 주문 중계 서비스를 구성하는 핵심 티어는 <화면 1>과 같이 일반 사용자와 배달음식 가맹점, 그리고 이 둘을 이어주는 주문 중계 서비스업체로 구분할 수 있습니다. 여기에 부가적인 기능을 수행하기 위해 통신사 및 결제대행업체(VAN)가 참여할 수 있습니다.


<화면 1. 주문 중계 서비스 티어>


각 티어의 고유 기능과 티어들 간의 연결을 위해 <리스트 1>과 같이 다양한 플랫폼과 기술이 개발되고 운영되고 있습니다. 모든 내용을 살펴보기에는 분량이 매우 많기 때문에, 핵심 티어인 일반 사용자와 배달음식 가맹점, 그리고 이 둘 사이를 실시간으로 연결하기 위한 방법에 대해 보다 자세히 알아보도록 하겠습니다.


 

 주문 중계 서비스 업체

배달음식 가맹점 

일반 사용자 

 솔루션

 Java 기반 

Spring, Vert.x 

서버 시스템

 Windows 응용 어플리케이션

iOS

Android

Web 

서버 연동방식 

RemoteObjectCall

REST API

연동방식 제공 

RemoteObjectCall 

REST API 

 실시간 알림

TCP PUSH

TTS

SMS/LMS

알림 방식 제공 

TCP PUSH

TTS

SMS/LMS 

APNS

GCM 

<리스트 1. 티어별 시스템 구성>


가장 먼저 일반 사용자가 관심 있어 하는 지역(현재 위치 또는 미리 등록된 지점 등)에 위치한 배달음식 가맹점 정보를 제공하고, 특정 가맹점으로 주문정보를 발생시키는 기능을 수행하는 어플리케이션이 필요합니다. 일반적으로 안드로이드/iOS 운영체제에서 동작하는 스마트폰 어플리케이션은 그 역할을 매우 훌륭하게 수행해 냅니다. 

물론 스마트폰 어플리케이션에 전국의 배달음식 가맹점 데이터 모두를 담을 수 없고, 또한 실시간으로 변동되는 배달음식 가맹점 상태 정보를 수집하고 제공하기 위해 주문 중계 서비스 업체는 스마트폰 어플리케이션과 연동될 수 있는 서버를 운영해야 합니다. 스마트폰 어플리케이션과 서버는 인터넷을 통해 데이터를 송/수신하는 할 수 있는데, RESTful하고 Stateless한 인터페이스(이하 REST API)를 통해 통신하는 것이 일반적인 선택사항 이라 할 수 있습니다.


일반 사용자는 이러한 스마트폰 어플리케이션을 통해 배달음식 가맹점 정보를 확인하고, 최종적으로 선택한 가맹점에 주문정보를 발생시킵니다. 주문정보는 REST API서버에서 최종적으로 확인되고 검증됩니다. 검증 정보에는 주문 금액, 결제방식 등이 포함되며, 경우에 따라 결제대행업체(VAN)와 연동될 수도 있습니다.


자 이제부터 핵심적인 문제가 시작됩니다.

일반 사용자로부터 발생한 주문정보는 현재 REST API서버에서 확인되었고, 주문정보를 해당 배달음식 가맹점에 전달해야 합니다. 주문정보를 배달음식 가맹점에 전달하는데 걸리는 시간은 실시간에 가까울수록 좋습니다. 주문 후 30분 넘도록 음식이 오지 않을 경우 여러분의 모습을 생각해보면 쉽게 그 이유를 알 수 있습니다.



방법 1. 전화 재 주문 방식


먼저 엔지니어 입장에서 가장 간단한 방법이지만, 서비스 운영의 측면에서는 비용이 가장 많이 드는 방법에 대해 이야기 해보겠습니다.


그것은 바로, 발생된 주문정보를 사람이 확인하고, 해당 배달음식 가맹점에 전화를 해서 주문을 대신해주는 방법입니다. 주문정보가 언제 발생할지 예측할 수 없기 때문에, 해당 업무를 수행하는 사람은 24시간 새로운 주문정보가 있는지 확인해야 합니다. 간혹 주문정보를 잘못 확인하고 엉뚱한 주문을 넣는 바람에 일반 사용자는 시키지도 않은 음식을 받아보거나, 배달 장소가 바뀌어 음식이 오지 않는 경험을 할지도 모릅니다. 


스마트폰에서 스마트하게 주문하는 것은 좋았지만, 이 후 과정은 전혀 스마트하지 않은 이 방법이 아이러니하게도 실제 현장에서는 가장 폭 넓게 사용되고 있기도 합니다. 24시간 주문정보를 모니터링하기 위한 직원을 수십 명(또는 수백 명)씩 배치하기 때문에 엄청난 인건비가 지출되기도 합니다. 근본적으로 전통의 전화를 기반으로 하는 커뮤니케이션 방법에서 크게 나아지지 않은 방법입니다.


하지만, 우리 같은 엔지니어는 별로 할 일이 없습니다. 스마트폰 어플리케이션과 이와 연동되는 REST API서버, 그리고 주문정보를 알려주는 윈도우 하나만 개발하면 모든 게 끝납니다.



방법 2. 배달음식 가맹점 프로그램 사용


사실 방법 1의 주문내용 전달 오류, 고비용이란 치명적인 단점에도 불구하고, 많은 서비스 업체에서 해당 방법을 고수하는 이유는, 전국의 모든 배달음식 가맹점에 실시간으로 주문정보를 전달할 수 있는 표준적인 방법도 없고, 그렇다고 전국의 모든 가맹점에 주문정보 알림 프로그램을 설치하기도 어렵기 때문입니다. 현실에는 주문정보 알림 프로그램의 개발부터 배포, 교육, 지속적 관리 등 해결해야할 문제가 산재해 있지만, 지면을 통해서는 ‘Zookeeper, Vert.x를 활용한 실시간 PUSH 알림 구현’이라는 주제에 맞게 주문정보 알림 프로그램 개발이라는 문제에만 집중해 보겠습니다.


가장 먼저 고민해야 할 것은 바로 주문정보 알림 프로그램 그 자체를 무엇으로 어떻게 만들 것인가? 하는 것입니다. PC환경에 독립적이게 웹 기반으로 만들 수도 있고, Windows 네이티브 어플리케이션으로 만들 수 도 있습니다. 반드시 고려해야할 점은 배달음식 가맹점 현장의 컴퓨팅 환경은 상당히 열악한 조건이 많으며, 사용자의 연령대를 고려해봤을 때 사용하기 어렵지 않아야 하고, 사용자에게 많은 조작을 요구하면 주문 받고 요리하는데 바쁜 가맹점 사장님들이 싫어할게 당연하기 때문에 웬만한 건 다 자동으로 처리 되어야 한다는 것입니다.


그래서 우리는 유행이 좀 지난 기술이긴 하지만 위와 같은 요구사항을 수용하기 위해 FLEX AIR를 기반으로 <화면 2>의 배달음식 가맹점 프로그램을 개발하였습니다. (사실 FLEX AIR Windows 어플리케이션은 지금도 충분히 좋은 대안이라 생각합니다.)



<화면 2. 철가방 가맹점 관리 프로그램>


가맹점 관리 프로그램의 주요 기능은 2가지로 요약할 수 있는데, 첫 번째 기능은 스마트폰 어플리케이션처럼 서버와의 연동을 통해 각종 정보 처리가 가능해야 하는 것이고, 두 번째 기능은 실시간 주문정보 알림을 받을 수 있어야 하는 것입니다.



Spring BlazeDS Integration


스마트폰 어플리케이션이 서버와 REST API통해 연결될 수 있는 것처럼, FLEX AIR 어플리케이션 또한 서버와 REST API를 통해 연결될 수 있습니다. 그러나 여기에는 좀 더 나은 대안이 존재하는데 그것은 바로 Adobe에서 제공하는 BlazeDS란 오픈소스 솔루션을 통해 AMF3(Action Message Format 3) 기반의 RemoteObjectService를 사용하는 것입니다. RemoteObjectService에 대해 간단히 설명하자면, FLEX 어플리케이션에서 서버에 정의된 JAVA Method를 Call할 수 있도록 하는 일종의 RPC(Remote Procedure Call) 서비스입니다. AMF3은 현재 폭넓게 사용되고 있는 JSON이나 XML보다 작은 메모리를 사용하고 훨씬 빠른 객체 직렬화 속도를 보여줍니다. 또한, Spring Source에서 Spring BlaseDS Integration을 제공하기 때문에 기존 Spring 기반 어플리케이션에 쉽게 통합할 수 있기도 합니다.



<화면 3. Spring BlazeDS Integration>

출처 : http://10panther01.blogspot.kr


한 가지 주의 사항은, Google App Engine처럼 Load Balancing 되는 환경에서 BlazeDS를 운영하다 보면 중복되는 Flex 세션이 있다는 오류를 만나게 되는데, 해당 오류를 발생시키는 flex-messaging-core.jarlex.messaging.endpoints.BaseHTTPEndpoint를 패치하고 다시 컴파일하면 문제를 해결할 수 있습니다.

 


PUSH서버


BlazeDS의 RemoteObjectService를 통해 첫 번째 요구사항을 충족시켰다면, 두 번째 요구 조건인 실시간 주문정보 알림을 구현하기 위해 PUSH서버를 개발하게 됩니다. PUSH서버와 가맹점 관리 프로그램은 TCP를 통해 연결됩니다. 주문정보가 발생할 경우 PUSH서버에서는 해당 가맹점의 관리 프로그램 연결을 확인하고, 주문정보를 전송하게 됩니다. 여기까지만 본다면 PUSH서버는 별로 어렵지 않게 구현할 수 있을 것 같습니다.



<화면 4. 시스템 구성도>


그러나 <화면 4>와 같은 시스템 구성을 보면 파악할 수 있듯이 위와 같이 간단한 구조에서는 PUSH서버가 SPOF(Single Point Of Failure)이며, PUSH서버와 연결되는 가맹점 프로그램 개수가 적게는 수백 개에서 많게는 수천/수만 개 까지 확장될 수 있다는 점을 감안해보면 결코 가볍게 넘어갈 수 있는 문제가 아님은 자명해 보입니다.


그래서 PUSH서버의 다중화와 부하 분산, 장애극복을 실현하기 위한 시스템 구성을 다시 생각해 보았습니다.



<화면 5. 개선된 시스템 구성도>


이전 시스템 구성과의 차이점이라면, PUSH서버가 다중화 됨에 따라 로드밸런서(Vert.x 기반)가 추가되었고, PUSH서버의 온라인 상태를 체크하기 위해 Zookeeper가 사용된 것입니다. 또한, PUSH서버와 가맹점 관리 프로그램은 간단한 상태체크 알고리즘을 통해 TCP연결 상태를 확인하고, 연결에 문제가 있을시 가맹점 관리 프로그램은 장애극복 모드로 전환되어 PUSH서버로의 TCP연결을 복구합니다.



Zookeeper


먼저 Zookeeper에 대한 간략한 소개를 하자면, ‘분산 작업을 제어하기 위한 트리 형태의 데이터 저장소’라고 할 수 있습니다. Zookeeper에서 관리 되는 트리의 특정 노드에 데이터를 저장하고 변경할 수 있는데, 특정 노드에 감시자(Watcher)를 등록하면 Callback을 통해 클라이언트에게 노드 변경 여부를 알려줍니다. 노드에는 한 번 저장한 데이터가 영구적으로 유지되는 영구 노드(Permanent Node) 외에도 클라이언트 세션이 유효한 동안만 살아있는 임시 노드(Ephemeral Node)와 저장하는 순서에 따라 자동으로 일련번호가 붙는 순차 노드(Sequence Node)가 있습니다. Zookeeper의 기능은 사실상 이게 전부인데, 여기서 우리가 눈여겨봐야 하는 것은 바로 임시 노드(Ephemeral Node)입니다. 


PUSH서버는 Zookeeper와의 세션을 생성하고, 해당 세션이 유효한 동안에만 유지되는 임시 노드를 등록함으로 PUSH서버의 온라인/오프라인 상태를 Zookeeper를 통해 즉각적으로 파악할 수 있도록 합니다. 즉, 새로운 PUSH서버가 추가되면 Zookeeper에 신규 임시 노드가 등록될 것이고, 기존 PUSH서버에 장애가 생겨 세션이 끊어지면 해당 임시 노드는 Zookeeper에서 제거 될 것입니다. 



<화면 6. Zookeeper 임시노드와 PUSH서버>


가맹점 관리 프로그램은 이제 직접적으로 PUSH서버와 연결되지 않고, 로드밸런서를 통해 현재 온라인 상태의 PUSH서버 중 한 개를 배정받아 커넥션을 연결하게 됩니다. 로드밸런서는 알고리즘에 따라 적당한 PUSH서버를 선택함으로서 PUSH서버의 룩업 기능뿐 아니라 부하 분산 장치로서의 역할을 하게 됩니다.


가맹점 관리 프로그램과 PUSH서버는 일정 주기로 Ping을 주고받는 Healthcheck 기능을 지니고 있는데, Healthcheck 알고리즘을 통해 상태 이상이 감지되면 가맹점 관리 프로그램은 PUSH서버와의 연결을 즉각 종료하고, 처음의 과정으로 돌아가 로드밸런서를 통해 새로운 PUSH서버를 룩업하고 연결을 시도함으로서 장애극복을 실현할 수 있습니다.


PUSH서버로의 트래픽 분산 제어를 위해 Zookeeper가 사용되는 만큼, Zookeeper자체가

중단되면 PUSH서버로의 신규 연결 요청이 모두 마비가 됩니다. 따라서 Zookeeper자체도 최대한 정상 동작을 보장해야 하는데, 이를 위해 여러 대의 Zookeeper 서버를 클러스터로 구성해 고가용성을 확보해야 합니다. 이것을 Zookeeper 앙상블(Ensemble)이라 합니다.


앙상블로 묶인 Zookeeper 인스턴스 중 한 대는 쓰기 명령을 총괄하는 리더 역할을 수행하고, 나머지는 팔로어 역할을 수행하는데, 클라이언트가 전달한 읽기 명령은 현재 연결된 Zookeeper 인스턴스에서 바로 반환되지만, 이에 비해 쓰기 명령은 앙상블 중 리더 역할을 수행하는 Zookeeper 인스턴스로 전달되며, 리더 Zookeeper는 모든 팔로어 Zookeeper에게 해당 쓰기를 수행할 수 있는지 질의하게 됩니다. 만약 팔로어 중 과반수(> n/2)의 팔로어로부터 쓸 수 있다는 응답을 받으면 리더는 팔로어에게 데이터를 쓰도록 지시합니다. 즉, 앙상블의 구성하는 Zookeeper 인스턴스 중 과반수가 살아있다면 데이터 읽기/쓰기를 정상적으로 처리할 수 있습니다. (3대를 사용한다면 1대가 중단되어도 문제가 없습니다.)



Vert.x 기반 로드밸런서


가맹점 관리 프로그램과 PUSH서버, 그리고 Zookeeper까지 완료되었다면 최종적으로 로드밸런서만 준비되면 모든 것이 다 갖춰지게 됩니다. 로드밸런서는 앞서 설명한 대로 PUSH서버의 룩업 기능과 부하 분산 역할을 하게 됩니다. HTTP, TCP, WebSocket 등 다양한 프로토콜을 통해 로드밸런서에 접근할 수 있어야 하며, Zookeeper와 마찬가지로 최대한의 동작을 보장하기 위한 클러스터 구성이 가능해야 합니다. 그리고 이에 대한 해답으로 Vert.x를 선택하게 됩니다.


Vert.x는 요즘 핫한 Node.js처럼 비동기 이벤트 방식의 프로그래밍 모델을 제공하는 플랫폼으로, Javascript뿐만 아니라 Java, Groovy 등 다양한 언어를 지원하고, Node.js보다 효율적으로 멀티코어 시스템을 활용할 수 있다는 장점이 있습니다.



<화면 7. Vert.x 기본 구조>

출처: http://www.javaworld.com/article/2078838/mobile-java/open-source-java-projects-vert-x.html


Vert.x의 기본구조에 대해 설명하자면, 하나의 Vert.x 인스턴스는 다 수의 Verticle을 포함할 수 있으며, Vert.x 인스턴스는 동일한 JVM 머신 또는 네트워크상의 JVM 머신들과 클러스터로 구성될 수 있습니다. 클러스터로 묶인 Vert.x 인스턴스 내의 Verticle들은 분산 이벤트 버스를 통해 메시지를 주고받을 수 있습니다. (Vert.x의 이벤트 버스는 HazelCast 라는 In Memory Data Grid 오픈소스 솔루션을 사용합니다.)


Verticle은 Vert.x에서 하나의 실행 단위(또는 배포단위)로 생각할 수 있는데, 쉽게 말하자면 Main 메소드를 포함하는 하나의 Java 클래스라 할 수 있습니다. 즉, 실제적인 어플리케이션 코드를 담고 있는 것이 바로 Verticle이며, 서로 상호작용 하는 1개 이상의 Verticle들의 조합으로 Vert.x 어플리케이션을 만들 수 있습니다. 각각의 Verticle들은 서로 독립적인 클래스로더를 사용하기 때문에, Verticle 내의 선언된 지역번수는 물론 전역변수, 정적변수 까지 독립적인 상태를 유지하며, Thread 경합 상태를 발생시키지 않습니다. (Single Thread 기반이라 생각하고 코드를 작성해도 됩니다.)


Vert.x 어플리케이션을 개발할 때 주의할 점은, 표준 Verticle내의 코드들은 절대 Thread block을 유발해서는 안 된다는 것입니다. JDBC코드와 같이 Thread block 코드를 어쩔 수 없이 사용해야 하는 상황이 있다면, 표준 Verticle이 아닌, Worker Verticle을 사용해야 합니다. Worker Verticle은 표준 Verticle과 다르게, Event Loop Thread를 점유하지 않으며, Vert.x 인스턴스내의 별도의 Worker Thread Pool에서 동작하게 됩니다. 


이상 Vert.x의 특징을 빠르게 살펴보았는데, 보다 자세한 정보는 Vert.x의 공식 홈페이지에서 제공하는 매뉴얼을 통해 파악하실 수 있습니다. Vert.x를 프로젝트에 도입할 예정인 분들은 반드시 해당 자료를 참조하시기 바랍니다.


그럼 구체적으로 Vert.x를 사용해 개발한 로드밸런서의 구조를 살펴보겠습니다. 로드밴런서는 <화면 8>에서와 같이 5개의 Verticle로 구성되있으며, 이중 1개는 앞서 설명한 Woker Verticle로서 Zookeeper 서버와 통신을 담당하며, 3개의 Verticle은 각각 HTTP, TCP, WebSocket을 통해 클라이언트의 요청을 수신하는 역할을 담당하고 있습니다. 마지막 1개의 Verticle은 실질적은 어플리케이션 로직을 담고 있지는 않지만, 나머지 4개의 Verticle들에 대한 설정정보와 이들을 배포(deploy)시키는 역할을 담당합니다.


<화면 8. 로그 밸런서의 Verticle구성>

 

3개의 Verticle에서 클라이언트의 요청을 수신하면, 이벤트 버스를 통해 Worker Verticle에 해당 내용을 전달하게 되고, Worker Verticle은 해당 내용을 전달받아 처리하고, 그 결과는 다시 최초 요청을 전달한 Verticle에 전달하게 되는 매우 간단한 구조입니다.


<화면 9. 클러스터 구성>


클러스터 기능을 사용하면 <화면 9>와 비슷한 형태로 각 서버에 로드밸런서가 배포될 것입니다. 주목할 점은 로드밸런서를 구성하는 Verticle의 개수를 자유롭게 변경할 수 있다는 것입니다. 일반적으로 Worker Verticle에 부하가 몰리기 때문에 Worker Verticle의 개수를 4개로 늘려놓은 모습니다.



Vert.x 예제 코드


간단하게 로드밸런서를 구성하는 Verticle의 소스코드를 보며, Vert.x 개발 스타일에 대해 알아보겠습니다. 


아래 코드는 SocketAcceptorVerticle의 일부분입니다. 일부분이라고는 하지만 사실상 이 코드가 거의 전부이기도 합니다. 클라이언트가 새롭게 연결 되었을 때 호출되는 connectionHandler를 시작으로 Callback방식을 통해 각 이벤트를 처리하는 비동기 이벤트 방식 프로그래밍 모델임에 주목합니다. 


클라이언트로부터 데이터를 수신할 때 호출되는 dataHandler가 가장 중요한 부분입니다. 수신 된 데이터를 이벤트 버스를 통해 ZKClientWorkerVerticle로 전달하고, 리턴 값을 받으면 클라이언트로 최종 응답합니다. 예제에서는 고정 3바이트를 기준으로 TCP스트림을 나누는 방식을 사용하고 있는데, 가변길이 TCP스트림을 처리할 때는 TCP스트림을 정확한 단위로 나누기 위해 첫 4바이트는 뒤따라오는 페이로드의 길이를 알기 위해 사용되고, 해당 길이만큼 TCP스트림을 읽어 들이는 방법을 사용합니다.

//-- Net Server 설정
server = vertx.createNetServer();
server.connectHandler(new Handler<netsocket>() {
	@Override
	public void handle(final NetSocket sock) {
		logger.info("sock.writeHandlerID["+sock.writeHandlerID()+"] remote host connected: "+sock.remoteAddress());
				
		//-- close handler
		sock.closeHandler(new VoidHandler() {
			@Override
			protected void handle() {
				logger.info("sock.writeHandlerID["+sock.writeHandlerID()+"] remote host disconnected: "+sock.remoteAddress());
			}
		});
		//-- data handler
		sock.dataHandler(RecordParser.newFixed(3, new Handler<buffer>() {
			@Override
			public void handle(final Buffer data) {
				logger.debug("The total body received was "+data.length()+" bytes.");
				logger.info("Lookup request received: "+data.toString());
						
				eb.send(address, data.toString(), new Handler<message<string>>() {
					@Override
					public void handle(Message<string> reply) {
						sock.write(reply.body());
					}
				});
			}
		}));
		//-- exception handler
		sock.exceptionHandler(new Handler<throwable>() {
			@Override
			public void handle(Throwable throwable) {
				if( throwable instanceof IOException ) {
					/* 현재 연결은 원격 호스트에 의해 강제로 끊겼습니다(아마도?) */
				}
				else {
					logger.error("sock.writeHandlerID["+sock.writeHandlerID()+"] Unexpected exception occur: ", throwable);
				}
			}
		});
	}
});

ZKClientWorkerVerticle은 더 심플한데, Zookeeper 앙상블에 연결을 설정하고, 앞서 SocketAcceptorVerticle에서 이벤트 버스로 전송한 데이터를 파싱해 요청을 해석하고 그 결과 값을 다시 처음 해당 데이터를 전송한 SocketAcceptorVerticle로 응답하는 것이 전부입니다.

@Override
public void start(final Future<Void> startedResult) {
	super.start();
	
	address 	= StringUtils.defaultIfEmpty(config.getString("address"), DEFAULT_ZKWCLT_ADDRESS);
	zkhostPort 	= StringUtils.defaultIfEmpty(config.getString("zkhostPort"), DEFAULT_ZK_HOSTPORT);
	zkSessionTimeout = (Integer) ObjectUtils.defaultIfNull(config.getInteger("zkSessionTimeout"), DEFAULT_ZK_SESSION_TIMEOUT);
		
	try {
		zk = new ZooKeeper(zkhostPort, zkSessionTimeout, this);
	} catch (IOException e) {
		zk = null;
		logger.error(e, e);
	}
		
	eb.registerHandler(address, new Handler<Message<String>>() {
		@Override
		public void handle(final Message<String> data) {
			String result = createReponse(data.body());
			data.reply(result);
		}
	},
	new AsyncResultHandler<Void>() {
		@Override
		public void handle(AsyncResult<Void> result) {
			if( !result.succeeded() ) {
				result.cause().printStackTrace();
				startedResult.setFailure(result.cause());
			}
			else {
				startedResult.setResult(null);
			}
			logger.info("Zookeeper client worker "+(result.succeeded()?"OK":"FAILED"));
		}
	});
}

마지막으로 StarterVerticle인데 모듈을 구성하는 각 Verticle을 배포하는 역할을 하고 있습니다. Zookeeper로 연결되어 동기연산을 처리하는 ZKClientWokerVerticle은 Vert.x인스턴스의 Event Loop Thread에서 실행되면 안 되기 때문에 WokerVerticle로 실행하는 것에 주목합니다. HTTPAcceptorVerticle과 WSocketAcceptorVerticle은 따로 설명은 하지 않았지만, 앞서 SocketAcceptorVerticle의 흐름과 크게 다르지 않습니다.

package com.hellowd.s3.lookup;

import org.vertx.java.core.json.JsonObject;
import org.vertx.java.core.logging.Logger;
import org.vertx.java.platform.Verticle;

public class StarterVerticle extends Verticle {
	
	private Logger 		logger;
	private JsonObject 	config;
	
	@Override
	public void start() {
		logger = container.logger();
		config = container.config();
	   
	    logger.info("app config: "+config.toString());
	    
	    container.deployVerticle("com.hellowd.s3.lookup.acceptor.HTTPAcceptorVerticle", 
	    		config.getObject("httpAcceptor"));
	    container.deployVerticle("com.hellowd.s3.lookup.acceptor.SocketAccreptorVerticle", 
	    		config.getObject("sockAcceptor"));
	    container.deployVerticle("com.hellowd.s3.lookup.acceptor.WSocketAcceptorVerticle", 
	    		config.getObject("wsAcceptor"));
	    container.deployWorkerVerticle("com.hellowd.s3.lookup.worker.ZKClientWorkerVerticle", 
	    		config.getObject("zkClientWorker"), 4);
	}

}

마치며

이것으로 ‘철가방’ 시스템을 구성하는 주요 부분에 대해 알아보았습니다. 제한된 지면을 통해 많은 내용을 전달하려다 보니, 설명이 부족한 부분도 많은 것이라 생각합니다. 부하분산, 장애극복과 같은 주제는 실제 서비스를 운영하며 사고가 터지면 욕도 먹어보고, 새벽에 불려나가는 경험을 하면서 내가 먼저 인간답게 살아보고자 하는 마음에 연구에 매진하게 되는 분야인 것 같다는 생각을 했습니다. 이 내용이 비슷한 고민을 하고 있는 많은 분들께 작게나마 도움이 되기를 바랍니다.



* 참고문헌 및 자료

BlazeDS

1. http://livedocs.adobe.com/blazeds/1/blazeds_devguide

2. http://sourceforge.net/adobe/blazeds/wiki/Home

3. http://www.jamesward.com/2007/04/30/ajax-and-flex-data-loading-benchmarks

BlazeDS-Spring

1. https://github.com/spring-projects/spring-flex 

Zookeeper

1. http://zookeeper.apache.org

2. http://helloworld.naver.com/helloworld/583580

Vert.x

1. http://vertx.io

2. http://helloworld.naver.com/helloworld/163784

3. http://bcho.tistory.com/860

Posted by devop