2016년 5월 25일 수요일

Vert.x 3.1.0 시작해보기...(6)

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

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

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

역시나 오랫만에 글을 올립니다. 요즘 좀 많이 게을러지기도 해서...

제목이 살짝 바뀌었습니다. 눈치채신 분 계신가요? 그 사이 Vert.x 의 버전이 3.0.0 에서 3.1.0 으로 올라갔습니다. 몇몇 변화점들이 눈에 띄는데, 그 중에서도 제가 구글 그룹스에 글을 남겼던 부분도 개선이 되었네요. (https://groups.google.com/forum/#!topic/vertx/-ZofvUfbzj8 )

하지만, 일반적인 사용에 있어서는 큰 변화가 없으니...제 앞의 게시물들은 여전히 변경 없이 사용 가능할 것입니다.



이번에 알아볼 것은 config 파일와 LocalMap 입니다.



먼저, config 은 vertx run 을 통해 실행될 때 설정 파일을 읽어오는 기능입니다. Vert.x 는 JSON 을 이용해서 데이터들을 주고 받는 것을 기본으로 하고 있어서인지, 설정 파일 역시 JSON 파일로 읽어올 수 있게 개발되어 있습니다. 그냥 JSON 이 들어있는 파일을 실행 시 옵션으로 가리키면 그 파일을 읽어오는 것이죠.

가령 예를 들면 아래와 같습니다.

vertx run Test6_1.java -conf config.json

-conf 에 파일 경로와 이름을 적어주면 해당 파일을 읽어들입니다. 지금은 java 파일과 같은 위치에 존재하기 때문에 특별히 경로를 적지 않았습니다만, 개발 PC 와 서버마다 VCS 외부에 파일을 위치시켜두고 실행을 배치 파일로 하도록 만들어서 각각의 config 파일을 읽게 해주면 Spring 의 profile 와 같은 역할을 할 수 있습니다. JSON 형태를 읽기 때문에 좀 더 강력하다고 할까요...

위와 같이 실행될 경우 Test6_1.java 는 start() 안에서 context.config() 으로 읽어올 수 있습니다.

하지만, 주의할 것은 이 설정 파일은 Test6_1.java 파일에서만 읽을 수 있으며, Test6_1.java 파일에서 deploy 한 다른 파일에서는 context.config() 으로 받을 수 없다는 것입니다. 대신에, deployVerticle() 시 옵션을 통해 config JSON 을 전달할 수 있습니다(물론 중간에 가공한 뒤 보낼 수도 있겠죠). 아래와 같이 말이죠.

{
  "TestMessage": "Hello World"
}

public class Test6_1 extends AbstractVerticle {

 @Override
 public void start() throws Exception {

  super.start();

  EventBus eventBus = vertx.eventBus();
  JsonObject config = context.config();

  DeploymentOptions options = new DeploymentOptions().setConfig(config);

  vertx.deployVerticle("Test6_2.java", options);
  vertx.deployVerticle("Test6_3.java", options);

앞 글에서 살펴본 DeploymentOptions 을 통해서 여러 설정을 할 수 있는데, 그 중에 config 파일을 담아서 보내는 기능도 있습니다.

그럼, 다른 Verticle 에서 잘 받는지 구문을 만들어봐야겠죠? 아래와 같이 만들어봤습니다.

public class Test6_2 extends AbstractVerticle {

 @Override
 public void start() throws Exception {

  super.start();

  EventBus eventBus = vertx.eventBus();
  JsonObject config = context.config();

  eventBus.consumer("Test6_2", (Message<Object> message) -> {

   String testMessage = config.getString("TestMessage");

실제로 Test6_1~Test6_3 까지 받으신 후 실행하고서, http://localhost:8080/Test6_2  라고 호출하면 브라우저에 config 안에 있던 "Hello World" 가 출력됨을 아실 수 있을 겁니다.



이번에는 LocalMap 입니다. Vert.x 2.x 에서는 SharedMap 이라고만 있었는데, Vert.x 3 가 나오면서 LocalMap 와 ClusterWideMap 으로 나뉘어졌습니다. 예전의 SharedMap 은 LocalMap 와 동일한 것입니다. Embeded 모드에선 ClusterWideMap 이 의미가 없지만, 실행 모드에서 Cluster 구성을 한 뒤에는 차이가 존재합니다. Vert.x 는 Hazelcast 을 통해 Cluster 을 제공하는데, EventBus 을 여러 instance 의 것을 분산해서 사용할 수 있는 형태입니다. 문제는 다른 JVM 이다보니 일반적인 Object 로는 값의 동기화가 안되기 때문에 Hazelcast 의 공유 Object 을 이용하는데, Vert.x 는 이것을 ClusterWideMap 을 통해서 제공합니다. LocalMap 은 Instance 마다 개별적으로 분리되기 때문에 Cluster 환경에선 LocalMap 와 ClusterWideMap 의 사용에 신중을 기해야 합니다. 그리고, ClusterWideMap 은 반드시 비동기 형태로만 작성되는 것도 유의해야 하구요(Hazelcast 는 아닌데...).

LocalMap 은 다음과 같이 String 기반으로 개체를 구분해서 사용할 수 있습니다. 다음 예제는 다른 Verticle 에서 선언한 LocalMap 을 공유해서 쓰는 것을 보여줍니다.

public class Test6_2 extends AbstractVerticle {

 @Override
 public void start() throws Exception {

  super.start();

  EventBus eventBus = vertx.eventBus();
  JsonObject config = context.config();

  eventBus.consumer("Test6_2", (Message<Object> message) -> {

   String testMessage = config.getString("TestMessage");

   LocalMap<String, String> map = vertx.sharedData().getLocalMap("Test6.LocalMap");

   String localMapValue = map.get("LocalMapKey");

   System.out.println("LocalMapValue : " + localMapValue);

   String sCount = map.get("Count");

   if (sCount == null) {

    sCount = "0";

   }

   int count = Integer.parseInt(sCount) + 1;

   map.put("Count", Integer.toString(count));

   localMapValue = "Count" + count;

   map.put("LocalMapKey", localMapValue);

   message.reply(testMessage);

  });

 }

위의 config 와 같은 파일에 구현을 했는데, config 의 결과는 웹브라우져에 결과가 전송되도록 되어 있고, 이번 예제는 서버 측 콘솔에 결과가 출력되도록 되어 있습니다. 처음 Test6_2 을 호출하게 되면 콘솔에 LocalMapValue : null 가 출력됩니다. 하지만 다음 요청부터는 Test6_2 을 호출하든, Test6_3 을 호출하든 LocalMapValue : Count1 와 같이 개체에 값이 추가된 것을 확인할 수 있습니다. 이렇게 값을 공유할 수 있다는 것이죠.



오늘은 여기까지...



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

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

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

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

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

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

오랫만에 글을 올리네요. 하루에 한 개씩 올릴 수 있을 줄 알았으나, 게으름을 이기지 못하네요.

이번에는 Vert.x 의 Verticle 이 가지는 아주 기초적인 특성 하나만 알아볼 것입니다. 바로 독립적으로 돌아가는 특성입니다.

먼저, 가장 기본적인 Java 에서의 싱글톤(Singleton)을 한 번 구성해봤습니다. 너무 쉬운 내용이지만, 다음 내용 설명을 위해서 기초적인 형태로 구성한 점, 양해 바랍니다.

public class Test5_1 {

 private Test5_1() {

 }

 private static Test5_1 instance;

 public static Test5_1 getInstance() {

  if (instance == null) {

   System.out.println("Create Instance Test5_1");

   instance = new Test5_1();

  }

  return instance;

 }

}

정말 단순한 형태의 싱글톤입니다. 단지, 클래스가 생성될 때 인스턴스를 생성하게 하지 않고, 아직 인스턴스가 null 인지 확인한 뒤 생성해줄 때 로그를 한 번 출력해주고(생성되는 횟수도 알겸) 개체를 생성한 다음 결과로 인스턴스를 넘겨주도록 구성했습니다.

그리고 이걸 불러서 호출하는 클래스를 2 개 만들어 봤습니다.

import java.util.Random;

public class Test5_2 {

 public static void main(String[] args) throws Exception {

  System.out.println("Start Test5_2");

  Test5_2 test5_2 = new Test5_2();
  Test5_3 test5_3 = new Test5_3();

  Random random = new Random(System.currentTimeMillis());

  for (int iCount = 0; iCount < 10; iCount++) {

   if (random.nextInt(2) == 0) {

    if (random.nextInt(2) == 0) {

     test5_2.test1();

    } else {

     test5_2.test2();

    }

   } else {

    if (random.nextInt(2) == 0) {

     test5_3.test3();

    } else {

     test5_3.test4();

    }

   }

  }

  System.out.println("End Test5_2");

 }

 public void test1() {

  Test5_1 test5_1 = Test5_1.getInstance();

  System.out.println("Get Instance Test5_1 : test1");

 }

 public void test2() {

  Test5_1 test5_1 = Test5_1.getInstance();

  System.out.println("Get Instance Test5_1 : test2");

 }

}

public class Test5_3 {

 public void test3() {

  Test5_1 test5_1 = Test5_1.getInstance();

  System.out.println("Get Instance Test5_1 : test3");

 }

 public void test4() {

  Test5_1 test5_1 = Test5_1.getInstance();

  System.out.println("Get Instance Test5_1 : test4");

 }

}

main 은 Test5_2 에 있고, main 이 호출되면 Random 을 이용해서 임의적으로 4 개의 매서드 중 하나를 호출해서 인스턴스를 가져오는 아주 간단한 예제입니다.

출력 결과는 다음과 같습니다.

Start Test5_2
Create Instance Test5_1
Get Instance Test5_1 : test3
Get Instance Test5_1 : test1
Get Instance Test5_1 : test2
Get Instance Test5_1 : test1
Get Instance Test5_1 : test2
Get Instance Test5_1 : test4
Get Instance Test5_1 : test1
Get Instance Test5_1 : test3
Get Instance Test5_1 : test2
Get Instance Test5_1 : test2
End Test5_2

물론 출력되는 Get Instance Test5_1 : 이후의 내용은 Random 하게 바뀌겠지만, Start 후 Create 가 한 번만 나오고 End 가 마지막에 나오는 것은 누구나 예측할 수 있을 것입니다. 이게 싱클톤이니까요. 보통 이런 싱글톤은 JDBC 을 이용해서 DB Connection 을 가져오는 문장에서 많이들 사용하셨을 겁니다.

그런데, Vert.x 에서는 이렇게 싱글톤을 이용할 경우 큰일이 납니다. 다음 예제를 한 번 보시죠.

import io.vertx.core.AbstractVerticle;
import io.vertx.core.eventbus.EventBus;
import io.vertx.core.eventbus.Message;

import java.util.Random;

public class Test5_4 extends AbstractVerticle {

 @Override
 public void start() throws Exception {

  super.start();

  vertx.deployVerticle("Test5_5.java");

  EventBus eventBus = vertx.eventBus();

  eventBus.consumer("Test5_4.test1", (Message<Object> message) -> {

   Test5_1 test5_1 = Test5_1.getInstance();

   System.out.println("Get Instance Test5_1 : test1");

   message.reply(null);

  });

  eventBus.consumer("Test5_4.test2", (Message<Object> message) -> {

   Test5_1 test5_1 = Test5_1.getInstance();

   System.out.println("Get Instance Test5_1 : test2");

   message.reply(null);

  });

  Random random = new Random(System.currentTimeMillis());

  for (int iCount = 0; iCount < 10; iCount++) {

   if (random.nextInt(2) == 0) {

    if (random.nextInt(2) == 0) {

     eventBus.send("Test5_4.test1", null);

    } else {

     eventBus.send("Test5_4.test2", null);

    }

   } else {

    if (random.nextInt(2) == 0) {

     eventBus.send("Test5_5.test3", null);

    } else {

     eventBus.send("Test5_5.test4", null);

    }

   }

  }

 }

 @Override
 public void stop() throws Exception {

 }

}

import io.vertx.core.AbstractVerticle;
import io.vertx.core.eventbus.EventBus;
import io.vertx.core.eventbus.Message;

public class Test5_5 extends AbstractVerticle {

 @Override
 public void start() throws Exception {

  super.start();

  EventBus eventBus = vertx.eventBus();

  eventBus.consumer("Test5_5.test3", (Message<Object> message) -> {

   Test5_1 test5_1 = Test5_1.getInstance();

   System.out.println("Get Instance Test5_1 : test3");

   message.reply(null);

  });

  eventBus.consumer("Test5_5.test4", (Message<Object> message) -> {

   Test5_1 test5_1 = Test5_1.getInstance();

   System.out.println("Get Instance Test5_1 : test4");

   message.reply(null);

  });

 }

 @Override
 public void stop() throws Exception {

 }

}

위 예제를 보시면 메서드를 EventBus 로 단순히 바꾼 형태라는 걸 눈치채실 겁니다. 이 코드를 이용해서 Test5_4.java 을 Vert.x 로 실행하면 어떤 결과가 나올까요? 대략 아래와 같은 결과가 나옵니다.

> vertx run Test5_4.java
Create Instance Test5_1
Get Instance Test5_1 : test3
Get Instance Test5_1 : test4
Get Instance Test5_1 : test4
Create Instance Test5_1
Get Instance Test5_1 : test1
Get Instance Test5_1 : test2
Get Instance Test5_1 : test1
Get Instance Test5_1 : test2
Get Instance Test5_1 : test1
Get Instance Test5_1 : test4
Get Instance Test5_1 : test3
Succeeded in deploying verticle

예상하신 분들은 예상하셨겠지만...Create 가 두 번 발생합니다.

만약 Verticle 이 10 개라면, 10 개의 Create 가 발생한다는 이야기입니다. 즉, 모든 Verticle 은 별개로 동작하고, 이걸 내부적으로 Hazelcast 로 통신하면서 값을 전달하기 때문에 기존의 Singleton 을 가지고 하나의 접속만을 유지하면서 사용한다는 개념으로 접근해버리면, 전혀 엉뚱한 결과에 멘탈이 붕괴되는 현상을 경험하실 겁니다.

그래서, DB 접속이든 쿼리를 실행하는 구문이든, Singleton 으로 Connection 개체를 받아와서 처리하는 형태로 구성해서는 안되고, 쿼리를 처리하는 하나의 버티클을 만들고 그 안에 쿼리를 처리하는 EventBus 을 만든 뒤, 모든 요청을 그 EventBus 로 보내서 처리하고 결과만 JsonObject 등으로 받아오는 형태로 프로그램을 작성해야 합니다. 그래서, 오히려 처리가 더 힘든 면이 있지 않나 싶네요.


오늘은 여기까지...


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

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