Redis Cluster๋ฅผ ์ฌ์ฉํ ๋ Spring Boot์ Lettuce client๋ฅผ ์ค์ ํด ๋๋ฆฝ๋๋ค
Redis Cluster๋ฅผ ์ฌ์ฉํ ๋ Spring Boot์ Lettuce client๋ฅผ ์ค์ ํด ๋๋ฆฝ๋๋ค ๊ด๋ จ
๊ฐ์
Spring Boot์์ Redis์ ๋ช ๋ น์ด๋ฅผ ์คํํ๊ธฐ ์ํ์ฌ Lettuce ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ๊ธฐ๋ณธ์ผ๋ก ์ฌ์ฉํ๊ณ ์์ต๋๋ค. ์ด๋ ์๋ ์ค์ (Auto Configuration)์ ์ฌ์ฉํ๊ธฐ๋ณด๋ค ๋ช ๊ฐ์ง ์ค์ ๊ฐ์ ํ๋ํด์ ์ฌ์ฉํ๋ค๋ฉด ๋ณด๋ค ์์ ์ ์ผ๋ก ์ฌ๋ฌ๋ถ๋ค์ ์๋น์ค๋ฅผ ์ด์ํ ์ ์์ต๋๋ค.
Spring Boot ์ ํ๋ฆฌ์ผ์ด์ ์์ Redis๋ฅผ ์ฌ์ฉํ๊ธฐ ์ํ์ฌ ์ผ๋ฐ์ ์ผ๋ก๋ Spring-Boot-Starter-Data-Redis ์์กด์ฑ์ ์ถ๊ฐํด์ ์ฌ์ฉํฉ๋๋ค.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
Redis์ ๋ช
๋ น์ด๋ฅผ ์คํํ๋ ค๋ฉด ์๋ ์ค์ ๋ RedisTemplate ์คํ๋ง ๋น์ ์ฃผ์
ํ์ฌ ์ ๊ณตํ๋ ๋ฉ์๋๋ฅผ ์คํํฉ๋๋ค. ๊ทธ๋ฆฌ๊ณ Spring Boot์์ ์ ๊ณตํ๋ RedisAutoConfiguration
์ด ์๋ ์ค์ ์ ๋ด๋นํฉ๋๋ค. ๋ค์ RedisAutoConfiguration
์ ์ฝ๋์ ์๋ ์ค์ ์ด ๋์ํ๋ ์กฐ๊ฑด์ ์ดํด๋ณด๊ฒ ์ต๋๋ค.
@AutoConfiguration
@ConditionalOnClass(RedisOperations.class) // ---------- (1)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {
@Bean
@ConditionalOnMissingBean(name = "redisTemplate") // ---------- (2)
@ConditionalOnSingleCandidate(RedisConnectionFactory.class) // ---------- (3)
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
// ์๋ต
return template;
}
@Bean
@ConditionalOnMissingBean // ---------- (4)
@ConditionalOnSingleCandidate(RedisConnectionFactory.class) // ---------- (5)
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
return new StringRedisTemplate(redisConnectionFactory);
}
}
.RedisAutoConfiguration.java
์ ์ฃผ์์ ์ดํด๋ด
์๋ค.
- Spring-Data-Redis์ RedisOperation ํด๋์ค๊ฐ ํด๋์คํจ์ค์ ์๋ ์กฐ๊ฑด์ด๋ฉด
RedisAutoConfiguration
์ด ๋์ํ๋ค. - ์คํ๋ง ์ ํ๋ฆฌ์ผ์ด์ ์ RedisTemplate redisTemplate ์คํ๋ง ๋น์ด ์๋ ๊ฒฝ์ฐ
- ์คํ๋ง ์ ํ๋ฆฌ์ผ์ด์
์
RedisConnectionFactory
์คํ๋ง ๋น์ด ํ๋๋ง ์กด์ฌํ๋ ๊ฒฝ์ฐ (2)๋ฒ ํญ๋ชฉ๊ณผ ํจ๊ป ๋ง์กฑํ๋ฉดRedisTemplate
์คํ๋ง ๋น์ ์์ฑํ๋ค. - ์คํ๋ง ์ ํ๋ฆฌ์ผ์ด์ ์ StringRedisTemplate stringRedisTemplate ์คํ๋ง ๋น์ด ์๋ ๊ฒฝ์ฐ
- ์คํ๋ง ์ ํ๋ฆฌ์ผ์ด์
์
RedisConnectionFactory
์คํ๋ง ๋น์ด ํ๋๋ง ์กด์ฌํ๋ ๊ฒฝ์ฐ (4)๋ฒ ํญ๋ชฉ๊ณผ ํจ๊ป ๋ง์กฑํ๋ฉดStringRedisTemplate
์คํ๋ง ๋น์ ์์ฑํ๋ค.
๊ทธ๋ฌ๋ฏ๋ก ๊ฐ๋ฐ์๋ ์คํ๋ง ์ ํ๋ฆฌ์ผ์ด์
์ RedisConnectionFactory
์คํ๋ง ๋น์ ํ๋๋ง ์ค์ ํ๋ฉด ๋ฉ๋๋ค. ์๋ ์ค์ ์ ์ํด์ RedisTemplate redisTemplate, RedisTemplate stringRedisTemplate ์คํ๋ง ๋น์ด ์๋ ์์ฑ๋ฉ๋๋ค. RedisConnectionFactory
๋ Redis์ ์ ํ๋ฆฌ์ผ์ด์
์ฌ์ด์ Connection ๊ฐ์ฒด๋ฅผ ์์ฑํ๋ ๊ธฐ๋ฅ์ ์ ๊ณตํฉ๋๋ค. LettuceConnectionFactory
๋ RedisConnectionFactory
์ ๊ตฌํ ํด๋์ค๋ค ์ค ํ๋์ด๋ฉฐ, LettuceConnection
๊ฐ์ฒด๋ฅผ ์์ฑํ๋ ํฉํ ๋ฆฌ ํด๋์ค์
๋๋ค.
RedisConnectionFactory
์ค์ - LettuceConnectionFactory
Redis ํด๋ฌ์คํฐ์ ์ฐ๊ฒฐํ๊ธฐ ์ํ ์ํ RedisConnectionFactory
์คํ๋ง ๋น ์์ฑ ์ฝ๋๋ฅผ ํ์ธํด ๋ด
์๋ค. ์ ์ฒด ์ฝ๋๋ ๋ค์๊ณผ ๊ฐ์ผ๋ฉฐ ๊ฐ ์ค์ ์ ์์ธ ์ค๋ช
์ ๋ค์์ ๋ค์ ์ค๋ช
ํฉ๋๋ค. RedisConnectionFactory
๊ฐ์ฒด๋ฅผ ๊ฐ๋ฐ์๊ฐ ์ค์ ํ ์ ์๋ ๋ถ๋ถ์ ํฌ๊ฒ 4๋ถ๋ถ์ผ๋ก ๋๋ ์ ์์ต๋๋ค.
SocketOptions
: ์์ผ ์ค์ ClusterTopologyRefreshOptions
: Redis ํด๋ฌ์คํฐ ํ ํด๋ก์ง ์ ๋ณด๋ฅผ ํด๋ผ์ด์ธํธ์ ๋๊ธฐํํ๋ ์ค์ ClusterClientOptions
: SocketOptions + ClusterTopologyRefreshOptions๋ฅผ ์ฌ์ฉํ์ฌ ํด๋ผ์ด์ธํธ๋ฅผ ์ค์ LettuceClientConfiguration
: ClusterClientOptions๋ฅผ ์ฌ์ฉํ์ฌ Lettuce ํด๋ผ์ด์ธํธ๋ฅผ ์ค์
@Bean(name = "redisConnectionFactory")
public RedisConnectionFactory redisConnectionFactory() {
//----------------- (1) Socket Option
SocketOptions socketOptions = SocketOptions.builder()
.connectTimeout(Duration.ofMillis(100L))
.keepAlive(true)
.build();
//----------------- (2) Cluster topology refresh ์ต์
ClusterTopologyRefreshOptions clusterTopologyRefreshOptions = ClusterTopologyRefreshOptions
.builder()
.dynamicRefreshSources(true)
.enableAllAdaptiveRefreshTriggers()
.enablePeriodicRefresh(Duration.ofSeconds(30))
.build();
//----------------- (3) Cluster client ์ต์
ClusterClientOptions clusterClientOptions = ClusterClientOptions
.builder()
.pingBeforeActivateConnection(true)
.autoReconnect(true)
.socketOptions(socketOptions)
.topologyRefreshOptions(clusterTopologyRefreshOptions)
.maxRedirects(3).build();
//----------------- (4) Lettuce Client ์ต์
final LettuceClientConfiguration clientConfig = LettuceClientConfiguration
.builder()
.commandTimeout(Duration.ofMillis(150L))
.clientOptions(clusterClientOptions)
.build();
RedisClusterConfiguration clusterConfig = new RedisClusterConfiguration(redisClusterProperties.getNodes());
clusterConfig.setMaxRedirects(3);
clusterConfig.setPassword("password");
LettuceConnectionFactory factory = new LettuceConnectionFactory(clusterConfig, clientConfig);
//----------------- (5) LettuceConnectionFactory ์ต์
factory.setValidateConnection(false);
return factory;
}
์์ผ ์ต์
(SocketOptions.java
)
SocketOptions socketOptions = SocketOptions.builder()
.connectTimeout(Duration.ofMillis(100L))
.keepAlive(true)
.build();
Lettuce ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ๋ค๋ฉด Keep Alive ๊ธฐ๋ฅ์ ํ์ฑํํ๊ณ Connection timeout์ ์ค์ ํ๋ ๊ฒ์ ์ถ์ฒํฉ๋๋ค.
keepAlive
์ต์
์ ํ์ฑํ(keepAlive(true)
)ํ๋ฉด, ์ ํ๋ฆฌ์ผ์ด์
๋ฐํ์ ์ค์ ์คํจํ ์ฐ๊ฒฐ์ ์ฒ๋ฆฌํด์ผ ํ ์ํฉ์ด ์ค์ด๋ญ๋๋ค. ์ด ์์ฑ์ TCP Keep Alive ๊ธฐ๋ฅ์ ์ค์ ํฉ๋๋ค. TCP Keep Alive๋ ๋ค์๊ณผ ๊ฐ์ ํน์ฑ์ ๊ฐ์ง๋๋ค.
- TCP Keep Alive๋ฅผ ์ผ๋ฉด ์ค๋ซ๋์ ๋ฐ์ดํฐ๋ฅผ ์ ์กํ์ง ์์๋, TCP Connection์ด ํ์ฑ๋ ์ํ๋ก ์ ์ง๋ฉ๋๋ค.
- TCP Keep Alive๋ ์ฃผ๊ธฐ์ ์ผ๋ก ํ๋ก๋ธ(Probe)๋ ๋ฉ์์ง๋ฅผ ์ ์กํ๊ณ Acknowledgment๋ฅผ ์์ ํฉ๋๋ค.
- ๋ง์ฝ Acknowledgment๊ฐ ์ฃผ์ด์ง ์๊ฐ์ ์ค์ง ์๋๋ค๋ฉด, TCP Connection์ ๋์ด์ง ๊ฑธ๋ก ๊ฐ์ฃผ๋์ด ์ข ๋ฃ๋ฉ๋๋ค.
Java ์ ํ๋ฆฌ์ผ์ด์ ์์ TCP Keep Alive๋ฅผ ํ์ฑํํ๊ธฐ ์ํด์๋ ๋ช ๊ฐ์ง ์กฐ๊ฑด์ด ํ์ํฉ๋๋ค. ๋ค์์ ์ฐธ๊ณ ํ์๊ธธ ๋ฐ๋๋๋ค.
- Java 11 ๋๋ ๊ทธ ์ด์์ epoll์ ์ฌ์ฉํ๋ NIO Socket์ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ ๊ฐ๋ฅ
- Java 10์ด๋ ์ด์ ๋ฒ์ ์ epoll์ ์ฌ์ฉํ๋ NIO Socket์ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ ๋ถ๊ฐ๋ฅ
- kqueue๋ ๋ถ๊ฐ๋ฅ
- ์ฐธ๊ณ : https://lettuce.io/core/release/api/io/lettuce/core/SocketOptions.KeepAliveOptions.html
ConnectionTimeout
์ ์ค์ ๋ ์๊ฐ ๊ฐ(connectTimeout(Duration.ofMillis(100L))
)์ ์ ํ๋ฆฌ์ผ์ด์
๊ณผ Redis ์ฌ์ด์ LettuceConnection
์ ์์ฑํ๋ ์๊ฐ ์ด๊ณผ ๊ฐ์
๋๋ค. ์ผ๋ฐ์ ์ผ๋ก Redis์ ์ ํ๋ฆฌ์ผ์ด์
์ ๋ด๋ถ ๋คํธ์ํฌ๋ฅผ ์ฌ์ฉํ๊ณ ์์ผ๋ฏ๋ก ์ปค๋ฅ์
์ ์์ฑํ๋ ์๊ฐ์ ์งง๊ฒ ๋์ด๋ ๋ฌด๋ฐฉํฉ๋๋ค. ์์ ์์๋ 100ms๋ก ์ค์ ํ์ต๋๋ค. connectionTimeout
์ ๋ค์์ ์ค๋ช
ํ command timeout๊ณผ ๊ฐ์ด ๋ฐ๋์ ์ค์ ํด์ผ ํ๋ ๊ฐ์
๋๋ค.
๋คํธ์ํฌ ๋๋ Redis์ ๋ฌธ์ ๊ฐ ๋ฐ์ํ์ฌ Redis ๋ช ๋ น์ด๋ฅผ ๋น ๋ฅด๊ฒ ์คํํ ์ ์๋ค๋ฉด ์ ํ๋ฆฌ์ผ์ด์ ์ฒ๋ฆฌ๋๊น์ง ๋๋ ค์ง ์ ์์ต๋๋ค. ๋ ์ค์ ๊ฐ์ ๋๋ฌด ํฌ๊ฒ ์ก์ง ์๋๋ค๋ฉด(์: 1์ด ์ด์) Redis๋ ๋คํธ์ํฌ์ ๋ฌธ์ ๊ฐ ๋ฐ์ํ์ ๋ ๋น ๋ฅด๊ฒ ์์ธ(Exception)๋ฅผ ๋ฐ์์ํฌ ์ ์์ต๋๋ค. ๊ทธ๋์ ์ ํ๋ฆฌ์ผ์ด์ ์ด ์ฐ์์ ์ธ ์ฅ์ ์ ๋น ์ง์ง ์๊ฒ, ์์คํ ์ ๊ฒฉ๋ฆฌ/๋ณดํธํ๋ ์ ๋ต๋ ๊ณ ๋ คํด ๋ณผ ์ ์์ต๋๋ค. ๋น์ฆ๋์ค ๋ก์ง์ ๋ฐ๋ผ์ ๋น ๋ฅธ ์คํจ๊ฐ ์์คํ ์ ์ฒด๋ฅผ ๋ณดํธํ ์ ์์ต๋๋ค.
ํด๋ฌ์คํฐ ํ ํด๋ก์ง ๊ฐฑ์ ์ต์
(ClusterTopologyRefreshOptions
)
ClusterTopologyRefreshOptions clusterTopologyRefreshOptions = ClusterTopologyRefreshOptions
.builder()
.dynamicRefreshSources(true) // ๋ชจ๋ Redis ๋
ธ๋๋ก๋ถํฐ topology ์ ๋ณด ํ๋. default = true
.enableAllAdaptiveRefreshTriggers() // Redis ํด๋ฌ์คํฐ์์ ๋ฐ์ํ๋ ๋ชจ๋ ์ด๋ฒคํธ(MOVE, ACK)๋ฑ์ ๋ํด์ topology ๊ฐฑ์
.enablePeriodicRefresh(Duration.ofSeconds(30)) // ์ฃผ๊ธฐ์ ์ผ๋ก ํ ํด๋ก์ง๋ฅผ ๊ฐฑ์ ํ๋ ์๊ฐ
.build();
Redis ํด๋ฌ์คํฐ๋ 3๊ฐ ์ด์์ Redis ๋ ธ๋๋ค๋ก ๊ตฌ์ฑ๋์ด ์์ต๋๋ค. Redis ํด๋ฌ์คํฐ์ ๋ ธ๋๋ฅผ ์ถ๊ฐ/์ญ์ ๋๋ Master ์น๊ฒฉ ๊ฐ์ ์ด๋ฒคํธ๊ฐ ๋ฐ์ํ๋ฉด ํ ํด๋ก์ง๊ฐ ๋ณ๊ฒฝ๋ฉ๋๋ค. Redis ํด๋ฌ์คํฐ๋ฅผ ์ฐ๊ฒฐ๋ ํด๋ผ์ด์ธํธ ์ ํ๋ฆฌ์ผ์ด์ ์ ์ต์ ์ Redis ํด๋ฌ์คํฐ ์ ๋ณด๋ฅผ ๋๊ธฐํํฉ๋๋ค. ๊ทธ๋์ ํด๋ผ์ด์ธํธ ์ ํ๋ฆฌ์ผ์ด์ ์ด ์ด๋ค ๋ ธ๋์ ๋ฐ์ดํฐ๋ฅผ ์กฐํ/์์ฑ/์ญ์ ํ ์ง ๋ฏธ๋ฆฌ ์๊ณ ์์ต๋๋ค. ClusterTopologyRefreshOptions๋ Redis ํด๋ฌ์คํฐ ํ ํด๋ก์ง์ ๋ณ๊ฒฝ์ด ๋ฐ์ํ์ ๋ ํด๋ผ์ด์ธํธ ์ ํ๋ฆฌ์ผ์ด์ ์ด ๊ฐ์ง ํ ํด๋ก์ง ๊ฐฑ์ ๊ณผ ๊ด๋ จ๋ ์ค์ ๊ธฐ๋ฅ์ ์ ๊ณตํฉ๋๋ค.
enablePeriodicRefresh()
์ ์๊ฐ ์ธ์๋ ํด๋ผ์ด์ธํธ ์ ํ๋ฆฌ์ผ์ด์
์ด Redis ํ ํด๋ก์ง๋ฅผ ๊ฐฑ์ ํ๋ ์ฃผ๊ธฐ๋ฅผ ์ค์ ํฉ๋๋ค. ํ์ง๋ง dynamicRefreshSources()
, enableAllAdaptiveRefreshTriggers()
๋ Redis ํด๋ฌ์คํฐ์์ ๋ฐ์ํ๋ ์ด๋ฒคํธ๋ฅผ ํด๋ผ์ด์ธํธ ์ ํ๋ฆฌ์ผ์ด์
์ด ์์ ํ์ฌ ํ ํด๋ก์ง๋ฅผ ๊ฐฑ์ ํ๋ ์ฐจ์ด๊ฐ ์์ต๋๋ค.
๋ง์ฝ ํด๋ผ์ด์ธํธ ์ ํ๋ฆฌ์ผ์ด์
์ ํ ํด๋ก์ง ์ ๋ณด๊ฐ ์
๋ฐ์ดํธ๋์ง ์์ ์๋ชป๋ ๋
ธ๋์ ๋ช
๋ น์ด๋ฅผ ์คํํด๋ ๋ฌธ์ ์์ต๋๋ค. Redis ๋
ธ๋๋ค ๋ํ ํ ํด๋ก์ง ์ ๋ณด๋ฅผ ์
๋ฐ์ดํธํ๊ณ ์์ผ๋ฉฐ, MOVED
์๋ต์ผ๋ก ํด๋น ๋ฐ์ดํฐ๊ฐ ์ ์ฅ๋ ์ ํํ ๋
ธ๋๋ฅผ ์๋ตํฉ๋๋ค.
127.0.0.1:7100> get session:12351712:member:123123123
(error) MOVED 7879 127.0.0.1:7200
enablePeriodicRefresh()
์ ๊ธฐ๋ณธ๊ฐ์ 60์ด์
๋๋ค. ์ด ์ต์
์ด ๋นํ์ฑํ๋๋ฉด ํด๋ผ์ด์ธํธ ์ ํ๋ฆฌ์ผ์ด์
์ ํด๋ฌ์คํฐ์ ๋ช
๋ น์ ์คํํ๊ณ ์ค๋ฅ๊ฐ ๋ฐ์ํ ๋๋ง ํด๋ฌ์คํฐ ํ ํด๋ก์ง๋ฅผ ์
๋ฐ์ดํธํฉ๋๋ค. ๋๊ท๋ชจ์ Redis ํด๋ฌ์คํฐ๋ฅผ ์ฌ์ฉํ๊ณ ์๋ค๋ฉด ๋ฆฌํ๋ ์ ์ฃผ๊ธฐ๋ฅผ ๊ธธ๊ฒ ๊ฐ์ ธ๊ฐ๋ ๊ฒ์ด ์ข์ต๋๋ค. ๊ฐฑ์ ์๊ฐ ๊ฐ์ด ์งง๊ณ Redis ํด๋ฌ์คํฐ์ ๋
ธ๋ ์๊ฐ ๋ง์ ํด๋ผ์ด์ธํธ ์ ํ๋ฆฌ์ผ์ด์
์ด ์์ฃผ ํ ํด๋ก์ง๋ฅผ ๊ฐฑ์ ํ๋ค๋ฉด, Redis ํด๋ฌ์คํฐ ์ ์ฒด์ ๋ถํ๊ฐ ๋ ์ ์์ต๋๋ค.
enableAllAdaptiveRefreshTriggers()
๋ Redis ํด๋ฌ์คํฐ์์ ๋ฐํํ๋ ๋ชจ๋ ํธ๋ฆฌ๊ฑฐ์ ๋ํด์ ํ ํด๋ก์ง๋ฅผ ๊ฐฑ์ ํฉ๋๋ค. ํธ๋ฆฌ๊ฑฐ๋ MOVED_REDIRECT
, ASK_REDIRECT
, PERSISTENT_RECONNECTS
, UNCOVERED_SLOT
, UNKNOWN_NODE
๋ฑ์ด ๋ ์ ์์ต๋๋ค.
dynamicRefreshSources()
์ ๊ธฐ๋ณธ ๊ฐ์ true์
๋๋ค. ์๊ท๋ชจ ํด๋ฌ์คํฐ์๋ DynamicRefreshResources
๋ฅผ ํ์ฑํํ๊ณ ๋๊ท๋ชจ ํด๋ฌ์คํฐ์๋ ๋นํ์ฑํํ๋ ๊ฒ์ด ์ข์ต๋๋ค. ์ด ์ค์ ์ด false์ด๋ฉด Redis ํด๋ผ์ด์ธํธ๋ seed ๋
ธ๋์๋ง ์ง์ํ์ฌ ์๋ก์ด ๋
ธ๋๋ฅผ ์ฐพ๋ ๋ฐ ์ฌ์ฉํฉ๋๋ค. ์ด ๊ฒฝ์ฐ ๋ฌธ์ ๊ฐ ์๋ ๋
ธ๋๊ฐ ํด๋ผ์ด์ธํธ ์ ํ๋ฆฌ์ผ์ด์
์ ํ ํด๋ก์ง ์ ๋ณด์์ ์ ์ธ๋๋ ๋ฐ ์๊ฐ์ด ์์๋ฉ๋๋ค. ์ด ์ค์ ์ด true์ด๋ฉด Redis ํด๋ผ์ด์ธํธ๋ ๋ชจ๋ Redis ํด๋ฌ์คํฐ ๋
ธ๋์๊ฒ ์ง์ํ์ฌ ๊ฒฐ๊ณผ๋ฅผ ๋น๊ตํฉ๋๋ค. ๊ทธ๋์ ์๋ก์ด ์ ๋ณด๋ก ํ ํด๋ก์ง๋ฅผ ์
๋ฐ์ดํธํฉ๋๋ค. ๊ทธ๋ฌ๋ฏ๋ก ๋๊ท๋ชจ Redis ํด๋ฌ์คํฐ์๋ DynamicRefreshResources
๊ธฐ๋ฅ์ ๋๋ ๊ฒ์ ์ถ์ฒํฉ๋๋ค.
ํด๋ฌ์คํฐ ํด๋ผ์ด์ธํธ ์ต์
(ClusterClientOptions
)
ClusterClientOptions clusterClientOptions = ClusterClientOptions
.builder()
.pingBeforeActivateConnection(true) // ์ปค๋ฅ์
์ ์ฌ์ฉํ๊ธฐ ์ํ์ฌ PING ๋ช
๋ น์ด๋ฅผ ์ฌ์ฉํ์ฌ ๊ฒ์ฆํฉ๋๋ค.
.autoReconnect(true) // ์๋ ์ฌ์ ์ ์ต์
์ ์ฌ์ฉํฉ๋๋ค.
.socketOptions(socketOptions) // ์์ ์์ฑํ socketOptions ๊ฐ์ฒด๋ฅผ ์ธํ
ํฉ๋๋ค.
.topologyRefreshOptions(clusterTopologyRefreshOptions) // ์์ ์์ฑํ clusterTopologyRefreshOptions ๊ฐ์ฒด๋ฅผ ์์ฑํฉ๋๋ค.
.maxRedirects(3).build();
maxRedirects()
์ต์
์ Redis ํด๋ฌ์คํฐ๊ฐ MOVED_REDIRECT
๋ฅผ ์๋ตํ ๋ ํด๋ผ์ด์ธํธ ์ ํ๋ฆฌ์ผ์ด์
์์ Redirectํ๋ ์ต๋ ํ์๋ฅผ ์ค์ ํฉ๋๋ค.
Redis ํด๋ผ์ด์ธํธ๋ Redis ํ ํด๋ก์ง ์ ๋ณด๋ฅผ ๋๊ธฐํํ๊ณ ์์ต๋๋ค. ๊ฐ Redis ๋
ธ๋์ ๋ง์คํฐ/์ฌ๋ ์ด๋ธ ์ ๋ณด์ IP, ๊ทธ๋ฆฌ๊ณ ๋ฐ์ดํฐ๋ฅผ ๋ถ๋ฐฐํ๋ ์ ๋ณด์ธ ์ฌ๋กฏ ๋ฒ์๋ฅผ ๋๊ธฐํํฉ๋๋ค. ๋ง์ฝ Redis ํด๋ผ์ด์ธํธ๊ฐ ํ ํด๋ก์ง ์
๋ฐ์ดํธ์ ์คํจํ๊ฑฐ๋ ๋๊ธฐํํ์ง ๋ชปํ ๊ฒฝ์ฐ, ์๋ชป๋ ๋
ธ๋์ Redis ๋ช
๋ น์ ์คํํ ์ ์์ต๋๋ค. ์ด ๊ฒฝ์ฐ Redis๋ ์คํจ(MOVED_REDIRECT
)๋ฅผ ์๋ตํ๊ณ ํด๋ผ์ด์ธํธ๋ ์ ์ ํ ๋
ธ๋๋ก ๋ฆฌ๋ค์ด๋ ์
ํ ์ ์์ต๋๋ค. ๋ง์ฝ Redis ํด๋ฌ์คํฐ๊ฐ 3๋์ ๋
ธ๋๋ก ๊ตฌ์ฑ๋์ด ์๋ค๋ฉด maxRedirects
๊ฐ์ 3์ผ๋ก ์ค์ ํ๋ค๊ณ ์๊ฐํด ๋ด
์๋ค. ์ด ๊ฒฝ์ฐ ํด๋ผ์ด์ธํธ ์ ํ๋ฆฌ์ผ์ด์
์ด ์คํํ ๋ช
๋ น์ด๊ฐ ์คํจํ ํ๋ฅ ์ ๋งค์ฐ ์ค์ด๋ญ๋๋ค.
LettuceClientConfiguration
final LettuceClientConfiguration clientConfig = LettuceClientConfiguration
.builder()
.commandTimeout(Duration.ofMillis(150L)) // ----------- ๋ช
๋ น์ด ํ์์์ ์ค์ .
.clientOptions(clusterClientOptions)
.build();
Lettuce ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ ์ง์ฐ ์ฐ๊ฒฐ์ ์ฌ์ฉํ๊ณ ์์ผ๋ฏ๋ก, Command Timeout ๊ฐ์ด Connection Timeout ๊ฐ๋ณด๋ค ์ปค์ผ ํฉ๋๋ค. ์์ ์์๋ Command Timeout์ 150ms๋ก ์ค์ ํ์ผ๋ฉฐ, ์์ ์ค์ ํ SocketOptions
์ Connection Timeout ๊ฐ์ 100ms๋ก ์ค์ ํ์ต๋๋ค.
DynamicTimeout
Lettuce์์๋ Redis ๋ช
๋ น์ด๋ง๋ค ๋ณ๋์ Timeout์ ์ค์ ํ ์ ์์ต๋๋ค. FLUSHDB, FLUSHALL, KEYS, SMEMBERS ๋๋ Lua ์คํฌ๋ฆฝํธ์ ๊ฐ์ด ์ฌ๋ฌ ํค๋ฅผ ๋ฐ๋ณตํ๋ ๋ช
๋ น์ด์๋ ๋ ๊ธด command timeout์ ์ค์ ํ ์ ์์ต๋๋ค. ๋ฐ๋๋ก SET
, GET
, HSET
๋ฑ ๋จ์ผ ํค ๋ช
๋ น์ด์ ๋ํด์๋ ์๋์ ์ผ๋ก ์งง์ command timeout์ ์ค์ ํ ์ ์์ต๋๋ค. Lettuce ๋ผ์ด๋ธ๋ฌ๋ฆฌ์์๋ io.lettuce.core.TimeoutOptions.TimeoutSource
์ถ์ ํด๋์ค๋ฅผ ์ ๊ณตํ๊ณ ์์ผ๋ฉฐ, ๋ค์๊ณผ ๊ฐ์ด ์ปค์คํ
ํด๋์ค๋ฅผ ์์ฑํ ์ ์์ต๋๋ค.
public class DynamicCommandTimeout extends TimeoutOptions.TimeoutSource {
private static final Set<ProtocolKeyword> META_COMMAND_TYPES =
ImmutableSet.<ProtocolKeyword>builder()
.add(CommandType.FLUSHDB)
.add(CommandType.FLUSHALL)
.add(CommandType.CLUSTER)
.add(CommandType.INFO)
.add(CommandType.KEYS)
.build();
private final Duration defaultCommandTimeout;
private final Duration metaCommandTimeout;
// META_COMMAND_TYPES์ ์ ์๋ ๋ช
๋ น์ด๋ metaTimeout์ ์ค์ ํ๊ณ
// ๋๋จธ์ง ๋ช
๋ น์ด๋ defaultTimeout์ ์ค์ ํฉ๋๋ค.
DynamicCommandTimeout(Duration defaultTimeout, Duration metaTimeout) {
defaultCommandTimeout = defaultTimeout;
metaCommandTimeout = metaTimeout;
}
@Override
public long getTimeout(RedisCommand<?, ?, ?> command) {
if (META_COMMAND_TYPES.contains(command.getType())) {
return metaCommandTimeout.toMillis();
}
return defaultCommandTimeout.toMillis();
}
}
์์ ์ DynamicCommandTimeout
ํด๋์ค๋ FLUSHDB
, FLUSHALL
, CLUSTER
, INFO
, KEYS
๋ช
๋ น์ด์ ๋ํด์ Duration metaTimeout ๊ฐ์ ์ค์ ํ๋ ๊ตฌ์กฐ์
๋๋ค. ๋ค์์ DynamicCommandTimeout
๊ฐ์ฒด๋ฅผ ๋ง๋ค์ด์ ์ค์ ํ๋ ๋ฐฉ๋ฒ์ ์ค๋ช
ํฉ๋๋ค.
- ๋จผ์
TimeoutOptions.Builder
๊ฐ์ฒด๋ฅผ ์์ฑํ๊ณtimeoutSource()
๋ฉ์๋๋ฅผ ์ฌ์ฉํ์ฌDynamicCommandTimeout
๊ฐ์ฒด๋ฅผ ์ฃผ์ ํฉ๋๋ค. DynamicCommandTimeout
๊ฐ์ฒด๋ ๊ธฐ๋ณธ timeout ๊ฐ์ผ๋ก 100ms๋ฅผ ์ค์ ํ๊ณ ,META_COMMAND_TYPES
์ ์ ์๋ ๋ช ๋ น์ด์ ๋ํด์๋ 300ms๋ก ์ค์ ํ ์์ ์ ๋๋ค.
// ์์ ์์ฑํ ํด๋์ค๋ฅผ DynamicCommandTimeout ๊ฐ์ฒด
TimeoutOptions timeoutOptions = TimeoutOptions.builder()
.timeoutSource(new DynamicCommandTimeout(Duration.ofMillis(100L),Duration.ofMillis(300L)))
.build();
ClusterClientOptions clusterClientOptions = ClusterClientOptions
.builder()
//... ์๋ต
.timeoutOptions(timeoutOptions) // ์์ฑํ timeoutOptions๋ ๋ค์๊ณผ ๊ฐ์ด ์ค์ ํฉ๋๋ค.
.build();
DynamicConnection
ClusterClientOptions.Builder
ํด๋์ค์ nodeFilter()
๋ฉ์๋๋ ๋ฌธ์ ๊ฐ ๋ฐ์ํ ๋
ธ๋๋ฅผ Redis ํ ํด๋ก์ง์์ ์ ์ธํ๋ ๊ธฐ๋ฅ์ ์ ๊ณตํฉ๋๋ค. nodeFilter()
๋ฉ์๋๋ Predicate
๋ฅผ ์ธ์๋ก ๋ฐ์ผ๋ฏ๋ก ๋ค์ ์์ ์์๋ ๋๋ค์์ผ๋ก ๊ฐ๋จํ ํํ๋์ด ์์ต๋๋ค.
RedisClusterNode.NodeFlag
์ด๊ฑฐํ์ ์์ธํ ๋ด์ฉ์ ์ฌ๊ธฐ์์ ํ์ธํ๊ธธ ๋ฐ๋๋๋ค.
ClusterClientOptions clusterClientOptions =
ClusterClientOptions.builder()
... // other options
.nodeFilter(it ->
! (it.is(RedisClusterNode.NodeFlag.FAIL)
|| it.is(RedisClusterNode.NodeFlag.EVENTUAL_FAIL)
|| it.is(RedisClusterNode.NodeFlag.HANDSHAKE)
|| it.is(RedisClusterNode.NodeFlag.NOADDR)))
.validateClusterNodeMembership(false)
.build();
redisClusterClient.setOptions(clusterClientOptions);
Redis๊ฐ ์ฅ์ ์ํ์์ ๋ณต๊ตฌ๋๋ฉด Redis ํด๋ฌ์คํฐ๋ ๋ณต๊ตฌ ํ๋ก์ธ์ค๋ฅผ ์์ํฉ๋๋ค. ์ด๋ Redis ํด๋ฌ์คํฐ๋ ํ ํด๋ก์ง๋ฅผ ์๋ก ๊ฐฑ์ ํ๋ฉฐ, ์๋ ์ ๋๋ ๋
ธ๋(down node)๋ค์ ํ ํด๋ก์ง์์ ์ ๊ฑฐ๋ ๋ ๊น์ง๋ ์คํจ(FAIL) ์ํ๋ก ํ์๋ฉ๋๋ค. ์ด ๊ธฐ๊ฐ๋์ Redis ํด๋ผ์ด์ธํธ๋ ์ด๋ฅผ ์ ์ ๋
ธ๋(healthy node)๋ก ํ๋จํ๋ฉฐ ๊ณ์ํด์ ์ฐ๊ฒฐํ๋ ค๊ณ ํฉ๋๋ค.
validateClusterNodeMembership์ ํด๋ฌ์คํฐ ๋
ธ๋์ ์ฐ๊ฒฐ ์ ์ ํจํ ๋
ธ๋์ธ์ง ํ์ธํ๋ ์ต์
์ด๋ฏ๋ก ์ ์ ๋
ธ๋๋ก ์ฐฉ๊ฐํ์ฌ ๊ฒ์ฆํ์ง ์๋๋ก false๋ก ์ค์ ํฉ๋๋ค.