ElasticSearch Getting Started - with RESTClient on Java

2017. 6. 29. 17:05DB&NoSQL/Elasticsearch,ELK

X-pack 에 대해서 한마디 
===========================================================
x-pack 을 설치했었는지 기억이 가물하지만, 결론적으론 인증이 달라 붙어버렸다.

elasticsearch에 x-pack 플러그인 설치한것 같다.
$ bin/elasticsearch-plugin install x-pack

config/elasticsearch.yml 상에 action.auto_create_index 기능을 켜란다.

$ cd config
$ vi elasticsearch.yml
...
action.auto_create_index: .security,.monitoring*,.watches,.triggered_watches,.watcher-history*

그리고 난후에 인증에 대한 요청이 있으면,
ID : elastic
PW : changeme
ID : kibana
PW : changeme

으로 한다.
그다음 kibana에도 적용을 해보자


kibana에도 x-pack 플러그인을 설치한다.
$ bin/kibana-plugin install x-pack


키바나에서 ES에 접속시 default는 kibana계정 및 기본 패스워드를 사용한다.
하지만 이제 kibana 계정의 비번을 바꾸었으니 계정정보를 kibana.yml에 명시해준다.
이 부분을 바꾸지 않으면 키바나 구동 및 ES 접속이 정상동작 하지 않는다.


JAVA에서 elasticsearch client 를 가져다가 사용해봅시다. 

그냥 통째로 해석놀이!!!!

여러분의 어플리케이션에 Elasticsearrch를 어떻게 쉽게 통합하십니까요?
Elasticsearch는 여러분에게 두가지 방법을 제시합니다.
REST API 와 Native client를 제공하죠..

어떤것이 더 나을까요?
어떤것이든, 장점과 단점이 둘다 있답니다.
Elasticsearch에서 제공하는 REST API같은 경우에는, 상호작용을 수행(carry out)하기 위한 JAX-RS와 같이 써드파티 라이브러리를 사용해야 합니다. 
물론 Native clients가 Java,Python,Ruby 와 같은 많은 언어들상에 제공하는 아주 쉬운 선택이지만,
문제는 Elasticsearch의 메이저 버전 업그레이드가 있을 때마다 발생합니다.
여러분의 native client를 업그레이드 해야 하고, 많은 사람들이 이것을  불필요한 유지보수 노력으로 간주한답니다.

운좋게, Java REST Client의 출시로 보다 개선되어 왔습니다.
REST 호출을 만드는 것과는 다르게, 아래와 같은 추가적인 이익들을 제공합니다.

- 사용가능한 모든 노드들에 로드 밸런싱을 합니다.
  (Load balancing across all available nodes)
- 노드 장애 및 특정 응답 코드에 대한 장애 조치
  (Failover in case of node failures and upon specific response codes.)
- 영속적인 연결
  (Persistent Connections)
- 요청과 응답의 로깅 추적
 (Tracing logging of requests and responses)
- 선택적인 클러스터 노드의 자동 복구 
 (Optional automatic discovery of cluster node)
- 연결 실패에 대한 처벌
  (Failed connection penalization)

