top bar

글 목록

2015년 11월 22일 일요일

[JAVA] Executable(or Runnable) JAR 파일 생성

개요


JAR(Java Archive, 자바 아카이브)는 소프트웨어에서 수많은 자바 클래스 파일과 연관 메타데이터, 리소스(텍스트, 그림 등)을 하나의 파일로 모아서 자바 플랫폼에 응용 어플리케이션이나 라이브러리를 배포하기 위한 패키지 파일 포맷이다.(출처: 위키 백과)

하지만 'main' 메소드를 구동하여 하나의 자바 어플리케이션 으로서 동작하게 하려면 특별한 설정이 필요하다. 이제 자바 'Manifest' 파일을 통해 'Runnable JAR' 파일을 생성하는 법을 알아 보고자 한다.


JAR 파일 생성


먼저 아래와 같은 기본적인 'HelloWorld' 클래스를 컴파일하고, 'jar' 파일을 생성해보자.
package test;

public class HelloWorld {

    public static void main(String[] args) {
        System.out.println("Hello world!");
    }
}
컴파일한다
> javac -d . HelloWorld.java
시험삼아 구동해보자
> java test.HelloWorld
Hello world!
이제 jar 파일을 생성해본다. 이는 jdk bin 폴더 아래에 있는 'jar' 실행파일을 사용한다.
환경변수를 설정했다면 어디서든 'jar' 명령어를 사용 할 수 있다.
> jar -cf helloworld.jar test\*.class
참고로 아무것도 없이 'jar' 명령어만 치면 usage 목록이 쫙 나오고 각 옵션에 대한 설명도 나온다. 일단 위에서는 'c'와 'f' 옵션을 사용했는데, 직관적으로 보이겠지만 'c'는 새로운 아카이브 파일을 생성하는 것이고 'f'는 파일의 이름을 지정하는 것이다.

'-cf' 옵션뒤에 첫번째 파라미터는 jar파일의 이름, 둘째는 아카이빙될 .class 파일을 입력하는데, 여기서는 와일드카드(*)를 사용하였다.

이제 생성된 jar파일을 구동해볼까나?
> java -jar helloworld.jar
helloworld.jar에 기본 Manifest 속성이 없습니다.
이것이 필자가 이 글을 쓰고 있는 이유이다. 실무에서도 실행가능한 jar파일을 생성하지 못하여 위와 같은 메시지를 자주 접했었다. 이제 JAVA Manifest 파일을 통해서 실행가능한 jar파일을 생성해 보도록 하자.


JAVA Manifest 파일


JAVA Manifest 파일은 해당 자바 어플리케이션의 정보 즉, 일종의 메타정보를 담고 있는 파일이다. 아래에 가면 아주 자세한 Tutorial을 볼 수 있다.

https://docs.oracle.com/javase/tutorial/deployment/jar/manifestindex.html

일단 jar 명령어로 jar파일을 생성할때, META-INF/MANIFEST.MF 파일이 자동으로 jar 패키지에 포함된다. 일단 위에서 생성된 'helloworld.jar' 파일을 jar 명령어로 압축을 풀어보자.
> jar -xvf helloworld.jar
생성됨: META-INF/
증가됨: META-INF/MANIFEST.MF
증가됨: test/HelloWorld.class
위에서 볼 수 있는 것처럼 MANIFEST.MF 파일이 포함되어 패키지가 구성되어 있다는 것을 알 수 있다. 자동적으로 생성되는 default MANIFEST.MF 파일의 내용은 아래와 같다.
> type META-INF\MANIFEST.MF
Manifest-Version: 1.0
Created-By: 1.7.0_67 (Oracle Corporation)
Manifest-Version과 파일을 생성한 주체가 표시 되어 있다. 위 예에서 볼수 있듯이 Manifest파일의 기본 포맷은 'key: value' 의 포맷이다.

자동적으로 Manifest 파일이 생성되어 패키지에 포함되어 있는데 왜 기본 Manifest 속성이 없습니다. 라는 오류 메시지가 노출된 것일까?


Entry Point 설정


기본적으로 JAVA 어플리케이션은 'main' 메소드로 구동되는건 알 것이다. 이를 'Entry Point' 라고도 하나보다. 이제 Manifest 파일에 'Entry Point'를 설정해보자.

아래와 같은 내용으로 'Manifest.txt'를 생성한다
Main-Class: test.HelloWorld
그냥 한줄만 입력했다. 말그대로 'main' 메소드를 구동시키는 'Main-Class'를 지정한 것이다. 간단하지 않은가? 이 Manifest.txt 파일을 이용해 다시한번 jar 파일을 생성해보자.
> jar -cfm helloworld.jar Manifest.txt test\*.class
최초 '-cf' 옵션만을 사용했었는데, 여기다 'm' 옵션이 추가되었다. 말그대로 Manifest 파일을 포함해 jar파일을 생성하겠다는 의미 이다.

이제 다시 'helloworld.jar'파일을 구동해보자.
> java -jar helloworld.jar
Hello world!
이제 잘 구동된다.

하지만 Manifest 파일을 이용하지 않고도 Main-Class 지정, 즉 Entry Point를 설정하는 더 간단한 방법이 있다.
> jar -cfe helloworld.jar test.HelloWorld test\*.class
바로 위처럼 'e' 옵션을 사용하는 것이다. '-cfe' 옵션뒤에 jar 파일 이름, Main클래스 경로, jar 아카이빙될 클래스파일 경로를 지정한다. 위와 같이 하면 Manifest 파일을 사용하지 않고 'main' 메소드를 구동할 Main클래스를 지정할 수 있다!


Eclipse에서 'Runnable JAR' 생성하기


1) Project 우클릭 > Export






















2) JAVA > Runnable JAR file






















3) Launch Configuration 및 Export destination 선택 후 Finish























이때, Launch configuration은 해당 main 클래스의 실행 이력이 있다면 자동으로
생성되어 있을 것이다. 만약 select box에 아무것도 안뜬다면 이클립스의 'Run configuration' 에서 새로운 실행 설정을 추가해야 한다.


마치며..


이 topic에 대해서 포스팅을 하려고 마음먹었던 이유는 저 위의 기본 Manifest 속성이 없습니다. 와 같은 빌어먹을 오류 때문이었다. 보통 jar 파일 패키지 산출은 eclipse나 maven과 같은 빌드 도구가 알아서 해주다 보니, 저런 오류를 만나면 어디서부터 문제를 해결해야 할지 답답했다.

정말 요즘은 IDE나 갖가지 개발업무를 도와주는 Tool들 덕분에 개발자들이 해야할 일이 많이 줄어든건 사실이지만, 자신이 하는 개발업무 자체에 대한 깊이는 그만큼 얕아진것 같다. 물론 여기서 다루지 않은 JAVA 'Manifest' 파일의 스펙은 무척 다양하다.

하지만 오늘 포스팅을 하면서, 기본적으로 JAVA 어플리케이션이 어떻게 Main 메소드가 있는 클래스를 찾아서 프로그램을 실행 시키는지 이해 할 수 있었던 것 같다.

