cache의, cache를 위한, cache에 의한
cache의 3단 변신..
Vegeta로 하염없는 Request를 보내보니 알겠더군요..
* [대기열 시스템] HTTP load testing tool, Vegeta 설치(Window)
Cache가 중요하다는 것을요..
메모리에 저장해두고 누구보다 빠르게 남들과는 다르게 데이터를 전달해주는 Redis..
다양한 방법, 3.5단 변신을 정리해보고자 합니다..🤖
1단 변신 준비. RedisConfig - userRedisTemplate
Redis에 데이터를 저장하고 가져올 때, User 객체를 쉽게 직렬화/역직렬화하여 처리할 수 있도록 RedisTemplate을 설정
RedisConfig.java
@Configuration
public class RedisConfig {
@Bean
RedisTemplate<String, User> userRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
// RedisConnectionFactory: Redis 서버와의 연결을 관리하는 클래스
ObjectMapper objectMapper = new ObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.registerModule(new JavaTimeModule())
.disable(SerializationFeature.WRITE_DATE_KEYS_AS_TIMESTAMPS);
// ObjectMapper: JSON 데이터를 Java 객체로 변환하거나, Java 객체를 JSON 데이터로 변환
// DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false: JSON에 Java 클래스에 정의되지 않은 필드가 있어도 에러를 발생시키지 않도록 설정
// JavaTimeModule: java.time 패키지의 날짜/시간 클래스(LocalDate, LocalDateTime 등)를 처리할 수 있는 모듈을 추가
// SerializationFeature.WRITE_DATE_KEYS_AS_TIMESTAMPS
// : 날짜를 타임스탬프가 아닌 ISO-8601 포맷(예: 2023-01-18T10:15:30)으로 직렬화하도록 설정
// Object Mapper 설정 이유
// Java 객체의 구조에 맞지 않는 JSON 필드가 있을 경우 에러가 발생할 수 있음
// Java의 날짜/시간 클래스(LocalDate, LocalDateTime 등)는 기본적으로 직렬화/역직렬화가 제대로 지원되지 않음
// 기본적으로 날짜와 시간을 타임스탬프 형식(예: 1674042600000, 밀리초 단위)으로 직렬화
// 날짜를 사람이 읽을 수 있는 ISO 형식(예: 2025-01-18T15:30:00)으로 변환
RedisTemplate<String, User> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
template.setKeySerializer(new StringRedisSerializer()); // 키를 문자열(String)로 직렬화
template.setValueSerializer(new Jackson2JsonRedisSerializer<>(objectMapper, User.class)); // JSON 포맷으로 데이터를 저장
return template;
}
}
1단 변신. RedisConfig - objectRedisTemplate
RedisTemplate를 광범위하게 사용할 수 있도록 Value를 Object로 확장
RedisConfig.java
@Configuration
public class RedisConfig {
@Bean
RedisTemplate<String, Object> objectRedisTemplate(RedisConnectionFactory connectionFactory) {
PolymorphicTypeValidator ptv = BasicPolymorphicTypeValidator
.builder()
.allowIfSubType(Object.class)
.build();
// PolymorphicTypeValidator: allowIfSubType을 통해 역직렬화 허용 클래스 타입을 정의
ObjectMapper 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);
// activateDefaultTyping: Jackson에서 직렬화/역직렬화 시 객체 타입 정보를 포함하도록 설정하는 메서드
// DefaultTyping: 비최종 클래스(Non-final class)에 대해서만 타입 정보를 포함
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new GenericJackson2JsonRedisSerializer(objectMapper));
// RedisTemplate를 광범위하게 사용할 수 있도록 Value를 Object로 확장
// GenericJackson2JsonRedisSerializer
// : JSON 데이터를 역직렬화할 때, 대상 객체의 타입 정보를 명시적으로 제공하지 않으면 기본적으로 Map 타입(즉, LinkedHashMap)으로 역직렬화
return template;
}
}
2단 변신. RedisHashUser
@Redishash를 사용해 객체를 Redis의 Hash 데이터 구조로 저장
RedisHashUser.java
@RedisHash(value = "redishash-user", timeToLive = 30L)
// @RedisHash를 사용해 Redis에 저장할 객체를 정의
public class RedisHashUser {
@Id
private Long id;
// HSET redishash-user:3 id 3
private String name;
// HSET redishash-user:3 name "hi3"
@Indexed
private String email;
// HSET redishash-user:3 email "hi3@email.com"
// "SADD" "redishash-user:email:hi3@email.com" 3
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
}
RedisHashUserRepository.java
public interface RedisHashUserRepository extends CrudRepository<RedisHashUser, Long> {
// ...
}
JpaRepository는 Redis가 아닌 RDBMS(예: MySQL, PostgreSQL 등)와 상호작용하기 위한 Repository
CrudRepository / RedisRepository를 상속하는 Repository 이용 필수
기능 | JpaRepository | CrudRepository |
저장소 대상 | 관계형 데이터베이스 (RDBMS) | Redis |
데이터 모델 | 테이블과 레코드 | Key-Value 또는 Hash 등 |
내부 구현 | JPA(EntityManage, Hibernate 등) 기반 | RedisTemplate 기반 |
동작 방식 | SQL 쿼리 생성 및 실행 | Redis 명령어(SET, GET 등) 실행 |
@RedisHash 지원 여부 | 지원하지 않음 | 지원함 |
3단 변신(최종 변신). @Cacheable
캐시에 데이터가 없을 경우, 캐시에 데이터를 추가하고, 있을 경우, 캐시의 데이터 반환
CacheConfig.java
@EnableCaching
// Spring의 캐싱 기능을 활성화
// 캐싱 관련 애너테이션(@Cacheable, @CacheEvict, @CachePut)이 동작하도록 설정
// 애플리케이션 컨텍스트에 한 번만 추가되면 전체 프로젝트에서 동작
@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();
ObjectMapper 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()))));
});
// RedisCacheConfiguration.defaultCacheConfig(): 기본 Redis 캐시 설정을 가져옴
// disableCachingNullValues(): null 값을 캐싱하지 않도록 설정
// serializeKeysWith: Redis의 키를 직렬화하는 방법을 설정, 키를 문자열로 직렬화
// serializeValuesWith: Redis의 값을 직렬화하는 방법을 설정, GenericJackson2JsonRedisSerializer를 사용하여 값을 JSON으로 직렬화
// entryTtl: 캐시의 만료 시간을 설정
}
}
UserService.java
@Service
@RequiredArgsConstructor
public class UserService {
@Cacheable(cacheNames = CACHE1, key = "'users:' + #id")
// cacheNames: 캐시를 구분하고 각각의 캐시에 대해 커스터마이징된 설정을 적용하기 위해 사용
public User getUser3(final Long id) {
return userRepository.findById(id).orElseThrow();
}
}
📑
참고 자료
Chat GPT
https://fastcampus.co.kr/dev_online_traffic_data
'MinimiProject > 아이돌 티켓팅 접속자 대기열 시스템' 카테고리의 다른 글
[대기열 시스템] Redis와 Replication (0) | 2025.01.28 |
---|---|
[대기열 시스템] Docker Compose로 Grafana와 Prometheus로 모니터링 시스템 구축하기 (0) | 2025.01.26 |
[대기열 시스템] HTTP load testing tool, Vegeta 설치(Window) (0) | 2025.01.21 |
[대기열 시스템] objectRedisTemplate과 ClassCastException (0) | 2025.01.19 |
[대기열 시스템] Windows Docker 환경에서 MySQL 설치 및 연결 (0) | 2025.01.12 |