redis cluster with springboot

greenTeaΒ·2023λ…„ 7μ›” 21일
2

Redis cluster μŠ€ν”„λ§ λΆ€νŠΈ μ—°κ²°

πŸ€”μŠ€ν”„λ§ λΆ€νŠΈμ—μ„œ redis clusterλ₯Ό μ—°κ²°ν•˜λŠ” λ°©λ²•μ—λŠ” μ—¬λŸ¬κ°€μ§€κ°€ μžˆμŠ΅λ‹ˆλ‹€. μ—¬κΈ°μ„œλŠ” 기본적인 λ°©μ‹μœΌλ‘œ μ—°κ²°ν•΄λ³΄κ² μŠ΅λ‹ˆλ‹€.


1. RedisInfo μ„€μ •

😐redisλ₯Ό μ—°κ²°ν•˜κΈ° μœ„ν•΄μ„œ λ¨Όμ € redis에 κ΄€ν•œ 정보λ₯Ό μŠ€ν”„λ§ λΆ€νŠΈμ— 전달을 ν•΄μ£Όμ–΄μ•Ό ν•©λ‹ˆλ‹€.
λ¨Όμ € yml에 ν•΄λ‹Ή 정보듀을 λ„£μ–΄μ£Όκ² μŠ΅λ‹ˆλ‹€.

spring:
  data:
    redis:
      cluster:
        max-redirects: 3
        password: 1111
        connect-ip: 172.17.0.1
        nodes: 172.17.0.1:7000, 172.17.0.1:7001,172.17.0.1:7002,172.17.0.1:7003,172.17.0.1:7004,172.17.0.1:7005

πŸ« μ—¬κΈ°μ„œ nodes의 경우 μžμ‹ μ΄ μ‚¬μš©ν•˜λŠ” redis의 μ£Όμ†Œλ₯Ό λ„£μ–΄μ£Όλ©΄ λ©λ‹ˆλ‹€.(μ €λŠ” 도컀λ₯Ό ν™œμš©ν•˜μ—¬ λ„μš°κ³  μžˆκΈ°μ— μœ„μ™€ 같은 μ£Όμ†Œλ₯Ό μ‚¬μš©ν•˜μ˜€μŠ΅λ‹ˆλ‹€.)

이제 RedisInfo classλ₯Ό λ§Œλ“€μ–΄μ„œ ν•΄λ‹Ή 정보듀을 λ„˜κ²¨ μ£Όκ² μŠ΅λ‹ˆλ‹€.

@Getter
@Setter
@NoArgsConstructor
@ConfigurationProperties(prefix = "spring.data.redis.cluster")
@Configuration
public class RedisInfo {
    private int maxRedirects;
    private String password;
    private String connectIp;
    private List<String> nodes;
}

πŸ§ν”„λ‘œνΌν‹° 값듀을 ν•΄λ‹Ή ν΄λž˜μŠ€κ°€ 받을 수 μžˆλ„λ‘ @ConfigurationProperties(prefix = "spring.data.redis.cluster")λ₯Ό μ‚¬μš©ν•˜μ˜€μŠ΅λ‹ˆλ‹€.
이제 이 값을 μ΄μš©ν•˜μ—¬ config classλ₯Ό μ™„μ„±ν•˜κ² μŠ΅λ‹ˆλ‹€.


2. RedisClusterConfiguration

πŸ˜Žμœ„μ—μ„œ λ°›μ•„μ˜¨ 값듀을 redis μ„€μ • κ°’μœΌλ‘œ μ΄μš©ν•  수 있게 ν•΄λ‹Ή 값듀을 λ„˜κ²¨μ£Όκ² μŠ΅λ‹ˆλ‹€. RedisClusterConfiguration classλ₯Ό 톡해 ν•΄λ‹Ή 값듀을 λ„˜κ²¨μ£Όλ©΄ 기본적인 섀정이 μ™„λ£Œλ©λ‹ˆλ‹€.

