Spring redis 총정리
- Cache
- LocalCache ex) ehcache
- 로컬 캐시는 애플리케이션 내부에서만 유효하며, 동일한 어플리케이션 내의 여러 모듈이나 서비스 간에 공유하지 않는다.
- 메모리 내에 데이터를 저장하므로 매우 빠른 읽기 및 쓰기 성능을 제공한다.
- 로컬 캐시는 애플리케이션의 JVM 내부 또는 로컬 서버에 저장되며 외부에서 접근 할 수 없다.
- global cache. ex) redis
- 글로벌 캐시는 여러 서버 또는 애플리케이션 간에 데이터를 공유할 수 있다.
- 글로벌 캐시는 주로 네트워크를 통해 데이터에 접근해야하므로 로컬 캐시에 비해 상대적으로 느린 읽기 및 쓰기 성능을 가질 수 있다.
- 글로벌 캐시는 주로 네트워크를 통해 외부 서버에 데이터를 저장하므로 여러 애플리케이션 공유를 할 수 있다.
로컬케시를 사용하는 경우는 -> 데이터가 자주 변경되지 않는 다는 특징을 가져 캐싱 된 데이터가 서비스 간에는 공유되지 않더라도 크게 문제가 되지 않는 데이터를 사용할 때
-캐시 용량
캐시가 용량 제한에 도달하면 새 데이터를 수용하기 위해 항목을 제거해야한다. 이때 방식은 운영체제의 메모리 제거 방식을 사용한다.
ex) FIFO -> 가장 오래된 항목, LPU -> 가장 적게 엑세스 , LRU -> 최근 엑세스 항목
데이터 갱신하기
@cacheevict를 활용하여 캐시를 갱신하는 방법도 있고, TTl 설정을 통한 정기적인 갱신하는 방법이 있다.
데이터는 메모리 or Disk에 저장할 수 있다.
- 캐시의 사용성
캐시는 필요한 데이터를 확인하고, DB에서 데이터를 조회하는 작업이기에 데이터 히트율이 떨어지는 데이터를 담아 두는 경우 성능이 떨어질 수 있다(잘 조회하지 않는데 메모리나 디스크의 공간을 차지하기 때문) 이런 것들을 잘 고려해서 사용해야함.
- Redis
모든 데이터를 메모리에 저장하고 조회하는 in-memory DB, 모든 데이터를 메모리로 불러와서 처리하는 메모리 기반의 key-value 구조의 데이터 관리 시스템(DBMS)이다. 일종의 NoSQL이다.
Redis는 set map orederset hashmap list등 다양한 자료구조를 제공한다.
Redis는 config 파일이 필요하다.
@Bean
RedisTemplate<String, User> uesrRedisTemplate(RedisConnectionFactory connectionFactory) {
var objectMapper = new ObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.registerModule(new JavaTimeModule())
.disable(SerializationFeature.WRITE_DATE_KEYS_AS_TIMESTAMPS);
var template = new RedisTemplate<String, User>();
template.setConnectionFactory(connectionFactory);
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new Jackson2JsonRedisSerializer<>(objectMapper, User.class));
return template;
}
@Bean
RedisTemplate<String, Object> objectRedisTemplate(RedisConnectionFactory connectionFactory) {
PolymorphicTypeValidator ptv = BasicPolymorphicTypeValidator
.builder()
.allowIfSubType(Object.class)
.build();
var objectMapper = new ObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.registerModule(new JavaTimeModule())
.activateDefaultTyping(ptv, ObjectMapper.DefaultTyping.NON_FINAL)
.disable(SerializationFeature.WRITE_DATE_KEYS_AS_TIMESTAMPS);
var template = new RedisTemplate<String, Object>();
template.setConnectionFactory(connectionFactory);
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new GenericJackson2JsonRedisSerializer(objectMapper));
return template;
}
위의 두 방법은 지정클래스와 Object로 받는 두가지 방법이다. -> 둘의 차이점은 ObjectMapper에 PolymophicTypeValidator를 통해서 Object.class도 메핑시켜 주는 것이다.
Redis Cache도 사용할 수 있는데 사용하는 방법은 마찬가지로 Config 파일을 작성하는 것이다.
@EnableCaching
@Configuration
public class CacheConfig {
public static final String CACHE1 = "cache1";
public static final String CACHE2 = "cache2";
@AllArgsConstructor
@Getter
public static class CacheProperty {
private String name;
private Integer ttl;
}
@Bean
public RedisCacheManagerBuilderCustomizer redisCacheManagerBuilderCustomizer() {
PolymorphicTypeValidator ptv = BasicPolymorphicTypeValidator
.builder()
.allowIfSubType(Object.class)
.build();
var objectMapper = new ObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.registerModule(new JavaTimeModule())
.activateDefaultTyping(ptv, ObjectMapper.DefaultTyping.NON_FINAL)
.disable(SerializationFeature.WRITE_DATE_KEYS_AS_TIMESTAMPS);
List<CacheProperty> properties = List.of(
new CacheProperty(CACHE1, 300),
new CacheProperty(CACHE2, 30)
);
return (builder -> {
properties.forEach(i -> {
builder.withCacheConfiguration(i.getName(), RedisCacheConfiguration
.defaultCacheConfig()
.disableCachingNullValues()
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer(objectMapper)))
.entryTtl(Duration.ofSeconds(i.getTtl())));
});
});
}
}
실제로 사용할 경우를 보면
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Getter
@RedisHash(value = "redishash-user", timeToLive = 30L)
public class RedisHashUser {
@Id
private Long id;
private String name;
@Indexed
private String email;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
}
public interface RedisHashUserRepository extends CrudRepository<RedisHashUser, Long> {
}
RedisTemplete를 사용할 때는 RedisHash를 통해서 Entity를 설정할 수 있다.
@Service
@RequiredArgsConstructor
public class UserService {
private final UserRepository userRepository;
private final RedisHashUserRepository redisHashUserRepository;
private final RedisTemplate<String, User> userRedisTemplate;
private final RedisTemplate<String, Object> objectRedisTemplate;
public User getUser(final Long id) {
var key = "users:%d".formatted(id);
var cachedUser = objectRedisTemplate.opsForValue().get(key);
if (cachedUser != null) {
return (User) cachedUser;
}
User user = userRepository.findById(id).orElseThrow();
objectRedisTemplate.opsForValue().set(key, user, Duration.ofSeconds(30));
return user;
}
public RedisHashUser getUser2(final Long id) {
var cachedUser = redisHashUserRepository.findById(id).orElseGet(() -> {
User user = userRepository.findById(id).orElseThrow();
return redisHashUserRepository.save(RedisHashUser.builder()
.id(user.getId())
.name(user.getName())
.email(user.getEmail())
.createdAt(user.getCreatedAt())
.updatedAt(user.getUpdatedAt())
.build());
});
return cachedUser;
}
@Cacheable(cacheNames = CACHE1, key = "'user:' + #id")
public User getUser3(final Long id) {
return userRepository.findById(id).orElseThrow();
}
}
3가지 타입이있는데 처음은 object templete로 받아서 처리하는 것이고,
두번째는 ReidsRepository를 통해서 Redis에서 값을 가져오고 없다면 실제 DB에서 가져오는 것이다.
마지막은 redis cache를 통해서 간단하게 데이터를 저장하는 방법이다.