본문 바로가기

💻/spring

[Spring Boot] Elasticsearch SpringBoot 연동시켜 쿼리 결과 받아와서 api서버 구축

Spring boot 에서Elastic Search 를 사용하여 쿼리 결과를 반환해주는 api서버를 개발하게 되었다.

Spring bootElastic Search 연동하기 위해서는 spring-data-ElasticSearch 사용하면 된다.

연동하기 위한 과정은 다음과 같다.

1.의존성 추가

build.gradle

dependencies {

   implementation 'org.springframework.boot:spring-boot-starter-data-elasticsearch'
   //spring-data-elasticsearch 추가
   //..그외 나머지 의존성들

}

2.application.yaml 수정

spring:
  elasticsearch:
    cluster:
      initial-master-nodes: (clusternode name입력)
    uris: http://(ip주소 입력 ex>localhost):9200

위의 clusternode name의 경우 elasticsearch.yml파일에 있다.

필자의 ElasticSearch는 linux에 따로 설치 되어 있기 때문에 리눅스를 기준으로 설명한다

/etc/elasticsearch/elasticsearch.yml 에 위치해있다.

이후 파일을 열면

마지막 아래의 cluster.initial_mater_nodes를 써주면 된다.(물론 수정 가능하다)
해당 elasticsearch가 설치된 리눅스의 경우 aws의 ec2를 사용하기에 위와 같이 나왔다.

 

3.elasticsearch의 쿼리 결과를 받아오기 위한 Entity설계

 

elasticsearch 의 결과인 json을 Spring 에서 받아오기 위한 entity설계가 필요하다.

필자는 이미 elasticsearch에 저장되어 있는 index에서 쿼리를 spring을 통해 던져 결과를 받아오기가 목표이기 때문에 

elasticsearch의 devtool을 이용해 결과를 먼저 확인한 후 그에 맞는 객체를 생성했다.

GET metricbeat-*/_search
{
  "_source": ["@timestamp", "system.cpu.total.pct","host.hostname"],
  "query": {
    "bool": {
      "filter": [
        {
          "range": {
            "@timestamp": {
              "gte": "2023-08-01T18:30:56",
              "lte": "2023-08-01T18:45:57"
            }
          }
        },
        {
          "term": {
            "event.module": "system"
          }
        },
        {
          "term": {
            "event.dataset": "system.cpu"
          }
        }
      ]
    }
  }
}

해당 실제 쿼리를 돌려 나온 결과는 다음과 같다.

 "hits": {
    "total": {
      "value": 2,
      "relation": "eq"
    },
    "max_score": 0,
    "hits": [
      {
        "_index": ".ds-metricbeat-8.8.2-2023.07.11-000001",
        "_id": "IbVesokBHmNXj9gDF_II",
        "_score": 0,
        "_source": {
          "@timestamp": "2023-08-01T18:30:57.034Z",
          "system": {
            "cpu": {
              "total": {
                "pct": 0.0161
              }
            }
          },
          "host": {
            "hostname": "ip-172-31-12-240"
          }
        }
      },
      {
        "_index": ".ds-metricbeat-8.8.2-2023.07.11-000001",
        "_id": "LLVesokBHmNXj9gDGfKn",
        "_score": 0,
        "_source": {
          "@timestamp": "2023-08-01T18:30:57.722Z",
          "host": {
            "hostname": "ip-172-31-15-63"
          },
          "system": {
            "cpu": {
              "total": {
                "pct": 0.01
              }
            }
          }
        }
      }
    ]
  }

여기서 필자는 timestamp, system.cpu.total.pct, host.hotname 을 뽑아낼것이다.
해당 필드들에 대한 엔티티 설계는 다음과 같다.


import lombok.Getter;
import lombok.Setter;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
@Getter@Setter
@Document(indexName = ".ds-metricbeat-8.8.2-2023.07.11-000001")
public class MetricEntity {
    @Id
    private String id;

    @Field(name = "@timestamp", type = FieldType.Date)
    private String timestamp;

    @Field(type = FieldType.Nested)
    private SystemField system;