@RequiredArgsConstructor
@Configuration
public class RedisProdConfig {
    private final RedisInfo redisInfo;
     @Bean
    public RedisConnectionFactory redisConnectionFactory() {
        RedisClusterConfiguration redisClusterConfiguration = new RedisClusterConfiguration(redisInfo.getNodes());
        redisClusterConfiguration.setPassword(redisInfo.getPassword());
        redisClusterConfiguration.setMaxRedirects(redisInfo.getMaxRedirects());
	}
    

new RedisClusterConfiguration(redisInfo.getNodes());λ₯Ό 톡해 ν•΄λ‹Ή redis nodesλ“€μ˜ μ£Όμ†Œλ₯Ό λ„˜κ²¨μ€λ‹ˆλ‹€.
setPassword()λ₯Ό 톡해 passwordλ₯Ό μ„€μ •ν•΄μ£ΌλŠ”λ° passwordλ₯Ό μ„€μ •ν•˜μ§€ μ•Šμ•˜λ‹€λ©΄ μž‘μ„±ν•˜μ§€ μ•ŠμœΌμ…”λ„ λ©λ‹ˆλ‹€.

redirectλ₯Ό μ„€μ •ν•œ μ΄μœ μ— λŒ€ν•΄μ„œλŠ” λ¨Όμ € redis clsuter에 λŒ€ν•΄μ„œ μ•„μ…”μ•Ό ν•©λ‹ˆλ‹€.

πŸ₯³redis clsuterλŠ” μžλ™μœΌλ‘œ 데이터듀을 λΆ„μ‚° μ €μž₯ν•˜κ³  μžˆλŠ”λ° λ§Œμ•½ 1μ΄λΌλŠ” 값을 찾으렀고 ν•˜λŠ” 경우 ν•΄λ‹Ή λ…Έλ“œμ— 값이 μ—†λ‹€λ©΄ redirectλ₯Ό 톡해 λ‹€λ₯Έ λ…Έλ“œμ—μ„œ 찾아와야 ν•©λ‹ˆλ‹€. 이λ₯Ό μ„€μ •ν•˜μ§€ μ•ŠλŠ”λ‹€λ©΄ λ¬΄ν•œ redirectκ°€ λ˜μ–΄ μ„±λŠ₯에 μ•ˆ 쒋은 영ν–₯을 λ―ΈμΉ˜κΈ°μ— μ„€μ •ν•΄μ£Όμ—ˆμŠ΅λ‹ˆλ‹€.
기본값은 3으둜 지정 λ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€.


3. Topology μ„€μ •

πŸ«‘μ‚¬μ‹€ μœ„μ˜ κ³Όμ •κΉŒμ§€ μ§„ν–‰λ˜μ—ˆλ‹€λ©΄ μ—°κ²°μ—λŠ” 성곡을 ν•  κ²ƒμž…λ‹ˆλ‹€. κ·ΈλŸ¬λ‚˜ redis clusterλ₯Ό μ‚¬μš©ν•˜λŠ” μ΄μœ μ—λŠ” κ³ κ°€μš©μ„±μ„ ν™•λ³΄ν•˜κΈ° μœ„ν•΄μ„œμΈλ° μ‹€μ œλ‘œ μŠ€ν”„λ§ λΆ€νŠΈλ₯Ό μ‹€ν–‰ν•΄ 보고 λ‚˜μ„œ λ§ˆμŠ€ν„° λ…Έλ“œ 쀑 ν•˜λ‚˜λ₯Ό 연결을 λŠμ–΄λ²„λ¦°λ‹€λ©΄ κ°‘μžκΈ° λͺ¨λ“  연결이 λŠμ–΄μ§€λŠ” 상황이 λ°œμƒν•©λ‹ˆλ‹€.

이λ₯Ό 막기 μœ„ν•΄μ„œλŠ” Topologyλ₯Ό μ„€μ •ν•΄μ£Όμ–΄μ•Ό ν•©λ‹ˆλ‹€. 더 μžμ„Έν•œ λ‚΄μš©μ€ 초보 개발자λ₯Ό μœ„ν•œ Redis Cluster Migration κ°€μ΄λ“œλΌμΈλ₯Ό 톡해 확인 ν•˜μ‹€ 수 μžˆμŠ΅λ‹ˆλ‹€.

λ¨Όμ € μ•„λž˜μ™€ 같이 μž‘μ„±ν•΄μ€λ‹ˆλ‹€.