2015년 11월 13일 금요일

[JAVA] 우선순위(PriorityQueue) 큐

1. 개요


일반적으로 Queue라는 자료구조는 '선입선출'(First-In, First-Out)의 대기열 규칙(queuing discipline)을 가지고 있다. 말그대로 먼저들어온 놈이 먼저 나간다는 것이다.

하지만 JAVA에서 제공하는 'PriorityQueue'는 우선순위를 결정하여 들어온 순서와 상관없이 그 우선순위가 높은 엘리먼트가 나가게 된다. 이제부터 간단한 예제를 통해 알아보겠다.


2. 예제


아래와 같은 Prisoner(죄수) 클래스가 있다.
class Prisoner {

    String name;
    int weight; // 형량

    public Prisoner(String name, int weight) {
        super();
        this.name = name;
        this.weight = weight;
    }
}
이 클래스는 'name' 와 'weight(형량)' 의 2가지 필드가 있다. 이 Prisoner 클래스를 PriorityQueue에 넣고, 형량에 따라 큐에서 나오게(출소하게) 하려고 한다.

물론 일반적으로, 형량이 낮으면 먼저 출소하는 것이 인지상정. 이제 이 Prisoner 클래스에 Comparable 인터페이스를 구현 해보자.
class Prisoner implements Comparable<Prisoner> {

    String name;
    int weight; // 형량

    public Prisoner(String name, int weight) {
        super();
        this.name = name;
        this.weight = weight;
    }

    @Override
    public int compareTo(Prisoner target) {
        if (this.weight > target.weight) {
            return 1;
        } else if (this.weight < target.weight) {
            return -1;
        }
        return 0;
    }
}
위에 'Comparable' 인터페이스를 구현한 Prisoner 클래스에 있다. 형량이 낮은 Prisoner 객체를 먼저 꺼내기 위해 compareTo 메소드를 오름차순으로 정렬 되도록 구현하였다.

* PriorityQueue 사용

먼저 테스트 클래스에 'getPriorityQueue' 메소드를 구현한다. 이 메소드는 5개의 'Prisoner' 객체를 생성하여 'PriorityQueue'에 넣고 해당 'PriorityQueue' 객체를 반환한다.
private static PriorityQueue<Prisoner> getPriorityQueue() {

    Prisoner prisoner1 = new Prisoner("james", 5);
    Prisoner prisoner2 = new Prisoner("schofield", 99);
    Prisoner prisoner3 = new Prisoner("alex", 14);
    Prisoner prisoner4 = new Prisoner("silvia", 10);
    Prisoner prisoner5 = new Prisoner("thomson", 1);

    PriorityQueue<Prisoner> priorityQueue = new PriorityQueue<Prisoner>();

    priorityQueue.offer(prisoner1);
    priorityQueue.offer(prisoner2);
    priorityQueue.offer(prisoner3);
    priorityQueue.offer(prisoner4);
    priorityQueue.offer(prisoner5);
    
    return priorityQueue;
}
5명의 죄수 객체를 생성하여 우선순위 큐에 넣었다. 이 죄수 객체들은 각각 형량이 다르다.

만약 'Prisoner' 클래스에 Comparable 인터페이스를 구현하지 않은채 PriorityQueue에 집어넣으면 어떻게 될까? 우선순위 큐의 'offer'는 큐 한쪽 끝에 엘리먼트를 저장하는데, 이때 '다형성'을 이용하여 추가 되는 엘리먼트 객체를 'Comparable' 인터페이스로 'Up Casting' 한다. 하지만 'Comparable' 인터페이스를 구현한 객체가 아니라면 당연히 아래와 같은 에러메시지가 노출 될 것이다.
Exception in thread "main" java.lang.ClassCastException: Prisoner cannot be cast to java.lang.Comparable

이제 main 메소드를 구현하여 돌려보자.
public static void main(String[] args) {

        PriorityQueue<Prisoner> priorityQueue = getPriorityQueue();

        System.out.println("=============== Normal Order");

        while (!priorityQueue.isEmpty()) {
            Prisoner prisoner = priorityQueue.poll();
            System.out.println(prisoner.name);
        }
}
결과 >
=============== Normal Order
thomson
james
silvia
alex
schofield
형량이 낮은 'thomson' 이라는 녀석부터 출소한다. 형량이 '99년'인 schofield는 제일 나중에 나올 것이다.

이것이 우선순위 큐(PriorityQueue) 이다.

* Reversed Order

갑자기 교도소의 정책이 바뀌었다. 교도소장 생일을 맞이하여(.. 억지스럽더라고 걍 넘어가자) 형량이 가장 높은 죄수부터 차례로 출소하게 된것이다. 이럴때는 어떻게 해야할까? 'compareTo' 메소드를 오름차순에서 내림차순으로 바꿔서 구현해야 할까?

기존의 코드를 그대로 두고 Order를 뒤집는 방법이 있다. 아래와 같다.
public static void main(String[] args) {

        PriorityQueue<Prisoner> priorityQueue = getPriorityQueue();

        PriorityQueue<Prisoner> reversedPriorityQueue = 
         new PriorityQueue<Prisoner>(priorityQueue.size(), Collections.reverseOrder());
        reversedPriorityQueue.addAll(priorityQueue);

        System.out.println("=============== Reversed Order!");

        while (!reversedPriorityQueue.isEmpty()) {
            Prisoner prisoner = reversedPriorityQueue.poll();
            System.out.println(prisoner.name);
        }
}
결과 >
=============== Reversed Order!
schofield
alex
silvia
james
thomson
'Collections.reverseOrder()' 메소드를 살펴보면 알겠지만, 두 Camparable 객체를 비교하는 순서를 뒤집은 'Comparator' 객체를 리턴한다. 이로서 'reversedPriorityQueue' 컨테이너가 생성이 되고, 바로 위에 생성한 오름차순의 'priorityQueue' 컨테이너를 'addAll' 하게되면 'reversedPriorityQqueue'의 우선순위 정책으로 인해서 컨테이너 안의 엘리먼트의 우선순위가 뒤바뀐다.

그래서 위와 같은 결과를 도출 하는 것이다.

2015년 11월 11일 수요일

[MAVEN] Sonatype Nexus + Maven + Jenkins 배포환경 구성

1. 개요


 유지보수 업무를 하는 중, 여러 컴포넌트들이 공통적으로 쓰는 core 클래스들을 라이브러리로 묶어, 사내 maven repository에 등록하여 사용할 필요성을 느꼈다. 하지만 사내 repository에 jar를 등록하는 방법은 아이디를 발급받아야 하는 등 절차가 어려웠다.. 기보다는 귀찮았다. 그래서 직접 'sonatype nexus' 를 활용하여 maven repository를 구축해보기로 하였다.

Let the working begin~


2. Sonatype Nexus 설치


 이 작업은 소름돋을 정도로 쉽다. 예전엔 Nexus webapp을 다운받아, 따로 톰캣에 올려야했는데 요즘엔 jetty 기반의 WAS가 built-in 되어있는 컴포넌트로 릴리즈 되는것 같다.

