프로그래밍 노트/SPRING BOOT
[Spring Boot] RedisCluster 연동 & RedisTemplate 적용 (feat. AutoConfiguration)
깡냉쓰
2024. 10. 22. 11:34
728x90
반응형
Redis 의존성 추가
spring-boot-starter-data-redis 의존성 추가
implementation("org.springframework.boot:spring-boot-starter-data-redis")
SpringBoot에서 외부 라이브러리 버전을 관리하기에, spring-boot-starter-data-redis 를 추가하면 해당 SpringBoot버전에서 관리하는 외부라이브러리가 자동으로 추가된다.
나는 현재 SpringBoot 3.2.4 버전을 사용하여 lettuce 6.3.2.RELEASE 버전이 자동 추가된 것을 볼 수 있다.
관리되는 버전은 https://docs.spring.io/spring-boot/docs/3.2.4/reference/html/dependency-versions.html 에서 확인 가능하다.
RedisAutoConfiguration
Autoconfigure 에서 Spring Data's Redis를 support 해준다.
RedisAutoConfiguration.class 를 살펴보면 Bean이 자동 설정되는 조건들을 볼 수 있다.
@AutoConfiguration
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {
@Bean
@ConditionalOnMissingBean(RedisConnectionDetails.class)
PropertiesRedisConnectionDetails redisConnectionDetails(RedisProperties properties) {
return new PropertiesRedisConnectionDetails(properties);
}
@Bean
@ConditionalOnMissingBean(name = "redisTemplate")
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
return new StringRedisTemplate(redisConnectionFactory);
}
}
- RestTemplate은 RedisConnectionFactory Bean이 존재하면 자동 등록된다.
- RedisConnectionFactory는 RedisAutoConfiguration이 @Import하고 있는 LettuceConnectionConfiguration.class 에서 자동 생성해주지만 우리는 RedisCluster관련한 설정이 추가적으로 필요하므로 재정의하도록 한다.
- LettuceConnectionFactory(RedisConnectionFactory 구현체)를 custom bean으로 등록하면 RedisTemplate은 자동등록 되므로 해당 설정만 Configuration에 추가해준다.
LettuceConnectionFactory Bean 정의(feat. RedisCluster)
RedisCluster 연동시, fail-over를 위한 설정들이 몇 가지 필요하다. 설정 관련해서는 아래 글들을 참고했다.
@Configuration
class RedisConfig {
@Bean
fun redisConnectionFactory(
@Value("\${redis.node-list}") redisNodeList: String,
@Value("\${redis.connection-timeout-millis}") connectionTimeoutMillis: Long,
@Value("\${redis.operation-timeout-millis}") operationTimeoutMillis: Long,
@Value("\${redis.request-queue-size}") requestQueueSize: Int,
): RedisConnectionFactory {
val nodeList: List<String> = redisNodeList.split(",").toList()
// (1) Socket Option
val socketOptions =
SocketOptions
.builder()
.connectTimeout(Duration.ofMillis(connectionTimeoutMillis))
.keepAlive(true)
.build()
// (2) Cluster topology refresh
val clusterTopologyRefreshOptions =
ClusterTopologyRefreshOptions
.builder()
.dynamicRefreshSources(true) // 모든 Redis 노드로부터 topology 정보 획득
.enableAllAdaptiveRefreshTriggers() // Redis 클러스터 모든 이벤트(MOVE, ACK)등에 대해 topology 갱신
.enablePeriodicRefresh(Duration.ofSeconds(30)) // 토폴로지 갱신 텀
.refreshTriggersReconnectAttempts(1) // 한 번이라도 실패가 일어나면 토폴로지 갱신
.adaptiveRefreshTriggersTimeout(Duration.ofSeconds(30)) // 토폴로지 업데이터가 일어난 경우 30초 이내 재갱신 x
.build()
// (3) Cluster client
val clusterClientOptions =
ClusterClientOptions
.builder()
.pingBeforeActivateConnection(true) // 커넥션을 사용하기 위하여 PING 명령어를 사용하여 검증 default : true
.autoReconnect(true) // 자동 재접속 옵션 default : true
.socketOptions(socketOptions)
.topologyRefreshOptions(clusterTopologyRefreshOptions)
.maxRedirects(nodeList.size)
.requestQueueSize(requestQueueSize)
.disconnectedBehavior(ClientOptions.DisconnectedBehavior.REJECT_COMMANDS)
.build()
// (4) Lettuce Client
val lettuceClientConfig =
LettuceClientConfiguration
.builder()
.commandTimeout(Duration.ofMillis(operationTimeoutMillis))
.clientOptions(clusterClientOptions)
.build()
val clusterConfig =
RedisClusterConfiguration(nodeList).apply {
this.maxRedirects = nodeList.size
}
// (5) LettuceConnectionFactory
return LettuceConnectionFactory(clusterConfig, lettuceClientConfig).apply {
this.validateConnection = false // 연결 검증 비활성화 default : false
}
}
}
RedisTemplate Bean이 자동 등록됬는지 테스트
@SpringBootTest(
classes = [RedisConfig::class],
)
@ImportAutoConfiguration(RedisAutoConfiguration::class)
class RedisConfigTest(
private val applicationContext: ApplicationContext,
) : FreeSpec({
extensions(SpringExtension)
"RedisTemplate이 AutoConfiguration에 의해 자동 등록 된다." {
applicationContext.getBean("redisTemplate") shouldNotBe null
applicationContext.getBean("stringRedisTemplate") shouldNotBe null
}
})
RedisTemplate이 정상 동작하는지 테스트
@SpringBootTest(
classes = [RedisConfig::class],
)
@ImportAutoConfiguration(RedisAutoConfiguration::class)
class RedisTemplateTest(
private val redisTemplate: RedisTemplate<Any, Any>,
) : FreeSpec({
extensions(SpringExtension)
beforeTest {
redisTemplate.delete("key")
}
"redisTemplate Test" - {
"get() - key 미존재시 null 반환" {
redisTemplate.opsForValue().get("key") shouldBe null
}
"delete() - key 존재시 true 반환" {
redisTemplate.opsForValue().set("key", "value")
redisTemplate.delete("key") shouldBe true
}
"delete() - key 미존재시 false 반환" {
redisTemplate.delete("key") shouldBe false
}
"setIfAbsent() - key 존재시 false(이미 존재) 반환" {
redisTemplate.opsForValue().set("key", "value")
val result = redisTemplate.opsForValue().setIfAbsent("key", "newValue")
result shouldBe false
redisTemplate.opsForValue().get("key") shouldBe "value"
}
"setIfAbsent() - key 미존재시 true 반환" {
val result = redisTemplate.opsForValue().setIfAbsent("key", "value")
result shouldBe true
redisTemplate.opsForValue().get("key") shouldBe "value"
}
}
})
728x90
반응형