 ClusterTopologyRefreshOptions clusterTopologyRefreshOptions = ClusterTopologyRefreshOptions.builder()
                .enableAllAdaptiveRefreshTriggers()
                .enablePeriodicRefresh(Duration.ofHours(1L))
                .build();
        ClientOptions clientOptions = ClusterClientOptions.builder()
                .topologyRefreshOptions(clusterTopologyRefreshOptions)
                .build();

ClusterTopologyRefreshOptionsλŠ” λ³€κ²½ 사항을 μ–΄λ–»κ²Œ κ°μ§€ν•˜κ³  λ°˜μ‘ν• μ§€λ₯Ό μ„€μ •ν•˜λŠ” ν΄λž˜μŠ€μž…λ‹ˆλ‹€.

enableAllAdaptiveRefreshTriggers()λŠ” λ…Έλ“œλ“€μ˜ μƒνƒœ λ³€ν™”κ°€ λ°œμƒν•˜λ©΄ ν•΄λ‹Ή λ‚΄μš©μ„ λ°˜μ˜ν•˜λŠ” μ„€μ •μž…λ‹ˆλ‹€.(μ‰½κ²Œ 말해 λͺ¨λ“  λ³€κ²½(μ‚­μ œ, μΆ”κ°€...)에 λŒ€ν•΄μ„œ μœ„ ν•΄λ‹Ή μ˜΅μ…˜μ„ μ‹€ν–‰ν•œλ‹€λΌκ³  μ΄ν•΄ν•˜μ‹œλ©΄ λ©λ‹ˆλ‹€.)

enablePeriodicRefresh()λŠ” 주기적으둜 λ…Έλ“œλ“€μ˜ μƒνƒœλ₯Ό ν™•μΈν•˜λŠ” λ©”μ†Œλ“œμž…λ‹ˆλ‹€.

πŸ₯³μœ„와 같이 μ„€μ •ν•œ ν›„ ν•΄λ‹Ή option듀을 ClientOptions에 λ„£μ–΄μ€λ‹ˆλ‹€.


4. Configuration μ„€μ •

🫠configuration에 μœ„ client option을 λ„£μ–΄μ£Όλ©΄ ν•΄λ‹Ή 섀정듀이 적용되게 λ©λ‹ˆλ‹€.
적용 방법은 μ•„λž˜μ™€ κ°™μŠ΅λ‹ˆλ‹€.