아래 공식 웹사이트를 참고하자

http://www.sonatype.org/nexus/

참고로 nexus는 oss 와 pro 버전이 있는데, pro는 비용을 지불해야 하는 상용 버전이다. (pro도 기능에 따라 종류가 세분화 되는 듯 하다) 하지만 구지 pro 를 사용할 필요없이 oss 버전도 기본적인 repository management를 제공하기때문에 여기서는 oss 버전으로 진행한다.

1) download
$ wget --no-check-certificate https://sonatype-download.global.ssl.fastly.net/nexus/oss/nexus-2.11.1-01-bundle.tar.gz
wget으로 먼저 'tar' 파일을 다운받는다. 'https' 프로토콜 이기 때문에 '--no-check-certificate' 옵션을 잊지 말자

2) 압축해제
$ tar -xvzf nexus-2.11.1-01-bundle.tar.gz

3) 구동
bin]$ ./nexus start
Starting Nexus OSS...
Started Nexus OSS.

4) 접속

기본포트는 '8081'이며, /conf/nexus.properties 파일에서 변경할 수 있다
아래처럼 http://[server ip]:8081/nexus 로 접속한다

























5) 둘러보기

우측 상단에 'Log In' 버튼을 눌러 로그인할 수 있는데, 기본적으로 관리자 계정으로 'admin' (비번 admin123) 을 제공한다.

























왼쪽 메뉴의 'Repositories' 를 클릭하면 각종 저장소가 보인다.

저장소에는 3가지 유형이 있는데, 각각 아래와 같다.


  • 프록시 저장소 (proxy) : 프록시 저장소는 외부에 있는 메이븐 공개 저장소에 대한 프록시 역할을 하는 저장소이다.  위 사진에서는 'Central' 이라는 이름으로 메이븐 중앙 저장소가 이미 추가가 되어있으며, 대략적인 개념은 아래와 같다. 말그대로 '대리자'인 셈이다.








  • 호스티드 저장소(hosted) : 필자가 이 삽질을 하고 있는 이유다. 말그대로 사내에서 사용하는 라이브러리 관리 또는 3rd Party 라이브러리를 관리하기 위한 용도이다. 보면 알겠지만 기본적으로 'Release' , 'Snapshots' , '3rd party' 라는 저장소가 이미 추가되어있다.
  • 버추얼 저장소(virtual) : 넥서스에 이미 설정되어 있는 저장소에 대하여 다른 URL로 접근 할 수 있도록 지원하기 위한 논리적인 저장소이다.
  • 저장소 그룹(group) : 넥서스에 설정한 저장소의 그룹이다. 프로젝트가 진행되면서 의존 관계에 있는 라이브러리가 증가하면서 외부 저장소도 증가하는데, 이 저장소 그룹에다 추가되는 외부 저장소를 추가하면 메이븐 설정파일 변경 없이 의존 관계를 확장할 수 있다.

3. Maven 설정


이 작업 또한 매우 간단하다

1) .m2/settings.xml 파일 변경

아래처럼 <servers> 설정에 nexus 사용자 아이디와 비번을 설정한다.
      .
      .
      .
      <server>
            <id>releases</id>
            <username>admin</username>
            <password>admin123</password>
      </server>
</servers>

2) project pom.xml 파일 설정 추가
<distributionManagement>
        <repository>
            <id>releases</id>
            <url>http://[server ip]:8081/nexus/content/repositories/releases</url>
        </repository>
</distributionManagement>

이때 주의해야 할점은 settings.xml 파일에 설정한 <server><id> 와 pom.xml 에 설정한 <repository><id> 값이 일치해야 한다는 점이다.


4. Jenkins 설정 및 Nexus로 배포하기


1) Jenkins 설정

Jenkins에서 해당 프로젝트를 빌드할때 maven goal을 'deploy'로 설정한다. 아래와 같다.











'profile' 매개변수는 빌드 환경에따라 다른 저장소로 (release/alpha) 배포하기 위해 설정한다. 'deploy' 를 입력하면 deploy phase에 메이븐에 기본적으로 내장된 'maven-deploy-plugin' 이 수행되면서 <distributionManagement> 에 설정된 저장소로 배포하게 된다.

이때 profile 마다 <distributionManagement> 설정을 바꿔 다른 저장소로 배포하게 하면 개발 환경에 따라 저장소를 달리해서 구분하여 관리할 수 있다.

2) 배포 하기 

Jenkins Build를 수행한다. output console 로그를 보면 아래와 같이 해당 Nexus로 Uploaded 되었다는 내용이 출력 될 것이다.







3) Nexus에서 확인


해당 repository의 'Browse Index' 탭을 열어보면 방금 빌드한 라이브러리가 배포된 것을 볼 수 있다.

이것으로 완료 되었다!


2015년 10월 12일 월요일

[Performance] JVM Option을 활용한 heap dump 파일 추출. 'Eclipse Memory Analyzer' 로 분석하기

오늘은 자바 성능의 기본중의 기본, OOM에 대한 heap dump 파일을 떨구는 법과,
이를 Eclipse Memory Analyzer로 분석하는 방법을 포스팅하고자 한다.

1. 아래와 같은 코드를 작성
import java.util.ArrayList;
import java.util.List;

class HeavyInstance {
    char[] data = new char[10000000];
}

public class OOMTest {

    public static void main(String[] args) {

        List<HeavyInstance> dataList = new ArrayList<HeavyInstance>();

        while (true) {
            dataList.add(new HeavyInstance());
        }
    }
}
무조건 OutOfMemory 에러가 발생할 수밖에 없는 코드다.

2. 위 java application을 jar로 생성

Project > Export > Java > Runnable JAR file































Finish를 누르면 실행 가능한 jar 파일이 생성된다.

3. Jar 파일 실행

위 jar 파일을 실행하면 OOM이 발생하고, 아래와 같은 메시지와 함께 어플리케이션이 종료된다.
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at test.HeavyInstance.<init>(OOMTest.java:14)
    at test.OOMTest.main(OOMTest.java:30)
하지만 우리가 원하는 heap dump 파일을 생성하지는 못한다. 바로 아래와 같은 JVM 옵션을 주고 jar파일을 실행해야 heap dump 파일이 생성된다.
c:\> java -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=c:\dumps -jar oom-test.jar
위 옵션을 주면, HeapDumPath 옵션에 지정한 경로로 아래와 같이 '.hprof' 확장자를 가진 dump 파일이 생성된다.

















Confer.

jar파일을 생성하고 커맨드 라인에서 실행하기 귀찮다면, 아래 run configuration을 설정하여 JVM 옵션을 적용한 채로 이클립스에서 곧바로 실행 할 수 있다.

































4. Eclipse Memory Analyzer(이하 mat)로 분석하기

mat는 무료이며, 아래에서 다운 받을 수 있다.

https://eclipse.org/mat/

heap dump 파일(.hprof) 을 이용해 momory leak 등 메모리 성능 이슈를 찾기에 용이한, 공짜치고는 훌륭한 툴인 것 같다.

