본문 바로가기

Spring

Spring Boot에서 Redis 사용하기

Redis

Redis는 Key, Value 구조의 비정형 데이터를 저장하고 관리하기 위한 오픈 소스 기반의 비관계형 데이터 베이스 관리 시스템이다

데이터베이스, 캐시, 메세지 브로커로 사용되며 인메모리 데이터 구조를 가진 저장소입니다

 

장점 및 특징

  • 다양한 자료 구조를 지원한다. (Strings, Hash, List, Set 등)
  • 영속성을 지원하는 인 메모리 데이터 저장소
  • 싱글 스레드 방식으로 인해 연산을 원자적으로 수행 (Race condition 방지)
  • 데이터를 메모리에 저장하기 때문에 매우 빠른 응답 시간 제공
  • Master-slave 복제 지원 → 데이터 가용성 높일 수 있음, 클러스터링을 통해 수평적 확장 가능 (높은 가용성 및 확장성)
  • Expires → 데이터에 만료시간을 설정하여 일정 시간 후 데이터를 자동으로 없앨 수 있다

단점

  • 데이터를 메모리에 저장하기 때문에 메모리 사용량이 큰 데이터일 경우 추가적인 메모리 비용 발생
  • 단일 스레드 모델이기 때문에 다중 CPU 코어를 활용하지 못한다 → 많은 동시 요청이 발생하는 경우 처리 성능 한계

간단하게 Redis에 대해서 알아보았다.

그런데 우리는 DB를 사용하기 위해서 보통 관계형 데이터베이스를 사용한다.

그렇다면 언제 Redis를 사용하면 좋을까??

  • 캐싱 → 다른 시스템의 쿼리 결과 및 계산 결과 등을 캐싱하여 중복 계산을 피하고 빠른 데이터 접근 가능
  • 세션 저장 → 사용자의 세션을 저장하여 관리를 효율적으로 할 수 있다.
  • 실시간 데이터 분석 및 처리
  • 인증 토큰 저장

아래 예제를 통해 Spring Boot에서 Redis를 사용해보자

Spring Boot + Redis

build.gradle

implementation 'org.springframework.boot:spring-boot-starter-data-redis'

application.yml

spring:
	redis:
	    host: localhost
	    port: 6379

RedisConfig.java

@Configuration
public class RedisConfig {
	@Value("${spring.redis.host}")
	private String redisHost;

	@Value("${spring.redis.port}")
	private int redisPort;

	// Redis 연결
	@Bean
	public RedisConnectionFactory redisConnectionFactory(){
		return new LettuceConnectionFactory(redisHost,redisPort);
	}

  // Redis에서 넘겨준 byte 값 객체 직렬화
	@Bean
	public RedisTemplate<?,?> redisTemplate(){
		RedisTemplate<byte[], byte[]> redisTemplate=new RedisTemplate<>();
		redisTemplate.setConnectionFactory(redisConnectionFactory());
		redisTemplate.setKeySerializer(new StringRedisSerializer());
		redisTemplate.setValueSerializer(new StringRedisSerializer());
		return redisTemplate;
	}
}

 

config 파일에서 Seraializer가 나오는데 이것은 직렬화를 의미한다.

Serializer에 대해서 알아보자

 

Serializer

Redis에 객체를 저장하기 위해서는 해당 객체를 직렬화를 해주어야한다.

Serializer에는 3가지 종류가 있다.

GenericJackson2JsonRedisSerializer

  • 해당 Serializer는 객체의 클래스 지정없이 직렬화할 수 있다.
  • 하지만 Object의 Class 및 Package까지 전부 함께 저장하게 되어 다른 프로젝트에서 Redis에 있는 값을 사용하려면 package까지 일치시켜줘야해서 번거롭다.
  • MSA 관점의 프로젝트에서는 사용하지 않고, 프로젝트의 변경사항이 자주 발생할 경우 문제가 생길 수 있으니 추천하지 않는다.

Jackson2JsonRedisSerializer

  • 해당 Serializer는 클래스를 지정하기 때문에 Redis에 저장할 때 class 값을 저장하지 않는다.
    • GenericJackson2JsonRedisSerializer는 다르게 package 가 일치할 필요가 없다.
  • Class 타입을 지정하기 때문에 RedisTemplate를 여러 쓰레드에서 접근하게 될 때 serializer 타입의 문제가 발생한다.

StringRedisSerializer

  • String 값을 그대로 저장하는 Serializer
  • JSON 형태로 직접 encoding, decoding을 해줘야하지만 위의 serializer에서 발생할 수 있는 문제가 발생하지 않는다.
    • Class 타입 지정이 필요하지 않음
    • Package까지 일치할 필요가 없다.

RedisUtils.java

@Slf4j
@Component
@RequiredArgsConstructor
public class RedisUtils {
	private final RedisTemplate<String, Object> redisTemplate;

	public void initRedis() {
		redisTemplate.execute((RedisCallback<? extends Object>)connection -> {
			connection.flushDb();
			return null;
		});
	}

	public <T> void saveDataList(String key, List<T> data) {
		for (T datum : data) {
			try {
				ObjectMapper mapper = new ObjectMapper();
				String value = mapper.writeValueAsString(datum);
				redisTemplate.opsForList().rightPush(key, value);
			} catch (JsonProcessingException e) {
				throw new RuntimeException(e);
			}
		}
	}

	public <T> boolean saveData(String key, T data) {
		try {
			ObjectMapper mapper = new ObjectMapper();
			String value = mapper.writeValueAsString(data);
			redisTemplate.opsForValue().set(key, value);
			return true;
		} catch (Exception e) {
			log.error(e.getMessage());
			return false;
		}
	}

	public <T> boolean saveDataWithTimeout(String key, T data, long timeout) {
		try {
			ObjectMapper mapper = new ObjectMapper();
			String value = mapper.writeValueAsString(data);
			redisTemplate.opsForValue().set(key, value, timeout, TimeUnit.MICROSECONDS);
			return true;
		} catch (Exception e) {
			log.error(e.getMessage());
			return false;
		}
	}

	public <T> T getData(String key, Class<T> classType) throws JsonProcessingException {
		String value = (String)redisTemplate.opsForValue().get(key);

		if (StringUtil.isNullOrEmpty(value)) {
			return null;
		} else {
			ObjectMapper mapper = new ObjectMapper();
			T obj = mapper.readValue(key, classType);
			return obj;
		}
	}
}

 

 

참조

[Spring] Redis에서 객체 그래프를 유지하며 "직접" 캐싱하기 (velog.io)