이 예제에서는, Qbox.io에 기동하는 Elasticsearch를 사용한다고 합니다.
가능하면, 로컬에 있는 걸로 최대한 테스트를 해보도록 합시다.
여러분은 회원가입할수 있거나,  여기 여러분의 클러스터를 런칭할수 있거나, 헤더 네비게이션상에 "Get started"를 클릭할수 있습니다.
혹, 여러분이 셋팅하는데 도움이 필요하다면, "Provisioning a Qbox Elasticsearch Cluster" (https://qbox.io/blog/provisioning-a-qbox-elasticsearch-cluster?utm_source=tutorial&utm_term=provision&utm_medium=article&utm_campaign=index-attachments-files-elasticsearch-mapper)를 참조하세요.

Java REST 클러스는 HTTP request를 보내기 위해  Apache HTTP Async Client를 사용합니다.
Java Rest Client를 Maven Central에 호스팅하는데, 여러분의 pom.xml 파일에 아래 디펜던시를 따르는 것을 추가해야 합니다.

<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>rest</artifactId>
    <version>5.0.0-rc1</version>
</dependency>

Elasticsearch에 어떠한 호출을 하기 위해선, RestClient를 생성할 필요가 있답니다.
생성은 RestClientBuilder 클래스를 사용하여 완료합니다.
대부분의 buildere 클래스들과 같이, 우리에게 인스턴스를 빌드 할때  파라미터를 설정할수 있도록 해줍니다.
여기, 여러분은 아래 setter 메소드를 사용하여 값을 넣을수 있습니다.
setDefaultHeaders, setMaxRetryTimeoutMillis, setFailureListener, setRequestConfigCallback, setHttpClientConfigCallback.
위의 setter들은 옵션입니다. ( RestClient.builder에 오직 필요로한 인자는, 하나 또는 그 이상의 HTTPHost 인스턴스 입니다.)

여기 꼭 필요로 한 인자값을 갖는 RestClient를 생성하는 예제가 있습니다. 

RestClient restClient = RestClient.builder(
       new HttpHost("localhost", 9200, "http"),
       new HttpHost("localhost", 9205, "http")).build();

혹시시시.... 위에서처럼 X-pack을 설치하고 나서, secure 상태라면, 사용자 인증(user credential)를 세팅해야 합니다.
혹시 Elasticsearch 호출들이 여러분이 작성한 몇몇 커스텀 플러그인들에 의해 가로채 갈 예정이면,
적당한 헤더들을 세팅하는 것을 잊지 마시길 바랍니다. 
"Header" 배열은 초기화 할수 있고, setter 메소드,setDefaultHeader()에 전달할수 있습니다.
간단하게, 한개는  HttpClientConfigCallback 을 제공함으로 이 빌더를 통해 RestClient를 빌드하는 동안 기본 인증을 획득할수 있습니다.
아래는 이걸 설명한 예제입니다.

Header[] headers = { new BasicHeader(HttpHeaders.CONTENT_TYPE, "application/json"),  new BasicHeader("Role", "Read") };
final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
                                      credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials("admin", "admin"));
restClient = RestClient.builder(new HttpHost("localhost", 9200)).setDefaultHeaders(headers)
.setHttpClientConfigCallback(new RestClientBuilder.HttpClientConfigCallback() {
          public HttpAsyncClientBuilder customizeHttpClient(HttpAsyncClientBuilder arg0) {
               return arg0.setDefaultCredentialsProvider(credentialsProvider);
          }
})
.build();

 Elasticsearch는 "performRequest" 와 제각각(respectively)의 RestClient의  performRequestAsync 메소드 변수들를 통해
동기 와 비동기 REST call Request를 제공합니다.
이 메소드들은 HTTP verb 과 인자로 endpoint를 취합니다.
아래와 같이 추가적인 인자를 받는 다른 오버로드된 함수들이 있습니다.

- 쿼리 문자 파라미터들을 보내기위한 map
  (Map to send query string parameters)
- 요청 Body를 보내기 위한 HTTPEntity 객체
  (HTTPEntity object to send request body.)
- 비동기 요청의 실패 또는 성공을 감지하기 위해 필요한 ResponseListener 메소드
  (ResponseListener method that needs to notified on asynchronous request success or failures.)
- 클라이언트 측상에 논블록킹 HTTP connection로 부터 response body를 스트림을 받는 방법을 조절하는 HttpAsyncResponseConsumer
 (HttpAsyncResponseConsumer which controls how the response body gets streamed from a non-blocking HTTP connection on the client side.)
- request header상에 전달되는 옵션의 Headers 배열.
 (An optional Headers array to pass on request headers.)


Example of the features of RestClient

새로운 문서 인덱스 (Index a New Document)

HttpEntity entity = new NStringEntity(
        "{\n" +
        "    \"company\" : \"qbox\",\n" +                                     
        "    \"title\" : \"Elasticsearch rest client\"\n" +
        "}",
        ContentType.APPLICATION_JSON
);

Response indexResponse = restClient.performRequest(
        "PUT",
        "/blog/post/1",
        Collections.<String, String>emptyMap(),
        entity
);

Log.debug(EntityUtils.toString(indexResponse.getEntity()));

 Output 

{"_index":"blog","_type":"post","_id":"1","_version":1,"_shards":{"total":2,"successful":1,"failed":0},"created":true}


쿼리 파라미터를 사용한 document 검색 (Search the document using Query Params)


Map<String, String> paramMap = new HashMap<String, String>();
paramMap.put("q", "company:qbox");
paramMap.put("pretty", "true");

Response response = restClient.performRequest(
    "GET",
    "/blog/_search",
    paramMap
);
Log.debug(EntityUtils.toString(response.getEntity()));
Log.debug("Host -" + response.getHost() );
Log.debug("RequestLine -"+ response.getRequestLine());

output