mat를 실행하고 아래와 같이 방금전 추출한 dump 파일을 open한다
























파일을 선택하면 3가지 옵션이 나오는데, 여기서는 Leak Suspects Report를 선택한다.
이는 자동적으로 dump 파일을 parsing하여 어떤 객체가 GC되지 않고 계속 살아있어 OOM의 원인이 되는지 분석해준다.






















Finish 를 누르면 아래와 같은 화면이 노출 된다.
























노란색 박스 아래쪽에 있는 'Details' 를 클릭하면, 생성된 instance의 갯수, 얼마나 heap 공간을 차지하는지 등, 상세한 정보가 나온다. 이를 통해 OOM의 원인을 분석할 수 있을 뿐만 아니라 잠재된 memory leak 이슈를 발견 할 수 있다.

5. 마치며..

'mat' 라는 툴은 앞에서도 말했지만 공짜치고는 상당히 디테일한 정보들을 제공 한다. 위의 예제에서는 아주 간단한 OOM 발생 어플리케이션의 예 였지만, 좀더 복잡한 자바 어플리케이션은 분석하기가 상당히 까다로울 것이다. 따라서 'mat' 툴에 대한 공부도 계속해서 해야 할것 같다....

2015년 9월 23일 수요일

[Performance] VisualVM + JMX 연동을 통한 tomcat cpu 사용량 모니터링

 지난 번에는 VisualVM과 Jstatd를 연동하여 remote jvm 을 모니터링하는 방법을 살펴보았다. 하지만 위 방법은 원격지 서버의 cpu 사용률까지 모니터링 하지는 못한다는 단점이 있었다.

이번 포스팅 에서는 'JMX'를 이용하여 원격지 jvm의 cpu사용량을 모니터링 해보도록 하겠다.

JMX란?



 JMX(Java Management Extensions)는 프로그래머들에게 자바 어플리케이션의 모니터링과 관리 기능을 제공한다. 실제로 이 API는 웹서버에서 네트워크 디바이스, 웹폰에 이르기까지 자바로 이용 가능한 것은 어느 것이든 로컬 혹은 원격으로 처리 할 수 있게 한다. JMX 기술은 JCP(Java Community Process)에 의해 개발된 밀접한 관계의 두 스펙, Java Specification Request (JSR) 3: Java Management Extensions (JMX) Specification 와 JSR 160: Java Management Extensions (JMX) Remote API 1.0에 의해 정의된다.


톰캣 구동 스크립트에 JVM 옵션 추가



매우 간단하다.

먼저 톰캣서버를 구동하기전에, bin/catalina.sh 쉘스크립트 파일에 아래와 같이 jvm 옵션을 추가해야 한다.
JAVA_OPTS="$JAVA_OPTS -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.port=[port] -Djava.rmi.server.hostname=[ip address]
해당 옵션을 추가한후에 톰캣을 구동한다.


VisualVM 에서 모니터링하기



1) Remote > Add Remote Host...















입력창이 뜨면 원격지의 IP를 입력한다

2) Add JMX Connection

















3) port 입력















jvm 옵션 추가시 입력했던 port를 입력한뒤 OK를 누르면 아래와 같이 생성된다


4) Sampler 탭에서 모니터링

이제 아래와 같이 원격지 tomcat 서버 전체의 cpu 사용량과 
각 클래스 및 메소드의 cpu사용량을 확인 할 수 있다!


2015년 9월 3일 목요일

[Elasticsearch] Logstash를 이용한 bulk 데이터 인덱싱

들어가며..



이전 포스트에서 Logstash와 Elasticsearch를 연동하여 인덱싱하는 방법을 살펴보았다. 참고

오늘은 bulk format의 데이터를 Logstash를 이용해 인덱싱하는 방법을 정리하겠다. 하지만 기본적으로 Logstash는 bulk format을 처리하지 못한다. 왜 그런지 아래 예제를 보며 살펴보겠다.

일단 로그스태쉬 configuration은 아래와 같이 작성한다.
input {
        file {
                codec => json
                path => "/home/asuraiv/logstash_data/*.json"
        }
}

output {
        elasticsearch {
                host => "localhost"
                index => "school"
                index_type => "students"
                protocol => "http"
                template_name => "school"
        }
        stdout {
                codec => rubydebug {
                        metadata => true
                }
        }
}
input plugin은 'file'을 사용하였다. 'path' 값으로 설정된 경로안에 json 확장자 파일이 저장되면 Logstash의 file input plugin이 자동으로 그 파일을 읽어 output으로 보내준다.

아래와 같은 bulk format을 저장하여 인덱싱을 시도해보자
{ "index" : { "_index" : "school", "_type" : "students", "_id" : "1" }}
{ "address" : "newyork", "name" : "james", "class" : "A" }
하지만 결과는 아래와 같다
{
         "index" => {
        "_index" => "school",
         "_type" => "students",
           "_id" => "1"
    },
      "@version" => "1",
    "@timestamp" => "2015-09-03T08:03:14.064Z",
          "host" => "cweb02.ami",
          "path" => "/home1/irteam/logstash_data/data.json",
     "@metadata" => {
        "retry_count" => 0
    }
}
{
       "address" => "los engeles",
          "name" => "kim",
         "class" => "C",
      "@version" => "1",
    "@timestamp" => "2015-09-03T08:03:14.065Z",
          "host" => "cweb02.ami",
          "path" => "/home1/irteam/logstash_data/data.json",
     "@metadata" => {
        "retry_count" => 0
    }
}
원래 bulk format은 첫번째줄이 meta정보, 두번째줄이 source 데이터이다. 따라서 2줄이 하나의 event로 인식되야 하는것인데 지금은 각 라인이 서로 다른 event로 인식되어 따로따로 인덱싱이 되었다.

bulk format에서 첫째줄 메타정보에 인덱싱을 수행할 인덱스, 타입, id값등을 설정할 수 있지만 위와같은 결과가 나오면 이 메타정보가 아무 소용이 없다. 두번째 줄의 데이터가 인덱싱이 된것도 Logstash configuration에 'index'와 'index_type'이 명시되어있기 때문에 정상적으로 인덱싱이 된것이다.

따라서 bulk format을 정상적으로 인지하면서, 메타정보에 맞게 유동적으로 index, type, id등을 설정하여 인덱싱을 하려면 어떻게 해야할까?

es_bulk codec



input 단계에서 bulk format을 파싱하여 메타데이터와 source데이터를 하나의 이벤트로 묶어주는 codec이 있다.

Logstash 설치 폴더의 'bin' 에서 아래와 같은 명령어로 설치가 가능하다 (Logstash 1.4+)
$ ./plugin install logstash-codec-es_bulk
그리고 configuration 파일의 input 설정부분에 아래와 같이 작성한다
input {
        file {
                path => "/home/asuraiv/logstash_data/*.json"
                codec => es_bulk {
                }
        }
}
위의 하이라이트된 설정을 추가한다. es_bulk 코덱안에는 별다른 설정이 없는 그냥 비어있는 중괄호 { } 이다.

