-
OpenSearch + Spring Boot + Java 연동 예제 (HTTPS 및 Apache HttpClient 5 활용하기!)프로젝트 노트 2023. 12. 6. 15:18
📌 들어가며
회사 프로젝트에서 기존에 DB를 Oracle을 사용하다 급격한 로깅 트래픽의 증가로 Oracle에서 검색 효율이 좋은 ElasticSearch를 사용해보려 했습니다.
허나 그 막대한 라이선스 비용을 감당하느니 차라리 다른 식으로 구현을 해보자 하여 갑자기 대두된 플랫폼이 OpenSearch였습니다.
처음에 OpenSearch라는 플랫폼이 생소하긴 했지만 이미 AWS 진영에서는 많이 사용되고 있었고, 별도의 로컬 서버를 구축할 수 있도록 이미 설치패키지가 제공되고 있었습니다.
LogStash와 Kibana로 멋지게 대시보드도 구현할 수 있고, QueryDSL을 활용할 수 있는 Dashboard도 손쉽게 활용할 수 있었습니다.
근데 다만 문제는 Java와의 연동 예제가 많지 않다는 것이었죠..
이번에는 OpenSearch를 Java With Spring Boot에 연동하며 생산한 코드들을 기록해놓을까 합니다.
📌 OpenSearch? ElasticSearch?
OpenSearch의 모태는 ElasticSearch입니다.
OpenSearch는 ElasticSearch의 7.x대 버전을 포크하여 개발된 플랫폼입니다.
기본적으로 ElasticSearch와 마찬가지로 분산 검색 및 분석 엔진으로써 웹로그나 각종 미디어 데이터, IoT 데이터 등 다양한 유형의 데이터를 빠르고 효율적으로 검색하고 분석해낼 수 있는 장점을 보유하고 있습니다.
대용량 데이터를 빠르게 검색해낼 수 있기 때문에 AI 솔루션에서도 많이 사용되고 있는 데이터 관리 플랫폼이죠.
OpenSearch의 경우 ElasticSearch와 동일하나 ElasticSearch와 달리 상업적 사용에 대한 라이선스 제한이 없는 것이 특징입니다.
OpenSearch는 다음과 같은 특징을 가지고 있습니다.
✅ 분산 처리OpenSearch는 클러스터링 기술을 사용하여 데이터를 여러 대의 노드에 분산하여 처리합니다.
따라서, 대규모 데이터를 빠르고 효율적으로 처리할 수 있습니다.
✅ RESTful APIOpenSearch는 RESTful API를 사용하여 데이터를 검색하고 분석할 수 있습니다.
RESTful API는 다양한 언어와 플랫폼에서 사용할 수 있습니다.
✅ 확장성OpenSearch는 클러스터의 크기를 쉽게 확장할 수 있습니다.
따라서, 데이터의 양이 증가하더라도 쉽게 대처할 수 있습니다.
라이센스 비용이 걱정된다 하면 당연히 OpenSearch를 선택해야겠죠?
📌 SSL환경 KeyStore 설정
우선 OpenSearch를 별도의 윈도우즈 서버에 설치하였습니다.
OpenSearch를 설치하고, SSL 인증서를 발급받으면 config폴더 내에서 root-ca.pem확인하실 수 있습니다.
SSL이 적용된 OpenSearch와 통신을 하기 위해선 클라이언트 측, 즉 개발자가 본인 PC의 KeyStore에 해당 SSL인증서를 등록해주어야 합니다.
root-ca.pem을 서버로부터 복사해서 옮겨옵니다. 저는 jdk폴더 내부 lib > security로 옮겨왔습니다.
CMD를 관리자 권한으로 연 다음 해당 폴더로 접근을 해줍니다.
그리고 다음의 명령어를 입력해줍니다.
> keytool -importcert -alias opensearch -keystore ./cacerts -file root-ca.pem 여기서 opensearch는 제가 등록하는 alias 명이며 맨 뒤의 root-ca.pem이 루트 인증서입니다. 이렇게 등록을 해주면 password를 물어봅니다. > Enter keystore password: 저는 여기서 changeit으로 등록을 해주었습니다.
KeyStore 등록이 완료되면 다음처럼 확인하시면 됩니다.
> keytool -list -keystore cacerts ...(생략)... opensearch, 2023. 12. 6., trustedCertEntry ...(생략)...
이렇게 등록된 alias가 확인되면 등록은 끝! 이제 연동하면 됩니다!
📌 OpenSearch + Java with Spring Boot 연동!
SSL 통신이 가능한 OpenSearch와 연동 준비가 모두 끝났습니다.
프로젝트를 하나 만들어보죠.
가볍게 Spring Boot에 Lombok이 추가된 자바 17 버전의 프로젝트를 만들어줍니다.
참고로 OpenSearch는 자바 11부터 지원합니다. 다만 LTS 버전의 Java가 11은 곧 끝나기 때문에 저는 17로 개발하였습니다.
OpenSearch와 Java간의 통신을 하기 위해 기본적으로 세 가지의 방법이 존재합니다.
1. HighLevelRest Client 방식 : 이 방식은 OpenSearch 3.x 버전부터 Deprecated 될 예정입니다.
2. RestClient 방식 : Java Client 중 Rest Client를 사용하는 방식입니다.
3. Apache HttpClient 5 Transport 방식 : 현재 추천되는 Java Client 방식입니다. 자세한 내용은 아래를 참고해주세요!
그렇기 때문에 여기선 Apache HttpClient 5 Transport 방식을 활용할 것입니다.
Apache HttpClient 5에 대한 설명은 이전 게시글에 설명해놓았으니, 아래 게시글을 확인해주시면 됩니다😁
우선 pom.xml을 확인해보겠습니다.
// pom.xml ... <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.2.0</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.example</groupId> <artifactId>opensearch-demo</artifactId> <version>0.0.1-SNAPSHOT</version> <name>opensearch-demo</name> <description>opensearch-demo</description> <properties> <java.version>17</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- Apache HttpClient --> <dependency> <groupId>org.apache.httpcomponents.client5</groupId> <artifactId>httpclient5</artifactId> <version>5.2.1</version> </dependency> <dependency> <groupId>org.apache.httpcomponents.core5</groupId> <artifactId>httpcore5</artifactId> <version>5.2.1</version> </dependency> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.14</version> </dependency> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpcore</artifactId> <version>4.4.16</version> </dependency> <!-- Apache HttpClient --> <!-- OpenSearch --> <dependency> <groupId>org.opensearch.client</groupId> <artifactId>opensearch-java</artifactId> <version>2.6.0</version> </dependency> <!-- OpenSearch --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> ... </project>
Apache HttpClient와 OpenSearch의 의존성을 추가를 해줍니다.
그리고 미리 만들어놓은 OpenSearch서버의 접속 정보를 아래처럼 application.yml에 기재해줍니다.
opensearch: protocol: https url: Opensearch Server IP port: 9200 username: username password: password keystore: C:\\jdk\\lib\\security\\cacerts keystore.password: changeit
접속 정보 기입이 모두 완료되었다면 이제 Configuration Class를 통해 OpenSearch에 질의할 수 있는 OpenSearchClient를 만들어줍시다.
// OpenSearchClientConfig.class @Setter @Component @Slf4j public class OpenSearchClientConfig { @Value("${opensearch.protocol}") public String protocol; @Value("${opensearch.url}") public String url; @Value("${opensearch.port}") public int port; @Value("${opensearch.username}") public String username; @Value("${opensearch.password}") public String password; // HTTPS Settings @Value("${opensearch.keystore}") private String keystorePath; @Value("${opensearch.keystore.password}") private String keystorePassword; @Bean public OpenSearchClient openSearchClient() { final HttpHost host = new HttpHost(protocol, url, port); final BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider(); credentialsProvider.setCredentials(new AuthScope(host), new UsernamePasswordCredentials(username, password.toCharArray())); // HTTPS 통신을 위한 KeyStore 환경 설정 KeyStore keyStore; // HTTPS 통신을 위한 SSLContext 환경 설정 SSLContext sslContext; try { keyStore = KeyStore.getInstance(new File(keystorePath), keystorePassword.toCharArray()); sslContext = SSLContextBuilder .create() .loadTrustMaterial(keyStore, new TrustSelfSignedStrategy()) .build(); } catch (Exception e) { log.error(e.getLocalizedMessage()); throw new RuntimeException("Failed to get KeyStore instance..."); } // Apache HttpClient 5의 Transport를 사용하기 위한 Builder final ApacheHttpClient5TransportBuilder builder = ApacheHttpClient5TransportBuilder.builder(host); builder.setHttpClientConfigCallback(httpClientBuilder -> { // SSL 설정 final TlsStrategy tlsStrategy = ClientTlsStrategyBuilder.create() .setSslContext(sslContext) .setHostnameVerifier(new NoopHostnameVerifier()) .build(); // Pooling 설정 final PoolingAsyncClientConnectionManager connectionManager = PoolingAsyncClientConnectionManagerBuilder .create() .setTlsStrategy(tlsStrategy) .build(); return httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider) .setConnectionManager(connectionManager); }); final OpenSearchTransport transport = builder.build(); return new OpenSearchClient(transport); } }
등록한 KeyStore의 설정 정보와 비밀번호를 통해 Client 설정을 진행합니다.
여기서는 ApacheHttpClient5 Transport를 사용하여 HTTP 통신을 처리하고, OpenSearchClient는 이 transport를 활용해 데이터를 전송 및 수신하는 작업을 진행합니다.
연동 작업은 모두 완료되었습니다.
OpenSearchClient는 @Bean으로 처리하였기에 Spring Container에서 Bean으로 보유하고 있게 됩니다.
그 말은 즉슨 저희가 필요한 곳에 OpenSearchClient를 꺼내서 사용할 수 있다는 말이 되겠습니다.
📌 연동 테스트
백문이 불여일견! 테스트해보죠.
TestService 클래스를 만들어 OpenSearch에 인덱스의 데이터를 가져오도록 지시합니다.
@Service @RequiredArgsConstructor public class TestService { private final OpenSearchClient client; public void search() { try { SearchResponse<Object> sample = client.search(new SearchRequest.Builder().index("Index_Name").build(), Object.class); System.out.println("sample = " + sample); } catch (IOException e) { throw new RuntimeException(e); } } }
별도의 파싱 클래스를 만들어 두지 않아서 그냥 Object로 결과값을 받았습니다.
앞서 말씀드렸던데로 OpenSearchClient라는 이름으로 빈 등록이 되었기 때문에 client를 꺼내어 쓸 수 있게 되었습니다.
이를 호출하기 위해 Main 클래스를 조금 바꿔보겠습니다.
@SpringBootApplication public class OpensearchDemoApplication { public static void main(String[] args) { // ApplicationContext ConfigurableApplicationContext context = SpringApplication.run(OpensearchDemoApplication.class, args); // Context 내 OpenSearchClient Bean을 꺼내서 Service의 생성자에 전달 OpenSearchClient client = context.getBean(OpenSearchClient.class); TestService service = new TestService(client); // search 메소드 실행 service.search(); } }
Main클래스에선 TestService에서 만들어놓은 search()를 사용하기 위해 OpenSearchClient가 필요합니다.
ConfigurableApplicationContext를 통해 OpenSearchClient Bean을 꺼내고 이를 TestService의 생성자에 전달해줍니다.
이를 통해 Service를 Main 클래스 내에서 사용할 수 있게 됩니다.
search()를 호출해보겠습니다. 결과는 어떻게 되었을까요?
인덱스 내 10개의 데이터가 조회되었습니다.
기본적으로 size설정 없이 조회를 하면 10개의 도큐먼트만 가져오게 됩니다.
이를 다음과 같이 바꾸면 size를 조절하여 도큐먼트의 개수를 늘릴 수 있습니다.
...(생략)... public void search() { try { SearchRequest.Builder builder = new SearchRequest.Builder().index("Index_Name"); builder.size(1000); // 가져올 도큐먼트의 갯수 설정 SearchResponse<Object> sample = client.search(builder.build(), Object.class); System.out.println("sample = " + sample); } catch (IOException e) { throw new RuntimeException(e); } } ...(생략)...
만약 1000개를 가져오라고 요청하면 어떻게 될까요?
sample에는 1000개의 hit(Document)가 담기게 됩니다.
📌 마치며
직접 연동할 때는 자료가 많이 없어서 조금 헤맸는데, 이렇게 정리하고 나니 연동하는 것은 무척이나 쉽네요.
다만 이제 QueryDSL(JPA의 그것과는 조금 다릅니다)을 활용해 BoolQuery를 자유롭게 다루게되면 OpenSearch 활용 방안도 어느정도 체득될 듯 합니다.
이리저리 굴려봐야겠네요.
'프로젝트 노트' 카테고리의 다른 글