하나의 스프링 부트 프로젝트에서 Redis의 두 개의 DB를 연결하여 사용하기 위해서 설정하면서 겪은 내용을 정리한다.
우선 Redis의 Database(Namespace) 개념을 알아야 한다.
Redis의 네임스페이스는 데이터베이스를 말한다. PostgreSQL과 비교해 보면, PostreSQL은 하나의 클러스터 안에 여러 개의 Database를 생성하여 용도를 나눠 사용할 수 있는데, Redis 의 경우에는 네임스페이스를 나눠서 데이터베이스를 구분한다. 네임스페이스는 숫자로 구분된다. 테스트로 사용하고 있는 Redis의 경우에는 아래 그림과 같이 네임스페이스(DB) 0 ~ 15까지 16개가 기본 제공되는 것 같다.
이 기능은 하나의 Redis 서버에서 동시에 다른 여러 애플리케이션들을 실행하거나, 하나의 애플리케이션에서 DB를 구분해서 사용하고 싶은 경우에 사용하면 될 것 같다.
검색을 해 보니, redis에서 'multi db 사용은 권장하지 않는다'라는 말이 있고, redis 자체가 싱글 쓰레드로 동작하므로 여러 DB에 대해서 작업하는 것은 성능에 영향이 갈 수도 있다고 한다. 하나의 redis 서버에서 이렇게 사용할 수 밖에 없다면 어쩔 수 없이 사용할 수 밖에 없는 것 아닌가. 아직 공부가 부족해서 이와 관련된 내용은 더 조사해 보고 다시 정리하기로 하고, 지금은 내가 하는 프로젝트에서 이런 방식의 사용을 하는 것이 적절한지 확인하는 차원에서 시도해 보았다.
처음 시도한 방법은 redis connection 설정 후, RedisTemplate로 사용하는 시점에 DB를 선택하는 형태로 해봤지만, 역시나 제대로 동작하지 않았다. 이유는 redis도 DB와 같이 connection이 이미 하나의 DB로 스프링 부트에서 연결을 설정하고 있는 상태에서 다른 DB를 선택하는 것으로 기존에 설정된 DB(namespace)에 데이터가 삽입되었다. 즉, 이미 커넥션이 만들어진 상태에서 RedisTemaplate을 통한 다른 DB에 대한 데이터 컨트롤은 안된다.
따라서, 스프링 부트에서 여러 개의 datasource를 적용하는 방식과 같은 형태로 각각에 대한 connection을 설정하고, 각 connection에서 미리 사용할 DB 인덱스를 설정하도록 하였다.
Redis의 두 개의 DB를 사용하기 위해서 connection도 두 개가 필요한데, 이를 위한 설정을 아래와 같이 하였다. RedisConfig에서 기본 커넥션 만들고 이를 상속받은 클래스에서 DB 설정해서 각각 Bean으로 등록한다. 이렇게 한 이유는 Bean 등록 시 connection factory가 중복 에러가 나서 다른 이름으로 설정하여 각각 존재하도록 하기 위함임.
@Configuration
public class RedisConfig {
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private int port;
public RedisConnectionFactory createLettuceConnectionFactory(int dbIndex) {
final RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();
redisStandaloneConfiguration.setHostName(host);
redisStandaloneConfiguration.setPort(port);
redisStandaloneConfiguration.setDatabase(dbIndex);
return new LettuceConnectionFactory(redisStandaloneConfiguration);
}
}
@Configuration
public class DefaultRedisConfig extends RedisConfig {
@Bean
@Primary // 중요 이거 꼭 해야함. 설명은 아래에...
public RedisConnectionFactory defaultRedisConnectionFactory() {
return createLettuceConnectionFactory(0); // Redis DB 선택
}
@Bean
@Qualifier("defaultRedisTemplate")
public RedisTemplate<String, Object> defaultRedisTemplate() {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(defaultRedisConnectionFactory());
template.afterPropertiesSet();
return template;
}
@Bean(name = "defaultStringRedisTemplate")
public StringRedisTemplate stringRedisTemplate() {
final StringRedisTemplate stringRedisTemplate = new StringRedisTemplate();
stringRedisTemplate.setKeySerializer(new StringRedisSerializer());
stringRedisTemplate.setValueSerializer(new StringRedisSerializer());
stringRedisTemplate.setConnectionFactory(defaultRedisConnectionFactory());
return stringRedisTemplate;
}
}
@Configuration
public class DeviceControlRedisConfig extends RedisConfig {
@Bean
public RedisConnectionFactory deviceControlRedisConnectionFactory() {
return createLettuceConnectionFactory(2); // Redis DB 선택
}
@Bean
@Qualifier("deviceControlRedisTemplate")
public RedisTemplate<String, Object> deviceControlRedisTemplate() {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(deviceControlRedisConnectionFactory());
template.afterPropertiesSet();
return template;
}
@Bean(name = "deviceControlStringRedisTemplate")
public StringRedisTemplate stringRedisTemplate() {
final StringRedisTemplate stringRedisTemplate = new StringRedisTemplate();
stringRedisTemplate.setKeySerializer(new StringRedisSerializer());
stringRedisTemplate.setValueSerializer(new StringRedisSerializer());
stringRedisTemplate.setConnectionFactory(deviceControlRedisConnectionFactory());
return stringRedisTemplate;
}
}
위와 같이 설정하면 하나의 Redis 서버에 대해서 두 개의 커넥션을 빈으로 등록하여 사용할 수 있게 된다. RedisTemplate도 빈으로 등록 시, 각각 이름을 지정하기 위해서 @Qualifier 사용하거나, Bean 이름 지정하는 형태로 한다. 이와 관련해서는 스프링 Bean 등록하는 부분을 공부하면 된다.(나도 아직 부족함.)
이제 잘 동작하는지 테스트 해보자.
테스트 코드는 아래와 같이 작성하였고, Redis DB index 0번과 2번으로 각각의 데이터를 넣는 방식이다. 그리고 삽입된 데이터에 대해서 expire 기능을 넣어서, 시간이 경과하면 DB에서 삭제되도록 하였다.
@Autowired
StringRedisTemplate defaultStringRedisTemplate;
@Autowired
StringRedisTemplate deviceControlStringRedisTemplate;
@Test
@DisplayName("양쪽 다 데이터 저장되어야 한다.")
void redisMultiDatabaseTest2() {
final ValueOperations<String, String> valueOperations = defaultStringRedisTemplate.opsForValue();
final String keyPrefix = "ruleengine:cache-ruleId:";
final String value = "true";
for (int i = 0; i < 100; i++) {
String key = keyPrefix + i;
valueOperations.set(key, value);
defaultStringRedisTemplate.expire(key, i, TimeUnit.SECONDS);
}
final ValueOperations<String, String> deviceControlValueOperations = deviceControlStringRedisTemplate.opsForValue();
final LettuceConnectionFactory connectionFactory = (LettuceConnectionFactory) deviceControlStringRedisTemplate.getConnectionFactory();
final int database = connectionFactory.getDatabase();
System.out.println(" test database = " + database);
final String keyPrefix2 = "ruleengine:device-control-ruleId:";
final String value2 = "true";
for (int i = 0; i < 100; i++) {
String key = keyPrefix2 + i;
deviceControlValueOperations.set(key, value2);
deviceControlStringRedisTemplate.expire(key, i, TimeUnit.SECONDS);
}
}
이렇게 해서 실행하면 Redis의 Db index 0번과 2번에 각각 데이터가 저장이 되고, 각 key마다 expire 시간이 되면 DB에서 제거되는 것을 확인할 수 있다.
Redis의 데이터는 영속성을 위한 용도가 아니므로 특별히 필요한 경우가 아니면 위 방법처럼 사용하지 않고, key 생성 시 prefix를 붙여서 유니크한 key로 구분하는 것이 더 나은 방법일 것이다.
아직 Redis를 살펴보고 있는 중이고, 프로젝트 진행에 필요한 기능을 테스트해 보는 수준으로 사용해 본 것이어서 깊이있는 내용의 글은 아니다. 추후 Redis를 더 공부하고 사용해 보면서 더 좋은 내용을 포스팅하도록 하겠다.
물론 모든 내용은 나를 위한 것들일테지만...
스프링 부트에서 다중 DB 설정에 관련하여 @Primary 설명 링크들
Spring Boot Multi Datasource 작성
Springboot Multi Datasource 설정 및 사용법
@Primary로 같은 우선순위로 있는 클래스가 여러개가 있을 시 그 중 가장 우선순위로 주입할 클래스 타입을 선택할 수 있다. 많이 사용되는 곳에 걸어주도록 하자.
@Qualifier는 @Autowired와 같이 쓰이며 여러개의 타입이 일치하는 bean객체가 있을 경우 @Qualifier 어노테이션의 유무를 확인한 후 조건에 만족하는 객체를 주입하게 된다.