이 코덱을 적용하면 bulk format의 메타데이터 부분은 '@metadata' 로 넣어주고, filter나 output plugin에서 "{[@metadata][_index]}" 와 같은 방식으로 접근할 수 있다. 따라서, output pulgin을 아래와 같이 작성하면 bulk format에 메타데이터에 따라 유동적으로 인덱싱할 인덱스, 타입, id를 지정할 수 있다
elasticsearch {
        host => "localhost"
        index => "%{[@metadata][_index]}"
        index_type => "%{[@metadata][_type]}"
        document_id => "%{[@metadata][_id]}"
        protocol => "http"
        template_name => "school"
}



2015년 9월 2일 수요일

[JAVA] Set

개요



Set은 각 객체 값에 대해 하나의 인스턴스만 저장한다. 즉, 동일한 객체의 인스턴스를 추가하려고 하면 중복을 막는다. Set의 가장 일반적인 용도는 특정 객체의 존재 유무를 확인하는 것이고 때문에 contain 메소드를 사용한 '검색'이 Set에서 가장 중요한 기능이다. 일반적으로 해싱함수를 사용한 HashSet이 가장 많이 쓰인다.


예제



아래는 Integer 객체를 저장하는 HashSet 사용 예이다.
Random rand = new Random(47);

Set<Integer> intset = new HashSet<Integer>();
for (int i = 0; i < 10000; i++) {
    intset.add(rand.nextInt(30));
}
System.out.println(intset);
> 결과
[0, 1, 3, 2, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 12, 15, 17, 16, 19, 18, 21, 20, 23, 22, 25, 24, 27, 26, 29, 28]
0부터 29 사이의 값에 대해서 10000개의 무작위 수가 Set에 추가되었지만 위에서 보는바와 같이 중복을 허용하지 않고 있다.

만약 정렬된 결과를 얻고 싶다면 TreeSet을 사용한다.
SortedSet<Integer> intset = new TreeSet<Integer>();

for (int i = 0; i < 10000; i++) {
    intset.add(rand.nextInt(30));
}
System.out.println(intset);
> 결과
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29]
TreeSet의 요소들은 red-black tree(2진 정렬 트리)의 구조로 데이터 정렬을 유지하여 위와같은 결과가 나온다

LinkedHashSet도 정렬을 유지한다. 하지만 정렬의 기준은 '추가된 순서' 이다. 아래와 같다.
LinkedHashSet<Integer> linkedHashSet = new LinkedHashSet<Integer>();
        
linkedHashSet.add(1);
linkedHashSet.add(10);
linkedHashSet.add(5);
linkedHashSet.add(3);
linkedHashSet.add(7);
        
System.out.println(linkedHashSet);  
> 결과
[1, 10, 5, 3, 7]


2015년 9월 1일 화요일

[JAVA] List - LinkedList

List의 구현체중 가장 많이 쓰이는 'ArrayList'에 대해서는 이전 포스트에 정리했다. 참고
오늘은 LinkedList에 대해서 정리할텐데, 이는 우리가 아주 잘 아는 자료구조 중 하나이다. 가장 일반적인 '단방향 LinkedList'의 경우, 하나의 노드의 포인터가 다음노드를 가리키는 식으로 되어있어 연결된(Linked) 구조를 갖는다.

예제 - LinkedList



(1) getFirtst , element , peek
LinkedList<Pet> pets = new LinkedList<Pet>(Pets.arrayList(5));

System.out.println(pets);   
    
System.out.println("pets.getFirst() : " + pets.getFirst());
System.out.println("pets.element() : " + pets.element());
System.out.println("pets.peek() : " + pets.peek());
> 결과
[Rat, Manx, Cymric, Mutt, Pug]
pets.getFirst() : Rat
pets.element() : Rat
pets.peek() : Rat
일단 이 세 메소드는 동일하다. 가장 첫번째 요소를 반환한다. 단지 element() 메소드는 getFirst() 메소드의 'alias' 일뿐이고, peek() 메소드는 리스트가 비어있을 때 null을 반환한다.

(2)  remove , removeFirst , poll
LinkedList<Pet> pets = new LinkedList<Pet>(Pets.arrayList(5));

System.out.println("pets.remove() : " + pets.remove());
System.out.println("pets.removeFirst() : " + pets.removeFirst());
        
System.out.println("pets : " + pets);

System.out.println("pets.poll() : " + pets.poll());
System.out.println("After pets.poll() : " + pets);
> 결과
[Rat, Manx, Cymric, Mutt, Pug]
pets.remove() : Rat
pets.removeFirst() : Manx
pets : [Cymric, Mutt, Pug]
pets.poll() : Cymric
After pets.poll() : [Mutt, Pug]
세 메소드 역시 동일하며 remove()는 removeFirst()의 alias이다. 가장 첫번째 요소를 삭제하면서 반환한다. poll() 메소드는 리스트가 비어있을때 null을 반환한다.

(3) addFirst , offer , add , addLast
LinkedList<Pet> pets = new LinkedList<Pet>(Pets.arrayList(5));
        
System.out.println(pets);

pets.addFirst(new Rat());       
System.out.println("After addFirst() : " + pets);
    
pets.offer(Pets.randomPet());   
System.out.println("After offer() : " + pets);
    
pets.add(Pets.randomPet()); 
System.out.println("After add() : " + pets);

pets.addLast(new Hamster());    
System.out.println("After addLast() : " + pets);
> 결과
[Rat, Manx, Cymric, Mutt, Pug]
After addFirst() : [Rat, Rat, Manx, Cymric, Mutt, Pug]
After offer() : [Rat, Rat, Manx, Cymric, Mutt, Pug, Cymric]
After add() : [Rat, Rat, Manx, Cymric, Mutt, Pug, Cymric, Pug]
After addLast() : [Rat, Rat, Manx, Cymric, Mutt, Pug, Cymric, Pug, Hamster]
요소를 추가하는 메소드들이다. addFirst() 는 리스트의 맨앞에 요소를 추가하며, 나머지 세 메소드 offer(), add(), addLast()는 리스트의 끝에 요소를 추가한다.

(4) remove
LinkedList<Pet> pets = new LinkedList<Pet>(Pets.arrayList(5));
        
System.out.println(pets);

System.out.println("pets.removeLast() : " + pets.removeLast());
System.out.println("After removeLast() : " + pets);
>결과
[Rat, Manx, Cymric, Mutt, Pug]
pets.removeLast() : Pug
After removeLast() : [Rat, Manx, Cymric, Mutt]
remove() 메소드는 리스트의 맨끝 요소를 반환하며 삭제한다.




2015년 8월 31일 월요일

[Performance] VisualVM + jstatd 연동을 통한 remote tomcat instance 모니터링

jstatd



 jstatd tool은 일종의 RMI server application이다. 원격지의 JVMs을 모니터링하거나, 원격지의 모니터링 툴이 로컬에 돌고있는 JVMs 에 접근할 수 있도록 해주는 인터페이스를 제공한다. 자세한 사항은 아래를 참고하자

