elasticsearch는 자바로 개발되었다. 그래서 9300 포트는 자바 네이티브로 API 개발이 가능하다.


자바 관점에서 API 분류

- 자바 네이티브 API - TransportClient, NodeClient

- HTTP Rest API - restTemplate/Apache HttpClient와 같은 툴로 직접 연결, Jest


NodeClient와 TransportClient로 개발이 쉽게 가능하다는 장점이 있다. 그러나 공식적으로 쓰지 않기를 권고 하고 있다. 기능이 추가되어 버전이 올라갈 때 모델이 바뀌고 있다는 점을 현재 강조하고 있으며, 9200포트를 이용하여 REST API로 개발하기를 권고하고 있다. 또한, 일래스틱서치 클러스터의 노드로 연동되기 때문에 일래스틱서치의 로그를 계속 받도록 되어 있다.

예를 들면, 아래와 같은 로그를 일래스틱서치 클라이언트도 받는 다. 


2015-08-18 15:02:22.839  INFO 27145 --- [anagement][T#2]] o.e.cluster.routing.allocation.decider   : [Unseen] high disk watermark exceeded on one or more nodes, rerouting shards

2015-08-18 15:02:52.825  WARN 27145 --- [anagement][T#2]] o.e.cluster.routing.allocation.decider   : [Unseen] high disk watermark [10%] exceeded on [5kXkv-RfQEumsEqOsTUaKA][Unseen] free: 15.2gb[6.5%], shards will be relocated away from this node 


9200 포트는 Http REST api로 접근할 수 있으며 자바를 제외한 대부분의 언어에서는 API를 이용한 lib로 구성된다.


9300포트가 아닌 9200포트로 접근하는 Http REST API를 순수 Java로 개발할 수 있다. 그러나 Java와 Json의 관계가 아름다운 편이 아니다. Map으로 객체를 감싸고 이를 Json으로 만드는 과정이 있어야 한다. 특히 POJO로 마샬링해야 하는 부분에서 귀찮고 구구절절한 코드를 작성해야 하는 부담감이 있던지라 쓸만한 것이 있나 찾아보았는데, Jest라는 자바 라이브러리를 추천하고 있었다. 


Jest(https://github.com/searchbox-io/Jest)은 JetBrain에서 개발되어 오픈소스로 나오게 되었다.  저자가 써보니 쓰기도 편하고 성능도 괜찮게 나왔다. 자바 네이티브 보다 더 빠른 성능을 가졌다. Spring sagan 프로젝트, play2 연동 모듈(https://github.com/CedricGatay/play2-elasticsearch-jest) 도 사용하고 있었다.


0.16이 현재 마지막 버전이지만, 조만간에 1.0.0이 릴리즈 된다고 한다. 


내가 본 jest의 장점은 두가지이다. 

- json 요청을 코드에 넣는다. 헤드 플러그인으로 미리 테스트해볼 수 있다.  잘 동작하면 이를 사용한다.

- 응답 결과를 POJO로 마샬링할 수 있어서 JSON을 파싱하지 않아서 편리하다.

- Spring의 @Configuration  또는 FactoryBean으로 생성할 수 있는 좋은 팩토리 구조라 재사용성이 좋다.

- 검색시 자바 네이티브 API보다 속도가 10ms 정도 더 빨랐다. (구조적인 사례일 수도)

- 다양한 환경에서도 언제든지 쓸 수 있다. (일래스틱서치 구성을 서버당 한개로 설정하고 여러 대로 L4로 구성하는 최적화한 상태에서 클러스터 이름이 달라도 쉽게 쓸 수 있다.)




간단한 psedo 코드는 다음과 같다. 동작하는 코드를 확인하라면 아래 참조 자료를 확인한다.



pom.xml 

        <dependency>

            <groupId>io.searchbox</groupId>

            <artifactId>jest</artifactId>

            <version>0.1.6</version>

        </dependency>



JestClientConfiguration.java : JestClient Bean을 생성한다.

@Configuration

public class JestClientConfiguration {


@Value("${elasticsearch.search.endpoint}")

private String address = "http://ep123.google.com:9200";


@Value("${elasticsearch.search.max_connection}")

private int maxTotalConnection = 10;


@Value("${elasticsearch.search.conn_timeout")

private int connTimeout = 1000;


@Value("${elasticsearch.search.read_timeout")

private int readTimeout = 3000;


@Bean

public JestClient jestClient(){

// Configuration

HttpClientConfig clientConfig = new HttpClientConfig.Builder(address)

.multiThreaded(true)

.maxTotalConnection(maxTotalConnection)

.connTimeout(connTimeout)

.readTimeout(readTimeout)

.build();


JestClientFactory factory = new JestClientFactory();

factory.setHttpClientConfig(clientConfig);

JestClient client = factory.getObject();

return client;

}

}



SearchService.java : 실제 JestClient를 이용하는 코드

@Service

public class SearchService {


@Inject

private JestClient jestClient;


        public List<SearchModel> search(String queryString) {


if (StringUtils.isEmpty(queryString)) {

logger.error("term object : " + queryString);

return Collections.emptyList();

}


String query = "{\n"

+ "    \"query\": {\n"

+ "        \"filtered\" : {\n"

+ "            \"query\" : {\n"

+ "                \"query_string\" : {\n"

+ "                    \"query\" : \"" + queryString + "\"\n"

+ "                }\n"

+ "            }\n"

+ "        }\n"

+ "    },\n"

+ "    \"sort\" : \n"

+ "       { \"rank\" : \n"

+ "           {\"order\" : \"desc\"} \n"

+ "    },\n"

+ "    \"size\" : 3"

+ "}";

              // query 문이 지저분하니 이를 freemarker(ftl)로 변경할 수 있다.


Search.Builder searchBuilder = new Search.Builder(query).addIndex(SEARCH_INDEX_NAME).addType(SEARCH_TYPE_NAME);

Search search = searchBuilder.build();


List<SearchModel> list = Lists.newArrayList();

try {

SearchResult result = execute(search);

if (result == null) {

return Collections.emptyList();

}

List<SearchResult.Hit<SearchModel, Void>> hits = result.getHits(SearchModel.class);


for (SearchResult.Hit<SearchModel, Void> hit: hits) {

SearchModel objectSource = hit.source;

list.add(objectSource);

}


} catch (Exception e) {

logger.error(e.getMessage(), e);

}


return list;

}


}






참조할 만한 싸이트

http://www.ibm.com/developerworks/library/j-javadev2-24/  (강추)

http://blogs.justenougharchitecture.com/?p=828

http://www.searchly.com/documentation/developer-api-guide/java-jest/

http://thysmichels.com/2014/03/02/heroku-elastic-search-example/

https://github.com/spring-io/sagan/blob/e003b6a50342629f4ca75a79a00941f572c81c73/sagan-common/src/main/java/sagan/search/support/SearchService.java

https://github.com/spring-io/sagan/blob/e003b6a50342629f4ca75a79a00941f572c81c73/sagan-common/src/main/java/sagan/search/support/SearchResultParser.java


Posted by '김용환'
,