{
 "took" : 4,
 "timed_out" : false,
 "_shards" : {
"total" : 5,
"successful" : 5,
"failed" : 0
 },
 "hits" : {
"total" : 1,
"max_score" : 0.30685282,
"hits" : [ {
     "_index" : "blog",
     "_type" : "post",
     "_id" : "1",
     "_score" : 0.30685282,
     "_source" : {
       "company" : "qbox",
       "title" : "Elasticsearch rest client"
     }
} ]
 }
}
Host - http://localhost:9200
RequestLine - GET /blog/_search?q=company%3Aqbox&pretty=true HTTP/1.1

일반적인 response 결과와 별개로, hostname,request URL 등과 같은 세부사항을 출력할수 있습닌다.
Response 객체는 위와 같은 그안에 getter 메소드를 사용하여 획득할수 있는 세부사항을 노출합니다.
getHeader, getStatusLine, getEntity, getRequestLine, getHost

Query DSL을 사용하여 문서 검색하기 (Search the document using Query DSL) 


HttpEntity entity1 = new NStringEntity(
    "{\n" +
    "    \"query\" : {\n" +
    "    \"match\": { \"company\":\"qbox\"} \n" +
    "} \n"+
    "}",
    ContentType.APPLICATION_JSON
);

Response response = restClient.performRequest(
    "GET",
    "/blog/_search",
    Collections.singletonMap("pretty", "true"),
    entity1
);
Log.debug(EntityUtils.toString(response.getEntity()));

output


{
 "took" : 3,
 "timed_out" : false,
 "_shards" : {
"total" : 5,
"successful" : 5,
"failed" : 0
 },
 "hits" : {
"total" : 1,
"max_score" : 0.30685282,
"hits" : [ {
     "_index" : "blog",
     "_type" : "post",
     "_id" : "1",
     "_score" : 0.30685282,
     "_source" : {
       "company" : "qbox",
       "title" : "Elasticsearch rest client"
     }
} ]
 }
}


비동기 REST 호출을 만들어야 하는 상황이 있을수 있습니다.
예를 들면, 백만개의 문서들상에서 동작될 필요가 있는 집합 동작(aggreation operation)를 가지고 있다.
이경우에는, 일반적인 코드 실행 흐름을 막는 시간 소요하는 동작을 원하기 않을 것입니다.
RestClient의 async 변수, 예를 들면 performRequestAsync 는 이런 경우에 도움을 줍니다.
아래 예제는 어떻게  비동기 요청들을 만들수 있는지에 대해서 보여 줍니다.


HttpEntity entity1 = new NStringEntity(
    "{\n" +
    " \"company\" : \"qbox\",\n" +
    " \"title\" : \"Elasticsearch rest client\"\n" +
    "}",
    ContentType.APPLICATION_JSON
);

HttpEntity entity2 = new NStringEntity(
    "{\n" +
    " \"company\" : \"supergaint\",\n" +
    " \"title\" : \"supergaint is awesome\"\n" +
    "}",
    ContentType.APPLICATION_JSON
);

HttpEntity entity3 = new NStringEntity(
    "{\n" +
    " \"company\" : \"linux foundation\",\n" +
    " \"title\" : \"join linux foundation\"\n" +
    "}",
    ContentType.APPLICATION_JSON
);

HttpEntity[] entityArray= {entity1, entity2,entity3};

int numRequests = 3;
final CountDownLatch latch = new CountDownLatch(numRequests);
for (int i = 0; i < numRequests; i++) {
    restClient.performRequestAsync(
        "PUT",
        "/blog/posts/" + i,
        Collections.<String, String>emptyMap(),
        entityArray[i],
        new ResponseListener() {

            @Override
            public void onSuccess(Response response) {
             System.out.println(response);
             latch.countDown();
            }

            @Override
            public void onFailure(Exception exception) {
                    System.out.println(exception.getMessage());
             latch.countDown();
            }
        }
    );
}

//wait for completion of all requests
latch.await();

Output:


Response{requestLine=PUT /blog/posts/1 HTTP/1.1, host=http://localhost:9200, response=HTTP/1.1 201 Created}
Response{requestLine=PUT /blog/posts/2 HTTP/1.1, host=http://localhost:9200, response=HTTP/1.1 201 Created}
Response{requestLine=PUT /blog/posts/0 HTTP/1.1, host=http://localhost:9200, response=HTTP/1.1 201 Created}


결론

이건 지금까지 Elasticsearch에서 제공한 아주아주 적은 리소스로 무척무척 쉽게 접근이 가능하게 합니다.
이 장에서는, 여러분의 어플리케이션에 Elasticsearch를 통합하기를 원하는 개발자의 필요를 뒷받침 해주고 있습니다.
REST 클라이언트, 기능들 예를 들면 sniffing, load balancing 등등 아직 더 할것이 많다.