http://docs.oracle.com/javase/7/docs/technotes/tools/share/jstatd.html


Steps



연동 절차는 의외로 간단하다.

1) 원격지의 tomcat이 구동되고있는 서버에서 jstatd를 실행한다
$ jstatd
하지만 다음과같은 에러가 떨어질 것이다.
Could not create remote object
access denied ("java.util.PropertyPermission" "java.rmi.server.ignoreSubClasses" "write")
java.security.AccessControlException: access denied ("java.util.PropertyPermission" "java.rmi.server.ignoreSubClasses" "write")
        at java.security.AccessControlContext.checkPermission(AccessControlContext.java:372)
        at java.security.AccessController.checkPermission(AccessController.java:559)
        at java.lang.SecurityManager.checkPermission(SecurityManager.java:549)
        at java.lang.System.setProperty(System.java:783)
        at sun.tools.jstatd.Jstatd.main(Jstatd.java:139)
따라서 'jstatd.all.policy' 라는 파일을 만들고 다음과 같은 내용을 입력한다
grant codebase "file:/home/asuraiv/apps/jdk/lib/tools.jar" {
        permission java.security.AllPermission;
};
해당 파일을 저장후 다시 아래와 같이 명령어를 입력하자.
$ jstatd -J-Djava.security.policy=/경로/jstatd.all.policy &

2) VisualVM 을 실행하여 Remote Host를 추가한다















위의 창에서 'Host name'을 해당 서버의 ip주소를 입력한후 'OK'를 누르면 아래와 같이 해당 서버에서 돌고있는 Java 인스턴스의 목록이 노출된다.



하지만 치명적인 단점이 있는데, 바로 cpu와 memory의 모니터링은 불가하다는 것이다.
이는 JMX를 사용함으로서 어느정도 극복할 수 있는데, 다음에 포스팅 해보겠다.


2015년 8월 27일 목요일

[MySQL] 실행계획 (Query Plan)

원문 - http://www.sitepoint.com/using-explain-to-write-better-mysql-queries/

개요



 MySQL 쿼리 옵티마이저는 쿼리를 실행할때 최적의 계획을 세운다. 그 계획을 Database용어로 '실행계획'(Query Plan)이라고 하는데, MySQL에서는 'EXPLAIN' 키워드를 이용해 실행계획에 대한 정보를 살펴 볼 수 있다.

 이는 이슈가 발생하는 문제의 쿼리를 이해하고, 어떻게 최적화 할지에대한 insight를 제공하는 매우 강력한 도구가 될 수 있는데, 불행하게도 이를 잘 사용하는 개발자는 별로 없는것 같다. (나 포함 ㅠ)


Understanding EXPLAIN's Output



'EXPLAIN' 을 사용하면 쿼리를 실행하기전에 실행계획을 분석하여 출력한다. 아래와 같은 예를 들어보자
EXPLAIN SELECT * FROM categories
********************** 1. row **********************
           id: 1
  select_type: SIMPLE
        table: categories
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 4
        Extra: 
1 row in set (0.00 sec)
이제부터 해당 항목들에대해 자세히 살펴보도록 한다


  • id : 쿼리 안에 있는 각 select 문에 대한 순차 식별자이다. 이순서대로 select문이 실행된다고 생각하면 된다.
  • select_type : select 문의 유형을 말한다. 각 유형은 아래와 같다
    • SIMPLE : 서브쿼리나 'union'이 없는 가장 단순한 select문을 말한다
    • PRIMARY : 가장 바깥에 있는 select 문을 말한다
    • DERIVED : form 문 안에있는 서브쿼리의 select 문이다.
    • SUBQUERY : 가장 바깥의 select 문에 있는 서브쿼리이다.
    • DEPENDENT SUBQUERY : 기본적으로 SUBQUERY와 같은 유형이며, 가장 바깥의 select문에 '의존성'을 가진 서브쿼리의 select문이다.
    • UNCACHEABLE SUBQUERY
    • UNION : union 문의 두번째 select 문을 말한다
    • DEPENDENT UNION : 바깥 쿼리에 의존성을 가진 union문의 두번째 select문을 말한다
  • table : 참조되는 테이블을 말한다
  • type : MySQL이 어떤식으로 테이블들을 조인하는지를 나타내는 항목이다. 이는 매우 중요한데, 이유는 이 타입을 분석함으로써 어떤 인덱스가 사용되고 사용되지 않았는지를 알 수 있고, 이를통해 어떤식으로 쿼리가 튜닝되어야하는지에 대한 insight를 제공하기 때문이다. 각 유형은 아래와 같다
    • system : 0개 또는 하나의 row를 가진 테이블이다.
    • const 
    • eq_ref : primary key나 unique not null column으로 생성된 인덱스를 사용해 조인을 하는 경우이다. const 방식 다음으로 빠른 방법이다.
    • ref : 인덱스로 지정된 컬럼끼리의 '=' , '<=>' 와 같은 연산자를 통한 비교로 수행되는 조인이다
    • index_merge 
    • unique_subquery : 오직 하나의 결과만을 반환하는 'IN'이 포함된 서브쿼리의 경우이다.
    • index_subquery : unique_subquery와 비슷하지만 여러개의 결과를 반환한다
    • range : 특정한 범위의 rows들을 매칭시키는데 인덱스가 사용된 경우이다. BETWEEN이나 IN, '>', '>=' 등이 사용될 때이다.
    • all : 조인시에 모든 테이블의 모든 row를 스캔하는경우이다. 물론 성능이 가장 좋지 않다.
  • possible_keys : 테이블에서 row를 매핑시키기 위해 사용 가능한 (사용하지 않더라도) 키를 보여준다.  
  • key : 실제적으로 쿼리 실행에 사용된 key의 목록이다. 이 항목에는 possible_keys 목록에 나타지 않은 인덱스도 포함 될 수 있다.
  • ref : key column에 지정된 인덱스와 비교되는 column 또는 constants를 보여준다.
  • rows : 결과 산출에 있어서 접근되는 record의 숫자이다. 조인문이나 서브쿼리 최적화에 있어서 중요한 항목이다.
  • Extra : 실행계획에 있어서 부가적인 정보를 보여준다.
    • http://dev.mysql.com/doc/refman/5.6/en/explain-output.html#explain-extra-information

2015년 8월 26일 수요일

[WebDev] $TOMCAT_HOME/work 디렉토리의 용도?

The work directory, as its name suggests, is where Tomcat writes any files that it needs during run time, such as the generated servlet code for JSPs, the class files for the same after they are compiled, the serialized sessions during restarts or shutdowns (SESSIONS.ser).

Also, if you have Tomcat configured not to unapack war files when deployed, your web app's directory structure will be created under the work directory instead of the "webapps" directory.

http://www.coderanch.com/t/87174/Tomcat/Purpose-work-directory-Tomcat

work 디렉토리는 그 이름에서 알 수 있듯이 runtime에 필요한 파일들, 예를들어 JSP로부터 변환된 서블릿코드(java파일), 또 그것을 compile하여 발생된 class파일 그리고 세션 파일들이 상주하는 공간이다.

