이번 포스팅에서는 이전 포스팅에서 못 다한 Spring Redis Cache 를 사용해보고자 합니다.
코드는 Github 에 있습니다.
💡Redis 묶음
- Redis 란 (특징, 주의점, 동작 구조)
- Spring 에서 Redis 사용하기 (설정, In-memory DB, Transaction)
- ✔️ Spring Boot 에서 Redis Cache 사용하기
[목차]
- Cache Manager
- Redis Cache
- config
- @Cachable
- @CachEvict
- Cache hits 모니터링 종류
Spring Cache Abstraction
Spring 에서는 캐시 기능 자체에 대해서 특정 기술에 종속되지 않게 추상화를 제공합니다.
AOP 를 이용한 어노테이션을 활용하여 특정 기술에 종속되지 않고, 애플리케이션에 캐싱 기능을 부여할 수 있고
이를 통해 프로덕션 코드가 외부 서비스의 변경에 의존하지 않도록 해줍니다.
따라서 직접적으로 캐싱기능을 제공하는 외부 서비스를 Cache Manager 로 Bean 으로 등록하여 사용할 수 있습니다.
Spring Cache Manager
캐시 추상화 에서는 캐시 기술을 지원하는 캐시 매니저를 빈으로 등록해야합니다.
캐시 매니저의 종류는 상당히 여러가지인데, 저는 Redis 를 캐시매니저로 사용해보고자 합니다.
그 외의 캐시 매니저
- ConcurrentMapCacheManager
- SimpleCacheManager
- EhCacheCacheManager
- CaffeineCacheManager .. 등등
✔️ 요즘은 CaffeineCacheManager 가 EnCache 보다 성능이 좋다고하여 잘 쓰인다고 합니다
Redis Cache
- 레디스는 Cache Manager 로 굉장히 많이 사용됩니다.
- Cache 란 데이터를 더 빠르게 조회하기 위해, 한번 조회된 데이터를 임시 저장소에 저장한 후 → 반복된 동일 조회 시 임시 저장소에서 해당 데이터를 빠르게 호출해오는 것을 의미합니다.
- 레디스는 In-memory를 사용하는 서버이기 때문에, Cache 저장소로 충분히 사용할만큼 빠른 작업 속도를 가집니다.
0) Yaml
Spring 에서 Cache 용도로 Redis 를 쓸거라고 명시적으로 작성해줍니다.
spring:
cache:
type: redis
1) Config
그 다음 RedisCacheManager 를 설정해줍니다.
@Configuration
@EnableCaching
public class RedisConfig {
@Bean
public LettuceConnectionFactory redisConnectionFactory() {
return new LettuceConnectionFactory(new RedisStandaloneConfiguration("localhost", 6379));
}
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
return RedisCacheManager.create(connectionFactory);
}
}
✔️ RedisCacheManagerBuilder
- 혹은 RedisCacheManagerBuilder로 구성할 수 있으며, 이를 통해 기본 RedisCacheConfiguration, 트랜잭션 동작 및 미리 정의된 캐시를 설정할 수도 있습니다.
@Configuration
@EnableCaching
public class RedisConfig {
@Bean
public LettuceConnectionFactory redisConnectionFactory() {
return new LettuceConnectionFactory(new RedisStandaloneConfiguration("localhost", 6379));
}
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
return RedisCacheManager.builder(connectionFactory)
.cacheDefaults(defaultCacheConfig())
.withInitialCacheConfigurations(singletonMap("predefined", defaultCacheConfig().disableCachingNullValues()))
.transactionAware()
.build();
// return RedisCacheManager.create(connectionFactory);
}
}
✔️ RedisCacheConfiguration
- RedisCacheManager로 생성된 RedisCache의 동작은 RedisCacheConfiguration으로 정의됩니다.
- 이 구성을 사용하면 다음 예제와 같이 바이너리 스토리지 형식으로 변환하기 위한 키 만료 시간, 접두사 및 RedisSerializer 구현을 설정할 수 있습니다.
- 이 외에 읽기 쓰기 잠금, 배치 전략, 만료시간 설정, 통계 등의 설정은 공식문서를 참고해주세요
@Configuration
@EnableCaching
public class RedisConfig {
@Bean
public LettuceConnectionFactory redisConnectionFactory() {
return new LettuceConnectionFactory(new RedisStandaloneConfiguration("localhost", 6379));
}
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
.disableCachingNullValues()
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()))
.entryTtl(Duration.ofMinutes(3L));
return RedisCacheManager
.RedisCacheManagerBuilder
.fromConnectionFactory(connectionFactory)
.cacheDefaults(redisCacheConfiguration)
.build();
}
}
2) 캐싱 사용하기
Spring 에서 제공하는 3가지 어노테이션으로 캐싱 작업을 설정할 수 있습니다.
- @Cacheable : 메서드 호출 시, 메서드가 호출된 적 있는지를 판단하고 있으면 캐싱된 데이터를 리턴, 없으면 리턴값을 캐싱
- @CachePut : 데이터를 캐시에 저장
- @CacheEvict : 캐시 삭제
@Cacheable
- 호출 결과(리턴 값)을 캐시할 수 있음을 의미하는 주석입니다.
- cacheName(value) 값을 기준으로 캐시를 저장합니다.
- 해당 메서드가 호출될 때 마다 캐싱 동작이 수행되며 해당 메서드가 이미 호출되어 있는지 여부를 확인하고 캐시 데이터를 호출합니다.
@Cacheable(cacheNames = "findTicketAll", key = "#root.methodName", cacheManager = "cacheManager")
public List<Ticket> getTickets() {
return ticketRepository.findAll();
}
↓
✔️ Optional Element
- cacheName : 캐시 이름
- value : cacheName의 alias (cacheName 을 쓰나, value 를 쓰나 저장되는 key 값은 같았습니다.)
- key : 동적인 키 값을 사용하는 SpEL 표현식 (동일한 cache name을 사용하지만 구분될 필요가 있을 경우 사용되는 값)
- condition : SpEL 표현식이 참일 경우에만 캐싱 적용 (or, and 등 조건식, 논리연산 가능)
- unless : 캐싱을 막기 위해 사용되는 SpEL 표현식 (condition과 반대로 참일 경우에만 캐싱이 적용되지 않음)
- cacheManager : 사용 할 CacheManager 지정
- sync
- 여러 스레드가 동일한 키에 대한 값을 로드하려고 할 경우, 기본 메서드의 호출을 동기화
- 즉, 캐시 구현체가 Thread safe 하지 않는 경우, 캐시에 동기화를 걸 수 있는 속성
@Cacheable(cacheNames = "platformTeamBooks", key = "#root.target + #root.methodName + '_'+ #p0")
public Book getPlatformTeamBook(int bookId) { /* ... */}
@Cacheable(cacheNames = "users", condition = "#user.type == 'ADMIN'")
public Book selectUser(User user) { /* ... */}
@Cacheable(value="addresses", unless="#result.length()<64")
public String getAddress(Customer customer) {...}
출처: https://gngsn.tistory.com/157 [ENFJ.dev:티스토리]
@CacheEvict
- 메서드가 호출될 때 캐시에 있는 데이터가 삭제합니다.
- ✔️ 데이터가 변경됬을 시, 캐싱 된 데이터가 함께 변경되지 않기 때문에 보통 캐싱되었던 데이터를 삭제할 때 사용합니다.
✔️ Optional Element
- allEntries : 캐시 내의 모든 리소스를 삭제할지의 여부 (defalut : false)
- condition : SpEL 표현식이 참일 경우에만 삭제 진행 ( or, and 등 조건식, 논리연산 가능 )
- beforeInvocation
- true - 메서드 수행 이전 캐시 리소스 삭제
- false - 메서드 수행 후 캐시 리소스 삭제
@CacheEvict(cacheNames = "findTicketAll", allEntries = true, cacheManager = "cacheManager")
public void updateTicketQuantity(long ticketId) {
Ticket ticket = ticketRepository.findById(ticketId).get();
ticket.changeQuantity(-1);
ticketRepository.save(ticket);
}
@CachePut
- 캐시에 값을 저장하는 용도로만 사용합니다.
- @Cacheabler 과 다르게 데이터를 무조건 저장하며 항상 메소드의 로직을 실행합니다.
3) Redis Cache Hits 모니터링
👏 캐시의 성능을 측정하는 데 사용되는 몇 가지 용어가 있습니다. 이 용어들은 캐시의 효율성과 성능을 평가하고 측정하는 데에 도움이 됩니다.
- Cache Hit (캐시 적중): 캐시에 데이터가 이미 존재하여 캐시를 통해 데이터 제공을 수행한 경우.
- Cache Miss (캐시 누락): 접근하려는 데이터에 캐싱되어 있지 않은 상태. 이런 경우 원본 서버/스토리지를 통해 데이터를 조회가 이뤄진다.
- Cache Hit Rate(캐시 적중률): 데이터를 캐시로 제공을 수행한 비율.
- Cache Hit Ratio = Cache Hits Count / (Cache Hits Count + Cache Misses Count)
캐싱 성능이 잘나온다는 건 높은 Hit Ratio ↑, 낮은 Miss Ratio ↓ 를 말합니다.
✔️ 모니터링 도구
- Redis stat
- grafana
- spring actuator
이렇게 3가지 정도가 Redis Hit Ratio 를 모니터링하기 좋은 도구인 것 같습니다.
→ 간단하게 Redis Server 의 자원 사용량에 대해 모니터링하기하는 방법으로는 'INFO ALL' 명령어도 존재합니다
info all
Spring Boot
Redis Cache 에 대하여
끝..!
참고
'Spring > Spring Boot' 카테고리의 다른 글
동시성 제어 문제에 대한 고찰 (With. Spring, JAVA, MySQL, Redis, Kafka) (0) | 2024.08.30 |
---|---|
Repository는 어느 모듈에 위치해야할까? (DIP. 고수준모듈, 저수준 모듈) (4) | 2024.07.09 |
Spring 에서 Redis 사용하기 (설정, In-memory DB, Transaction) (0) | 2024.07.01 |
@Transactional 동작과정 살펴보기 (with. Spring AOP) (1) | 2024.06.16 |
Spring Rest Docs Enum class 문서화 하기 (0) | 2024.03.19 |