    @Field(type = FieldType.Object)
    private HostField host;

    // getters and setters
}
@Getter@Setter
class SystemField {
    @Field(type = FieldType.Nested)
    private CpuField cpu;
    private MemoryField memory;
    private NetworkField network;

    // getters and setters
}
@Getter@Setter
class CpuField {
    @Field(type = FieldType.Object)
    private TotalField total;

    // getters and setters
}
@Getter@Setter
class MemoryField {
    @Field(type = FieldType.Object)
    private ActualField actual;

    // getters and setters
}
@Getter@Setter
class NetworkField {
    @Field(type = FieldType.Object)
    private InField in;
    @Field(type = FieldType.Object)
    private OutField out;

    // getters and setters
}
@Getter@Setter
class ActualField {
    @Field(type = FieldType.Object)
    private UsedField used;

    // getters and setters
}
@Getter@Setter
class UsedField {
    @Field(name = "pct", type = FieldType.Float)
    private float pct;

    // getters and setters
}
@Getter@Setter
class TotalField {
    @Field(name = "pct", type = FieldType.Float)
    private float pct;

    // getters and setters
}
@Getter@Setter
class InField {
    @Field(name = "bytes", type = FieldType.Long)
    private Long bytes ;

    // getters and setters
}
@Getter@Setter
class OutField {
    @Field(name = "bytes", type = FieldType.Long)
    private Long bytes ;

    // getters and setters
}

@Getter@Setter
class HostField {
    @Field(name = "hostname", type = FieldType.Keyword)
    private String hostname;

    // getters and setters
}

@Document 에너테이션에 indexName은 해당 elasticsearch의 index명을 지정해야한다. 쿼리 결과의 Hits [ ]필드내의 index에서 확인할 수 있다.

참고로 @Getter@Setter 에너테이션은 롬복을 활용하여 필수로 작성하여야한다. 

또한 @Field의 경우 type명을 정확히 해당 필드의 타입과 일치하여야지 mapping시 문제가 생기지 않는다.

 

이렇게 엔티티를 설계한 후에는 elasticsearch와 spring을 연계시켜 쿼리를 작성할 차례이다.

4.쿼리 설계 및 controller 설계

해당코드는 /getMetricCPUdata를 통해 접속시 해당 쿼리 결과가 나오도록 했다.

 

import dev.project.hanium.Entity.MetricEntity;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.SearchHit;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilterBuilder;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;


import java.util.List;
import java.util.stream.Collectors;

@RestController

public class MetricApi {

    @Autowired
    private ElasticsearchRestTemplate elasticsearchRestTemplate;

    @GetMapping("/getMetricCPUdata")
    public ResponseEntity<Object> getMetricCPUData() {
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.query(QueryBuilders.boolQuery()
                .filter(QueryBuilders.rangeQuery("@timestamp")
                        .gte("2023-08-01T18:30:35")
                        .lte("2023-08-01T18:30:44"))
                .filter(QueryBuilders.termQuery("event.module", "system"))
                .filter(QueryBuilders.termQuery("event.dataset", "system.cpu")));

        SearchHits<MetricEntity> searchHits = elasticsearchRestTemplate.search(
                new NativeSearchQueryBuilder()
                        .withSourceFilter(new FetchSourceFilterBuilder().withIncludes("@timestamp", "system.cpu.total.pct", "system.memory.actual.used.pct", "host.hostname").build())
                        .withQuery(searchSourceBuilder.query())
                        .build(),
                MetricEntity.class
        );

        List<MetricEntity> metricbeatDataList = searchHits.stream()
                .map(SearchHit::getContent)
                .collect(Collectors.toList());
        return ResponseEntity.ok(metricbeatDataList);
    }

}

5.결과 확인

해당 spring-data-elasticsearch의 document를 번역한 글이 있어 링크를 첨부하겠다.

https://velog.io/@hanblueblue/%EB%B2%88%EC%97%AD-spring-data-elasticsearch

'💻 > spring' 카테고리의 다른 글

[Spring Boot] spring initializr 사용하여 프로젝트 만들기  (0) 2023.08.01