따라서 실행시간에 다이나믹하게 class파일, 세션파일들이 저장된다.

또 한가지 흥미로운 사실은, server.xml 파일의 unpackWARs 속성과 autoDeploy 속성을 'false'로 해놓으면, 서버가 재시작할때 웹 어플리케이션은 'webapps' 디렉토리 대신에 'work' 디렉토리에 배포된다는 것이다.

아래는 unpackWARs 속성이 true 일때이다.
$ tree ./work/
./work/
└── Catalina
    └── localhost
        └── _
work 디렉토리 밑에 web resources는 생성되지 않고 jsp나 세션파일과 같은 캐시성 파일들이 실행시간에 저장될 공간만 생성되었다.

아래는 unpackWARs 속성이 false 일때이다. (autoDeploy속성도 false)

일단 webapps 디렉토리에 아래와 같이 war파일만 그대로 있고 배포되지 않았다
$ tree ./webapps/
./webapps/
└── ROOT.war
하지만 work 디렉토리에 아래와 같이 WEB-INF (web resources)가 배포된 것을 확인 할 수 있다.
$ tree ./work/
./work/
└── Catalina
    └── localhost
        └── _
            └── WEB-INF
                ├── classes
                │   ├── META-INF
                │   ├── applicationContext.xml
                │   ├── com
                │   │   └── ntscorp
                │   │       └── rnote
                │   │           ├── home
                │   │           │   └── controller
                │   │           │       └── HomeController.class
                │   │           ├── message
                │   │           │   ├── bo
                │   │           │   │   └── MessageBO.class
                │   │           │   ├── controller
                │   │           │   │   └── MessageController.class
                │   │           │   └── model
                │   │           │       └── MessageModel.class
                │   │           ├── mgr
                │   │           │   └── QueueManager.class
                │   │           ├── user
                │   │           │   └── controller
                │   │           │       └── UserController.class
                │   │           └── utils
                │   │               └── LoggedInUserManager.class
                │   └── log4j.xml
                └── lib
                    ├── amqp-client-3.3.5.jar
                    ├── aopalliance-1.0.jar
                    ├── aspectjrt-1.6.10.jar
                    ├── jackson-core-asl-1.9.13.jar
                    ├── jackson-mapper-asl-1.9.13.jar
                    ├── javax.inject-1.jar
                    .
                    .
                    .
                    .
클래스파일과 jar 라이브러리 파일등이 모두 배포되었다.

server.xml의 설정에 따라서 work디렉토리는 실행시간에 생성되는 파일들을 캐시해두는 용도, 또는 웹어플리케이션이 배포되는 용도로 쓰이는것이다



2015년 8월 12일 수요일

[Elasticsearch] NoNodeAvailableException: None of the configured nodes are available

Elasticsearch를 무리 없이 운영하다가 별안간 아래와 같은 Exception을 만나게 되었다.

아래는 서버 log 이다.
org.elasticsearch.client.transport.NoNodeAvailableException: None of the configured nodes are available: []
        at org.elasticsearch.client.transport.TransportClientNodesService.ensureNodesAreAvailable(TransportClientNodesService.java:298) ~[elasticsearch-1.3.2.jar:na]
위와 같은 Exception이 발생하며 쿼리가 되지 않았다.
설정된 노드중에 사용가능한 노드가 없다 라는 메시지 인것 같다

Elasticsearch log파일에는 아래와 같은 에러 메시지가 있었다.
[2015-08-13 11:33:02,020][WARN ][transport.netty          ] [es-node1] exception caught on transport layer [[id: 0x31ef8136, /10.112.133.167:44057 :> /10.112.133.166:9300]], closing connection
java.io.StreamCorruptedException: invalid internal transport message format, got (ff,ed,ff,fd)
아무튼 클러스터에 정상적으로 접근을 못하는 것 같다.

음.... 첫번째 로그 파일(서버로그)을 좀더 조금더 살펴보니, 이상한점을 발견했다.





뭔가 클러스터 이름에 엄청난 공백이 있었다... 알고보니..






JAVA client api를 사용해 커넥션을 생성할때, properties 파일에 있는 Elasticsearch 정보 속성값을 이용해 생성한다. 이때 클러스터이름에 위 properties 파일에서 보이는것처럼 공백이 포함된 클러스터이름으로 커넥션을 생성해 버렸고, 따라서 그 클러스터에 제대로 접근하지 못해 발생했던 이슈였던 것이다..

공백을 제거하니 정상적으로 커넥션이 생성되어 쿼리가 되었다.

2015년 8월 7일 금요일

[Elasticsearch] Logstash와 Elasticsearch의 연동

Elasticsearch output Plugin

Elasticsearch의 output 플러그인은 'node', 'transport', 'http' 의 3가지 프로토콜이 있다.
여기서는 Elasticsearch 클러스터의 9200번포트로 직접 접속하여 데이터를 전송하는 방식인 'http' 프로토콜을 통해서, Logstash와 연동해보도록 한다.


1. 기초

아래와 같은 클러스터 구조가 있다.









일단 간단하게 stdin 으로 json 데이터를 입력받고, Elasticsearch에 색인하는 작업부터 해보겠다.

logstash.conf 파일은 아래와 같이 작성한다.
input {
        stdin {
                codec => json
        }
}

output {
        elasticsearch {
                host => "localhost"
                index => "school"
                index_type => "students"
                protocol => "http"
        }
        stdout {
                codec => rubydebug { }
        }
}
위와 같이 output 설정에 elasticsearch { } 를 작성하고, 중괄호 안에 각종 설정을 세팅한다. protocol은 'http' 로 작성한다. 이 protocol을 생략하게되면 default 설정인 'node'로 세팅되는데, 이 프로토콜은 로그스태쉬를 하나의 node로 실행시켜, 색인작업을 하고자 하는 elasticsearch cluster에 합류시킨다.

일단 지금은 http 프로토콜을 사용한다 host, index, index_type 등의 설정은 localhost의 elasticsearch 서버로, shcool이라는 인덱스의 students 타입에 데이터를 색인 시키는 설정이다.

입력된 값들이 제대로 인식이 되었는지 확인하기 위해서, stdout 설정도 추가하였다.

실행시켜본다.
./logstash -f logstash.test.conf
Logstash startup completed
그리고 { "class" : "A", "name" : "john" } 를 입력해보자
{ "class" : "A", "name" : "john" } ## 입력
{ ## 여기서부터 stdout rubydebug codec에 의해 출력되는 부분.
         "class" => "A",
          "name" => "john",
      "@version" => "1",
    "@timestamp" => "2015-08-07T07:11:41.975Z",
          "host" => "cweb02.ami",
     "@metadata" => {
        "retry_count" => 0
    }
}
출력을 보니 제대로 색인이 된것 같다.
head 플러그인 화면으로 가서 확인해보자.

위 logstash.conf 파일에서 설정해 준 대로, 'school' 인덱스와 'students' 타입이 동적으로 생성되며, 색인이 성공했음을 확인 할 수 있다.


