2016년 5월 25일 수요일

Vert.x 3.0.0 시작해보기...(3)

제가 요즘 요긴하게 써먹고 있는 Vert.x 을 소개하고자 합니다. 폴리글랏이긴 한데, 저는 Java 만을 사용해서 개발하고 있습니다. 그래서 Java 코드만 올리겠습니다.

장문의 글을 올리기 보다는, 바로 어떤 결과가 나오는 짧은 팁만 올리고, 댓글로 보충 내용을 올리는 쪽으로 해보겠습니다. 방송 형태가 적합하겠지만, 할 줄을 몰라서...(컴맹입니다)

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

사실 꽤나 글을 새로 못올리고 있었는데, 찾는 분이 없는걸 보니...재미 없기만 한 내용인건 사실인가 봅니다. 그래도 칼을 빼들었으니...5회 정도까지는 글을 적어볼까 합니다.

이번에는, 드디어 Vert.x 의 꽃이라고 할 수 있는 EventBus 를 다뤄볼까 합니다.



EventBus 을 가장 쉽게 이해할 수 있는 방법은...Vert.x 을 위한 비동기 Method...정도로 정의를 내릴 수 있겠네요. 실제 Method 처럼 선언을 하고 사용을 합니다. 단지...좀 사용상의 제약이 있고 비동기로 처리를 한다는 정도의 차이가 있겠네요.

백문이 불여일타...코드를 보면서 설명을 드리겠습니다.

EventBus eventBus = vertx.eventBus();

eventBus.consumer("Test.Login", (Message<JsonObject> message) -> {

 JsonObject param = message.body();

 System.out.println(param.getString("METHOD") + " Start!!!");

 message.reply(param.getString("METHOD") + " End");

});

먼저 EventBus 을 사용하기 위해서는 vertx 개체로부터 eventBus() 을 이용해 개체를 얻어와야 합니다. 그리고 Method 선언과 마찬가지의 작업인 consumer() 을 통해 하나의 EventBus 을 등록하게 됩니다.
첫번째 매개변수는 Method 의 이름과 같은...이름, 그리고 다음 매개변수는 실제 코드 부분입니다. Message 개체 안에 매개변수를 하나의 Bean 처럼 담아서 가져올 수 있는데, 하나의 개체만 받아올 수 있기 때문에 보통 JsonObject 에 필요한 정보를 입력해서 보냅니다. 이렇게 받아온 Message 개체는 body() 을 이용해서 실제 값을 받아올 수 있습니다.

그리고 return 대신에 Message 개체의 reply() 을 통해 호출한 쪽에 결과를 전송할 수 있습니다.



그럼 이번에는 실제 호출 부분을 보겠습니다.

eventBus.send("Test." + method, param, (AsyncResult<Message<String>> result) -> {

 if (result.succeeded()) {

  String returnValue = result.result().body();

  request.response().putHeader("content-type", "application/json");
  request.response().end("{\"METHOD\": \"" + method + "\", \"RESULT\": \"" + returnValue + "\"}");

 } else {

  request.response().putHeader("content-type", "application/json");
  request.response().end("{\"METHOD\": \"" + method + "\", \"ERROR\": \"" + result.cause().getMessage() + "\"}");
  result.cause().printStackTrace();

 }

});

예전 코드에서 조금 확장을 해봤는데, 저는 주로 호출된 URL 을 이용해서 consumer() 로 선언된 EventBus 을 자동으로 호출하고, 결과를 분기해서 처리하는 방법으로 개발합니다. 생각하지 않고 그냥 이렇게 만들어두는게 개인적으로는 편하더군요.

위의 코드들이 모두 적용된 소스는 아래 깃헙에 올라가 있습니다.

이 코드를 실행해 보시면, http://localhost:8080/Login  와 같이 호출을 할 경우 Test.Login 을 호출해서 콘솔에 메세지를 출력하고, 사용자의 브라우저에는 METHOD 와 RESULT 키가 포함된 JSON 구문이 출력될 것입니다. 물론 METHOD 부분의 글자를 조금 바꿔서 보내면...RESULT 대신에 ERROR 가 포함된 JSON 구문이 출력될 것입니다. 아쉽게도 에러 메세지는 부정확하거나 null 이 포함되어 있을 확률이 큽니다. 아직 Vert.x 의 라이브러리의 완성도가 높은 편은 아니거든요. (그렇다고 실제 서비스에 사용하지 못할 정도의 버그가 있는 건 아닙니다. 완성도가 좀 낮을 뿐...)

다음으로 아래 예제를 통해 node.js 와 마찬가지로 Vert.x 가 가지는 특징을 알아볼 수 있습니다.

Sleep 이라는 METHOD 가 추가되었는데, 실제 두 개의 브라우저를 실행한 뒤, Sleep 을 연달아 각각 호출해보면, 첫 Sleep 의 응답이 10 초 걸릴 동안, 두번째 Sleep 의 호출이 10 + a 초가 걸린다는 것을 알 수 있을 겁니다. a 초는 첫번째 호출 후 두번째 호출을 누를 때까지의 시간을 10초에서 뺀 시간입니다. 같은 EventBus 는 하나의 처리가 끝날 때까지 다음 처리를 기다려야 한다는 조건이 있기 때문입니다. 그리고, 비동기 처리에 익숙하지 않은 분을 위한 또 하나의 팁. 86 라인의 "Send End!!!" 는 첫번째 호출 시 즉시 출력이 됩니다. EventBus 을 호출한 뒤 결과 반환을 기다리지 않고 바로 다음 문장을 실행하는 비동기 프로그래밍의 전형적인 특징입니다. 역시나 node.js 나 javascript 의 callback 등을 많이 다뤄본 분들이라면 너무나 당연한 내용을 것입니다.

