10.1.2 Spring Boot에서 OCI Cache with Redis로 데이터 캐쉬하기
Spring Boot에서는 캐쉬를 설정할 수 있고, 캐쉬 서버로 Redis를 많이 사용합니다. OCI Cache with Redis로 만든 Redis 클러스터도 Spring Boot, Redis 코드에서 잘 연결되는 지 확인해 봅니다.
예제 Caching Data with Spring으로 기본 캐쉬 따라하기
Caching Data with Spring을 따라 순서대로 진행합니다. ‘OCI Cache with Redis를 캐쉬 서버로 연동’ 전까지는 해당 링크 내용과 동일합니다.
- 소소를 Download 받아, 압축해제하거나, Git 명령(
git clone https://github.com/spring-guides/gs-caching.git
)을 사용하여 복제합니다. - cd
gs-caching/initial
캐쉬설정하기 전
소스를 빌드하고 실행합니다.
./mvnw clean package java -jar target/caching-initial-0.0.1-SNAPSHOT.jar
애플리케이션을 실행합니다. 각 조회가 매번 3초 가량 걸리는 것을 볼 수 있습니다.
2023-11-16T11:34:38.459+09:00 INFO 4132 --- [ main] com.example.caching.AppRunner : .... Fetching books 2023-11-16T11:34:41.463+09:00 INFO 4132 --- [ main] com.example.caching.AppRunner : isbn-1234 -->Book{isbn='isbn-1234', title='Some book'} 2023-11-16T11:34:44.465+09:00 INFO 4132 --- [ main] com.example.caching.AppRunner : isbn-1234 -->Book{isbn='isbn-1234', title='Some book'} 2023-11-16T11:34:47.468+09:00 INFO 4132 --- [ main] com.example.caching.AppRunner : isbn-1234 -->Book{isbn='isbn-1234', title='Some book'}
Enable caching
src/main/java/com/example/caching/SimpleBookRepository.java
에@Cacheable("books")
를 추가하여, getByIsbn() 메서드의 결과를 books 캐쉬에 캐쉬하도록 설정합니다.package com.example.caching; import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Component; @Component public class SimpleBookRepository implements BookRepository { @Override @Cacheable("books") public Book getByIsbn(String isbn) { simulateSlowService(); return new Book(isbn, "Some book"); } // Don't do this at home private void simulateSlowService() { try { long time = 3000L; Thread.sleep(time); } catch (InterruptedException e) { throw new IllegalStateException(e); } } }
src/main/java/com/example/caching/CachingApplication.java
에@EnableCaching
annotation을 추가합니다.- 캐쉬 라이브러리를 별도로 지정하지 않으면, 디폴트로 ConcurrentHashMap을 사용합니다.
package com.example.caching; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cache.annotation.EnableCaching; @SpringBootApplication @EnableCaching public class CachingApplication { public static void main(String[] args) { SpringApplication.run(CachingApplication.class, args); } }
소스를 빌드하고 실행합니다.
./mvnw clean package java -jar target/caching-initial-0.0.1-SNAPSHOT.jar
처음 조회가 3초 가량 걸리고, 이후 2번은 캐쉬 데이터를 사용하여, 바로 응답한 것을 볼 수 있습니다.
2023-11-16T11:53:55.346+09:00 INFO 5782 --- [ main] com.example.caching.AppRunner : .... Fetching books 2023-11-16T11:53:58.351+09:00 INFO 5782 --- [ main] com.example.caching.AppRunner : isbn-1234 -->Book{isbn='isbn-1234', title='Some book'} 2023-11-16T11:53:58.355+09:00 INFO 5782 --- [ main] com.example.caching.AppRunner : isbn-1234 -->Book{isbn='isbn-1234', title='Some book'} 2023-11-16T11:53:58.356+09:00 INFO 5782 --- [ main] com.example.caching.AppRunner : isbn-1234 -->Book{isbn='isbn-1234', title='Some book'}
OCI Cache with Redis를 캐쉬 서버로 연동하기
dependency에 redis를 추가합니다.
- Maven 기준 pom.xml에 다음 추가
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
src/main/application.properties
파일을 생성하고 다음 항목을 추가합니다.- 아래 host, port는 개발환경을 기준으로 앞선 실습에서의 Bastion 서비스를 통해 포트포워딩으로 연동하는 예시입니다.
spring.redis.host=localhost spring.redis.port=6379 spring.redis.ssl=true
Redis 설정을 위한 Config 클래스 파일(
src/main/java/com/example/caching/RedisConfig.java
)을 만듭니다.- lettuce 라이브러리를 사용하는 예시입니다.
- redis-cli에서
--tls
옵션을 사용한 것 처럼lettuceClientConfigurationBuilder.useSsl().disablePeerVerification()
을 꼭 추가합니다. - application.properties에 값을 사용하여 host, port 값과 ssl 설정여부 설정하는 예시입니다.
- redis-cli에서
package com.example.caching; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Bean; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.connection.RedisStandaloneConfiguration; import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration; import org.springframework.data.redis.connection.RedisNode; @Configuration public class RedisConfig { @Value("${spring.redis.host}") private String host; @Value("${spring.redis.port}") private int port; @Value("${spring.redis.ssl}") private boolean ssl; @Bean public RedisConnectionFactory redisConnectionFactory() { final RedisNode redisNode = RedisNode.newRedisNode() .listeningAt(host, port) .build(); // Connecting as a Standalone Redis server final RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration(); redisStandaloneConfiguration.setHostName(host); redisStandaloneConfiguration.setPort(port); final LettuceClientConfiguration.LettuceClientConfigurationBuilder lettuceClientConfigurationBuilder = LettuceClientConfiguration.builder(); if (ssl) { lettuceClientConfigurationBuilder.useSsl().disablePeerVerification(); } final LettuceClientConfiguration lettuceClientConfiguration = lettuceClientConfigurationBuilder.build(); return new LettuceConnectionFactory(redisStandaloneConfiguration, lettuceClientConfiguration); } }
- lettuce 라이브러리를 사용하는 예시입니다.
캐쉬되는 데이터 모델(
src/main/java/com/example/caching/Book.java
)을Serializable
하도록 설정합니다.package com.example.caching; import java.io.Serializable; public class Book implements Serializable { ... }
소스를 빌드하고 실행합니다.
./mvnw clean package java -jar target/caching-initial-0.0.1-SNAPSHOT.jar
처음 조회가 3초 가량 걸리고, 이후 2번은 빠르게 실행되는 것을 볼 수 있습니다. 결과는 앞선 기본 ConcurrentHashMap를 저장소로 사용할 때랑 동일합니다.
2023-11-16T12:53:16.367+09:00 INFO 12783 --- [ main] com.example.caching.AppRunner : .... Fetching books 2023-11-16T12:53:20.008+09:00 INFO 12783 --- [ main] com.example.caching.AppRunner : isbn-1234 -->Book{isbn='isbn-1234', title='Some book'} 2023-11-16T12:53:20.058+09:00 INFO 12783 --- [ main] com.example.caching.AppRunner : isbn-1234 -->Book{isbn='isbn-1234', title='Some book'} 2023-11-16T12:53:20.100+09:00 INFO 12783 --- [ main] com.example.caching.AppRunner : isbn-1234 -->Book{isbn='isbn-1234', title='Some book'}
redis-cli로 Redis 클러스터에서 결과확인
redis-cli로 포트 포워딩된 localhost, 6379 포트로 접속합니다.
keys
,get
명령으로 데이터가 들어간 것을 확인 할 수 있습니다.$ redis-cli --tls -h localhost localhost:6379> keys * 1) "books::isbn-1234" localhost:6379> get books::isbn-1234 "\xac\xed\x00\x05sr\x00\x18com.example.caching.Book\x81\xa5yv\x90|\xa3\xe8\x02\x00\x02L\x00\x04isbnt\x00\x12Ljava/lang/String;L\x00\x05titleq\x00~\x00\x01xpt\x00\tisbn-1234t\x00\tSome book" localhost:6379>
추가 테스트
추가 테스트를 위해
src/main/java/com/example/caching/AppRunner.java
파일을 수정합니다.package com.example.caching; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.CommandLineRunner; import org.springframework.stereotype.Component; @Component public class AppRunner implements CommandLineRunner { private static final Logger logger = LoggerFactory.getLogger(AppRunner.class); private final BookRepository bookRepository; public AppRunner(BookRepository bookRepository) { this.bookRepository = bookRepository; } @Override public void run(String... args) throws Exception { logger.info(".... Fetching books"); logger.info("isbn-1234 -->" + bookRepository.getByIsbn("isbn-1234")); logger.info("isbn-4567 -->" + bookRepository.getByIsbn("isbn-4567")); logger.info("isbn-1234 -->" + bookRepository.getByIsbn("isbn-1234")); logger.info("isbn-4567 -->" + bookRepository.getByIsbn("isbn-4567")); } }
src/main/application.properties
파일에 다음 항목을 추가합니다.logging.level.org.springframework.cache=TRACE
다시 빌드하고 실행합니다.
./mvnw clean package java -jar target/caching-initial-0.0.1-SNAPSHOT.jar
실행 로그 확인
- isbn-1234 조회: 두 번 모두
Cache entry for key 'isbn-1234' found in cache 'books'
와 같이 Cache Hit 되었습니다. - isbn-4567 조회:
No cache entry for key 'isbn-4567' in cache(s) [books]
- 첫번째는 Cache MissCache entry for key 'isbn-4567' found in cache 'books'
- 두번째는 Cache Hit
2023-11-16T13:06:42.373+09:00 INFO 34105 --- [ main] com.example.caching.AppRunner : .... Fetching books 2023-11-16T13:06:42.375+09:00 TRACE 34105 --- [ main] o.s.cache.interceptor.CacheInterceptor : Computed cache key 'isbn-1234' for operation Builder[public com.example.caching.Book com.example.caching.SimpleBookRepository.getByIsbn(java.lang.String)] caches=[books] | key='' | keyGenerator='' | cacheManager='' | cacheResolver='' | condition='' | unless='' | sync='false' 2023-11-16T13:06:42.890+09:00 TRACE 34105 --- [ main] o.s.cache.interceptor.CacheInterceptor : Cache entry for key 'isbn-1234' found in cache 'books' 2023-11-16T13:06:42.890+09:00 INFO 34105 --- [ main] com.example.caching.AppRunner : isbn-1234 -->Book{isbn='isbn-1234', title='Some book'} 2023-11-16T13:06:42.890+09:00 TRACE 34105 --- [ main] o.s.cache.interceptor.CacheInterceptor : Computed cache key 'isbn-4567' for operation Builder[public com.example.caching.Book com.example.caching.SimpleBookRepository.getByIsbn(java.lang.String)] caches=[books] | key='' | keyGenerator='' | cacheManager='' | cacheResolver='' | condition='' | unless='' | sync='false' 2023-11-16T13:06:42.932+09:00 TRACE 34105 --- [ main] o.s.cache.interceptor.CacheInterceptor : No cache entry for key 'isbn-4567' in cache(s) [books] 2023-11-16T13:06:42.932+09:00 TRACE 34105 --- [ main] o.s.cache.interceptor.CacheInterceptor : Computed cache key 'isbn-4567' for operation Builder[public com.example.caching.Book com.example.caching.SimpleBookRepository.getByIsbn(java.lang.String)] caches=[books] | key='' | keyGenerator='' | cacheManager='' | cacheResolver='' | condition='' | unless='' | sync='false' 2023-11-16T13:06:45.996+09:00 INFO 34105 --- [ main] com.example.caching.AppRunner : isbn-4567 -->Book{isbn='isbn-4567', title='Some book'} 2023-11-16T13:06:45.997+09:00 TRACE 34105 --- [ main] o.s.cache.interceptor.CacheInterceptor : Computed cache key 'isbn-1234' for operation Builder[public com.example.caching.Book com.example.caching.SimpleBookRepository.getByIsbn(java.lang.String)] caches=[books] | key='' | keyGenerator='' | cacheManager='' | cacheResolver='' | condition='' | unless='' | sync='false' 2023-11-16T13:06:46.042+09:00 TRACE 34105 --- [ main] o.s.cache.interceptor.CacheInterceptor : Cache entry for key 'isbn-1234' found in cache 'books' 2023-11-16T13:06:46.043+09:00 INFO 34105 --- [ main] com.example.caching.AppRunner : isbn-1234 -->Book{isbn='isbn-1234', title='Some book'} 2023-11-16T13:06:46.043+09:00 TRACE 34105 --- [ main] o.s.cache.interceptor.CacheInterceptor : Computed cache key 'isbn-4567' for operation Builder[public com.example.caching.Book com.example.caching.SimpleBookRepository.getByIsbn(java.lang.String)] caches=[books] | key='' | keyGenerator='' | cacheManager='' | cacheResolver='' | condition='' | unless='' | sync='false' 2023-11-16T13:06:46.088+09:00 TRACE 34105 --- [ main] o.s.cache.interceptor.CacheInterceptor : Cache entry for key 'isbn-4567' found in cache 'books' 2023-11-16T13:06:46.088+09:00 INFO 34105 --- [ main] com.example.caching.AppRunner : isbn-4567 -->Book{isbn='isbn-4567', title='Some book'}
- isbn-1234 조회: 두 번 모두
redis-cli로 다시 조회해 보면 새로 추가한 isbn-4567도 들어가 있는 것을 볼 수 있습니다.
$ redis-cli --tls -h localhost localhost:6379> keys * 1) "books::isbn-4567" 2) "books::isbn-1234" localhost:6379>
참고
이 글은 개인으로서, 개인의 시간을 할애하여 작성된 글입니다. 글의 내용에 오류가 있을 수 있으며, 글 속의 의견은 개인적인 의견입니다.