2. Template 사용

템플릿을 사용하여 미리 index와 type의 스키마를 정해놓고 데이터를 색인 할 수 있다.
아래와 같이 템플릿을 정의하자.
curl -XPUT 'localhost:9200/_template/school?pretty' -d '{
    "template" : "school",
    "settings" : { "index.refresh_interval" : "5s" },
    "mappings" : {
        "students" : {
            "properties" : {
                "class" : { "type" : "string", "store" : "yes", index : "not_analyzed" },
                "name" : { "type" : "string",  "store" : "yes" },
                "address" : { "type" : "string",  "store" : "yes" }
            }
        }
    }
}'
'students' 라는 인덱스 템플릿을 하나 생성하였다. 인덱스 템플릿에 관한 자세한 내용은 아래의 doc 문서를 확인해보자

https://www.elastic.co/guide/en/elasticsearch/reference/1.6/indices-templates.htm

템플릿을 생성하였으면, Logstash가 해당 템플릿을 사용하여 색인을 할 수 있도록 설정해 놓아야 한다. 바로 아래와 같이 설정한다.
elasticsearch {
                host => "localhost"
                index => "school"
                index_type => "students"
                protocol => "http"
                template_name => "school" # 바로 여기 설정
        }
}
자, 다시 Logstash를 실행시키고 { "class" : "A", "name" : "iron man", "address" : "newyork" } 와 같은 데이터를 입력한다
{ "class" : "A", "name" : "iron man", "address" : "newyork" }
{
         "class" => "A",
          "name" => "iron man",
       "address" => "newyork",
      "@version" => "1",
    "@timestamp" => "2015-08-07T07:50:33.537Z",
          "host" => "cweb02.ami",
     "@metadata" => {
        "retry_count" => 0
    }
}
템플릿대로 document가 색인되었는지 head플러그인에서 확인해보자.

아래는 템플릿을 사용하지 않았을때와 사용했을때의 인덱스 메타데이터의 차이이다.

1) 템플릿을 미적용한 경우





















Elasticsearch의 기본 idea는 realtime 으로 document를 색인하는것이다. 따라서 동적으로 인덱스와 타입이 생성된다. 위의 경우는 정해진 스키마없이 동적으로 인덱스와 타입이 생성된 경우이며 dynamic_templates 라는 기본 템플릿이 적용된다.

2) 템플릿을 적용한 경우





















위의 경우는 템플릿을 적용한 경우이다. 위에서 생성한 템플릿의 필드설정대로, 'class' 필드는 index 설정이 'not_analyzed'로 되어있다. 템플릿이 정상적으로 적용되어 데이터가 색인되었다는 증거다.

2015년 8월 5일 수요일

[Maven] 메이븐 모듈 (Module)

1. 개요



 프로젝트 진행시 초기에는 하나의 프로젝트만으로 개발하다가 규모가 커지거나 새로운 기능을 요구하는 경우에는 프로젝트를 분리하게 되는데, 이때 모듈 구조를 사용한다.

메이븐 모듈 방식을 사용하면 하나의 프로젝트가 어려개의 모듈을 가지고 여러개의 프로젝트(모듈)을 한번에 빌드할 수 있게 된다.


2. 개념



1) 상속

메이븐에서 모든 설정 파일은 최상위 pom.xml 파일을 상속하고 있다.
최상위 pom.xml 의 정보를 출력하기 위해서는 아래와 같은 명령어를 사용한다
$ mvn help:effective-pom
이렇게 기본으로 최상위 pom.xml 을 상속하듯이 프로젝트에서 공통으로 사용하는 설정은 공통 pom.xml 파일을 만들어 관리하고, 하위 모듈에서 이 pom.xml 파일을 상속 할 수 있다.
<!-- 부모 POM 파일 예제 -->
 
<project>
   <modelVersion>1.0.0</modelVersion>
   <groupId>com.mycorp</groupId>
   <artifactId>parent</artifactId>
   <packaging>pom</packaging>           <!-- packaging 엘리먼트 값을 'pom'으로 설정 -->
   <version>1.0-SNAPSHOT</version>
   .
   .
   .
이때 위의 설정처럼 부모 pom.xml 파일의 packaging 태그 값은  'pom'으로 설정 하여야한
다. 자식 pom.xml 파일은 <parent/> 엘리먼트를 이용하여 부모 pom.xml 파일을 상속한다
<!-- 자식 POM 파일 예제 -->
<project>
   .
   .
   <parent>
      <artifactId>parent</artifactId>
      <groupId>com.mycorp</groupId>    
      <version>1.0-SANPSHOT</version>
   </parent>
   . 
   .
</project>
모듈 구조는 일단 이러한 식으로 구성된다.

파일 tree로 보자면 일반적인 모듈 구조는 아래와 같다.
parent
├── batch
│   ├── src
│   └── pom.xml
├── core
│   ├── src
│   └── pom.xml
├── web
│   ├── src
│   └── pom.xml
└── pom.xml
일단 최상위 디렉토리인 parent에 부모 pom.xml 파일이 있다. 이를 위에서 예로 든 것처럼 batch, core, web 의 3개의 모듈의 pom.xml에서 상속하고 있다.

2) 집합

느낌상으로 알겠지만 core 모듈은 batch 와 web 모듈에서 공통적으로 쓰이는 클래스를 제공한다. 다시말해 각각 batch와 web은 core 모듈에 의존성이 존재한다.

한마디로 web을 빌드하려면 core -> web 순으로 빌드되어야 하고, batch를 빌드하려면 core -> batch 순으로 빌드가 되어야 하는것이다. 이런식으로 빌드가 되게 하기 위해 부모 pom.xml 파일은 아래와 같이 작성되어야 한다.
<project>
   <modelVersion>1.0.0</modelVersion>
   <groupId>com.mycorp</groupId>
   <artifactId>parent</artifactId>
   <packaging>pom</packaging>           
   <version>1.0-SNAPSHOT</version>
   <modules>
      <id>web-build</id>
      <module>core</module>  <!-- 모듈 목록을 등록한다 -->
      <module>web</module>
   </modules>
   <modules>
      <id>batch-build</id>
      <module>core</module>  <!-- 모듈 목록을 등록한다 -->
      <module>batch</module>
   </modules>
   .
   .
   .
각각 id로 구분하여 빌드해야할 모듈 목록을 등록한다. mvn 명령어로 빌드시에, 위에 설정된 대로 id값을 빌드파라메터로 전달하면, 해당 id를 가진 모듈들이 위에서부터 아래로 순서대로 빌드 된다.

다시 정리하자면, 하나의 프로젝트가 여러 모듈로 분리될 경우, 모듈간에 의존관계가 발생하므로 빌드를 개개의 모듈별로 진행하면 빌드가 실패할 수 있다. 따라서 여러 모듈을 빌드할때 같은 단위로 빌드 할 수 있도록 하는 기능이 '집합' 이다.

예를들어 web 프로젝트를 빌드하고 싶다면 아래와 같이 빌드한다!
$ mvn -P web-build clean package