문제는 이 파일에서 Sleep 을 실행한 뒤 Login 을 실행해보면, 의도와 다르게 Sleep 을 두 번 실행했을 때와 마찬가지로 앞의 Sleep 이 끝나기 전까지 Login 이 처리되지 않는 것을 알 수 있습니다.

이를 해결하기 위해서는 Verticle 이라는 것을 쪼개서 처리해야 합니다. Test3_2.java 역시 하나의 Verticle 이기 때문에 이를 나누는 것이죠.

Test3_3.java 파일이 기본 파일이고, Test3_4.java, Test3_5.java 파일은 일종의 include 된 파일이라고 생각하셔도 좋습니다. 단지 include 을 하는 것과 달리 별도의 Verticle 로 돌아가기 때문에 Thread 을 쓰는 것과 같은 효과를 볼 수 있습니다.
Test3_3.java 에서 다른 두 파일을 선언하는 것을 Deploy 라고 하고, vertx.deployVerticle() 로 작업을 수행할 수 있습니다.

vertx.deployVerticle("Test3_4.java");

vertx.deployVerticle("Test3_5.java");

그리고 Method 선언과 동일하다고 말씀드린 .consumer() 을 각 파일로 이동시키는 것입니다.

그리고 vertx run Test3_3.java 을 실행한 뒤 Sleep 을 실행한 뒤 바로 Login 을 실행하면 Sleep 와 상관없이 Login 이 바로 실행되는 것을 알 수 있습니다.



그런데, Sleep 을 실행해보면 이상한 메세지들이 출력되는 것을 보실 수 있습니다. 대충 내용을 봐도 뭔가 시간이 오래 걸린다고 불평을 하는 것이라는 걸 알 수 있습니다. Vert.x 의 EventBus 는 하나의 처리에 1 초가 넘어가게 될 경우 메세지가 콘솔로 출력됩니다. 이렇게 장시간의 처리가 필요한 것은 적합하지 않기 때문에 개선을 하라는 의미입니다. 하지만, 또다른 방법으로 오랜 시간이 걸리는 작업은 EventBus 가 아닌 별도로 준비된 Worker Verticle 을 이용해서 처리하면 메세지 출력 없이 처리가 됩니다.

이를 위해 vertx.deployVerticle() 시 두번째 매개변수로 DeploymentOptions 개체를 통해 옵션값을 지정해줘야 합니다.

DeploymentOptions options = new DeploymentOptions().setWorker(true);
vertx.deployVerticle("Test3_5.java", options);


그리고, 하나의 Verticle 은 하나의 Instance 만 생성해서 처리하기 때문에 위의 예제에서는 Sleep 이 하나가 처리되기 전까지 다음 Sleep 은 앞의 Sleep 이 끝날 때까지 기다려야 하는 문제가 발생합니다. node.js 는 이를 여러개를 직접 실행하는 방법으로 약간은 무식(?)하게 해결하곤 하는데, Vert.x 에서는 그냥 Instance 수를 여러개 지정해서 띄워버리면 동시 처리개수를 늘려버릴 수 있습니다. 이는 JVM 을 사용하기 때문에 가질 수 있는 이점이기도 합니다

DeploymentOptions options = new DeploymentOptions().setWorker(true).setInstances(2);
vertx.deployVerticle("Test3_5.java", options);


이번에는 Vert.x 의 핵심이라 볼 수 있는 EventBus 의 기본적인 사용법과 간단한 특성을 잠깐 살펴봤습니다. 저번 글을 적고 난 뒤에 코드가 좀 길어질 것 같고 실수하는게 많아져서 아예 깃헙에 코드를 올리는 쪽으로 생각을 바꿨습니다. 설명이 자세하지 않더라도 소스를 실행해보시면 크게 어렵지는 않으실 겁니다. 질문 사항이 있으시면 언제든지 댓글로 올려주십시오.

오늘은 여기까지...





이 글은 제 개인 블로그(http://zepinos.blogspot.kr)와 okky(http://okky.kr)에만 공개되는 글입니다. 퍼 가는 것은 금해주시고, 링크로 대신해주시기 바랍니다. 당연히 상업적 용도로 이용하시면...저랑 경찰서에서 정모하셔야 합니다. ^^;;;

위에 작성한 코드 등은 실제 컴파일한 것이 아니라 제가 글을 적으면서 키보드 코딩(?...손 코딩의 친구) 한 것이므로, 오류가 있다면 저에게 알려주시면 고맙겠습니다.

댓글 2개:

  1. DeploymentOptions options = new DeploymentOptions().setWorker(true).setInstances(2);
    vertx.deployVerticle("Test3_5.java", options);

    를 해줬는데 브라우저 2개로 테스트해보면

    동시처리가 안되고

    처음 브라우저 10초 기다린후 , 다음브라우저 10초 기다립니다..

    버전은 3.4.2 사용중 입니다

    답글삭제
    답글
    1. vert.x 안쓴지 몇 년 지나서...가물가물 한데요...잘 처리가 안된다면 해당 내용은 삭제하던지 하겠습니다.

      그런데, 브라우져 두 개를 켜놓고 호출하신 건가요? 인스턴스가 두 개 떠있는지 확인도 해보셨는지 궁금합니다.

      삭제