 LettuceClientConfiguration clientConfiguration = LettuceClientConfiguration.builder()
                .clientOptions(clientOptions)
                .readFrom(ReadFrom.REPLICA_PREFERRED)
                .build();

LettuceλŠ” springμ—μ„œ μ§€μ›ν•˜λŠ” λΌμ΄λΈŒλŸ¬λ¦¬μž…λ‹ˆλ‹€.

πŸ₯³μ΄μ œ μœ„ 값을 μ„€μ •ν•œ factoryλ₯Ό λ°˜ν™˜ν•˜κ³  μ‚¬μš©ν•΄μ£Όμ‹œλ©΄ λ©λ‹ˆλ‹€.

LettuceConnectionFactory connectionFactory = 
	new LettuceConnectionFactory(redisClusterConfiguration)
    
return connectionFactory;

더 λ‚˜μ•„κ°€κΈ°

πŸ€”μœ„ redisCluster 섀정을 λ§ˆμΉ˜μ…¨λ‹€λ©΄ localμ—μ„œλŠ” μ‚¬μš©ν•  수 μžˆλŠ” μƒνƒœκ°€ λ©λ‹ˆλ‹€.
κ·ΈλŸ¬λ‚˜ ν•΄λ‹Ή cluster 섀정을 κ·ΈλŒ€λ‘œ μ‚¬μš©ν•˜μ‹€ 경우 cloud ν™˜κ²½μ—μ„œ μž‘λ™μ„ μ•ˆν•˜κ²Œ λ©λ‹ˆλ‹€.

κ·Έ μ΄μœ λ‘œλŠ” redis host,port 섀정을 해주지 μ•Šμ•˜κΈ° λ•Œλ¬Έμž…λ‹ˆλ‹€. Topology refresh trying to connect to 127.0.0.1 #1289 μ—μ„œ 톡해 ν•΄λ‹Ή λ¬Έμ œκ°€ λ°œμƒν•œ μ΄μœ μ™€ κ·Έ ν•΄κ²° 방법을 확인 ν•  수 μžˆμ—ˆμŠ΅λ‹ˆλ‹€.
πŸ§ν•΄λ‹Ή κΈ€μ—μ„œλŠ” 섀정이 잘λͺ»λ˜μ„œ 생긴 λ¬Έμ œκ°€ μ•„λ‹ˆλ©° ν•΄λ‹Ή μ£Όμ†Œλ₯Ό μž¬μ„€μ • ν•΄μ£Όλ©΄ ν•΄κ²° ν•΄ 쀄 수 μžˆλ‹€κ³  μ“°μ—¬μžˆμŠ΅λ‹ˆλ‹€.

μž¬μ„€μ •ν•˜λŠ” 방법은 μ•„λž˜μ™€ 같이 κ°™μŠ΅λ‹ˆλ‹€.

MappingSocketAddressResolver resolver = MappingSocketAddressResolver.create(DnsResolvers.UNRESOLVED,
                hostAndPort -> {
                    HostAndPort andPort = HostAndPort.of(redisInfo.getConnectIp(), hostAndPort.getPort());
                    return andPort;
                }
        );

πŸ˜λ§Œμ•½ μœ„ 섀정을 해주지 μ•Šμ„ 경우 localhost둜 λ™μž‘μ„ ν•˜κ²Œ λ˜λŠ”λ° μ €μ˜ 경우 λ„μ»€λ‘œ λ„μš΄ μƒν™©μ΄μ—¬μ„œ localhost둜 ν•˜κ²Œ 되면 톡신 μ•ˆλ˜λŠ” μƒν™©μ΄μ—ˆμŠ΅λ‹ˆλ‹€. κ·Έλž˜μ„œ 이λ₯Ό ν•΄κ²° ν•˜κΈ° μœ„ν•΄ docker.host둜 μ—°κ²°ν•˜λŠ” 방식을 μ‚¬μš©ν•˜μ˜€μŠ΅λ‹ˆλ‹€.

😎MappingSocketAddressResolverλ₯Ό λ§Œλ“€μ–΄ μ€€ ν›„ host의 경우 도컀 호슀트둜 보내고 port의 κ²½μš°μ—λŠ” μš”μ²­ν•œ κ°’ κ·ΈλŒ€λ‘œ λ°›μ•„μ„œ μ „ν•΄μ£Όμ—ˆμŠ΅λ‹ˆλ‹€.

λ‹€μŒ μœ„ 섀정값을 ClientResources λ„£μ–΄μ€€ ν›„ LettuceClientConfiguration에 λ„£μ–΄μ£Όλ©΄ λ©λ‹ˆλ‹€.

ClientResources clientResources = ClientResources.builder()
                .socketAddressResolver(resolver)
                .build();
                
LettuceClientConfiguration clientConfiguration = LettuceClientConfiguration.builder()
                .commandTimeout(Duration.of(10, ChronoUnit.SECONDS))
                .clientOptions(clientOptions)
                .clientResources(clientResources) <--- μΆ”κ°€
                .readFrom(ReadFrom.REPLICA_PREFERRED)
                .build();

μ΄λ ‡κ²Œ 되면 redis clsuter 연결이 μ™„μ„±λ˜κ²Œ λ©λ‹ˆλ‹€.


마무리

πŸ₯³κ³ κ°€μš©μ„±μ„ 확보 ν•˜κΈ° μœ„ν•΄ redis clusterλ₯Ό μ‚¬μš©ν•˜μ˜€λŠ”λ° κ³ κ°€μš©μ„±μ„ ν™•λ³΄ν•˜λŠ” λ°©λ²•μ—λŠ” λ‹€μ–‘ν•œ 방법이 μžˆμŠ΅λ‹ˆλ‹€.
aws, ncp, gcpμ—μ„œ redis cloudλ₯Ό μ§€μ›ν•˜λ©° redisλ₯Ό sentinel둜 μ—°κ²°ν•˜λŠ” 방법도 μžˆμŠ΅λ‹ˆλ‹€.
λ§Œμ•½ μ—¬μœ κ°€ λ˜μ‹ λ‹€λ©΄ μœ„μ˜ 방식도 ν•œλ²ˆ μ‹œλ„ν•΄ λ³΄μ‹œλŠ” 것도 쒋을 것 κ°™μŠ΅λ‹ˆλ‹€.

μ•„μ‰¬μš΄ 점 및 κΆκΈˆν•œ 점

πŸ˜₯springμ—μ„œλŠ” νŠΈλžœμž­μ…˜μ„ μ‚¬μš©ν•˜κΈ° νŽΈν•˜κ²Œ ν•˜κΈ° μœ„ν•΄ setEnableTransactionSupport()λΌλŠ” λ©”μ†Œλ“œλ₯Ό μ§€μ›ν•˜λŠ”λ° redis clusterλ₯Ό μ‚¬μš©ν•˜λŠ” 경우 ν•΄λ‹Ή λ©”μ†Œλ“œλ₯Ό μ΄μš©ν•  수 μ—†κ²Œ λ©λ‹ˆλ‹€.
redis clsuterμ—μ„œλŠ” mulit 연산을 μ§€μ›ν•˜μ§€ μ•ŠκΈ° λ•Œλ¬ΈμΈλ° 이λ₯Ό ν•΄κ²°ν•˜λŠ” λ°©λ²•μ—λŠ” 같은 node에 값듀이 μœ„μΉ˜ν•˜λ©΄ λœλ‹€λŠ”λ° μ €λŠ” 잘 μ•ˆλ˜λ”λΌκ³ μš”... ν˜Ήμ‹œ μ•„μ‹œλŠ” 뢄이 있으면 λŒ“κΈ€λ‘œ μ•Œλ €μ£Όμ‹œλ©΄ κ°μ‚¬ν•©λ‹ˆλ‹€.😭

참고자료

초보 개발자λ₯Ό μœ„ν•œ Redis Cluster Migration κ°€μ΄λ“œλΌμΈ
Topology refresh trying to connect to 127.0.0.1 #1289

profile
greenTeaμž…λ‹ˆλ‹€.

0개의 λŒ“κΈ€