Redis Cluster 모드 생성 및 배포 (feat: redis Redisson, Synology NAS, Spring boot Cache 연동)

2025. 1. 6. 12:32Redis

 

목차
  • Redis Cluster 란?
  • Redis Cluster 로컬환경 배포
  • Redis Cluster 클라우드 환경 배포
    • redis.conf 파일 수정
    • redis 연결 테스트
    • spring boot 캐싱 테스트

 

Redis Cluster 란?

 

여러개의 master를 두고 데이터를 분산 저장하며 수평적인 확장을 가능하게 만들어 준다. 즉 서버의 필요 상태에 따라 저장 공간을 늘리거나 줄이는 것이 용의하다.

 

각각의 마스터들에는 하나 이상의 slave가 매핑될 수 있으며 해당 slave는 master 상태가 비정상적인 경우 master로 승급되며 이전 데이터 상태를 유지하며 다시 redis 서버가 중지 없이 가동 될 수 있게 만들어 준다.

 

 

Redis Cluster 로컬환경 배포 3M - 3S

 

먼저 로컬 환경에는 redis cluster를 배포하기 위해 6개의 포트가 필요하다. 3개의 마스터와 3개의 슬레이브 구조로 만들 예정이다.

 

docker-compose.yml 파일

version: '3'
services:
  redis-master-1:
    container_name: redis-master-1
    image: redis:7.0.0
    command: redis-server /etc/redis.conf
    volumes:
      - ./config/redis-master-1.conf:/etc/redis.conf
    restart: always
    depends_on:
      - redis-master-2
      - redis-master-3
      - redis-slave-1
      - redis-slave-2
      - redis-slave-3
    ports:
      - "9001:9001"
      - "19001:19001"

  redis-master-2:
    container_name: redis-master-2
    image: redis:7.0.0
    command: redis-server /etc/redis.conf
    volumes:
      - ./config/redis-master-2.conf:/etc/redis.conf
    restart: always
    ports:
      - "9002:9002"
      - "19002:19002"

  redis-master-3:
    container_name: redis-master-3
    image: redis:7.0.0
    command: redis-server /etc/redis.conf
    volumes:
      - ./config/redis-master-3.conf:/etc/redis.conf
    restart: always
    ports:
      - "9003:9003"
      - "19003:19003"

  redis-slave-1:
    container_name: redis-slave-1
    image: redis:7.0.0
    command: redis-server /etc/redis.conf
    volumes:
      - ./config/redis-slave-1.conf:/etc/redis.conf
    restart: always
    ports:
      - "9004:9004"
      - "19004:19004"

  redis-slave-2:
    container_name: redis-slave-2
    image: redis:7.0.0
    command: redis-server /etc/redis.conf
    volumes:
      - ./config/redis-slave-2.conf:/etc/redis.conf
    restart: always
    ports:
      - "9005:9005"
      - "19005:19005"

  redis-slave-3:
    container_name: redis-slave-3
    image: redis:7.0.0
    command: redis-server /etc/redis.conf
    volumes:
      - ./config/redis-slave-3.conf:/etc/redis.conf
    restart: always
    ports:
      - "9006:9006"
      - "19006:19006"

 

redis-[이름]-[번호].conf

port 9001
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 3000
appendonly yes

 

폴더에 설정을 마치고 실행을 하게 되면 도커에 해당 설정을 마친 컨테이너들이 실행된다. 하단 redis-cluster 명령어 모음을 참고해 master와 slave들을 연결을 하면 된다.

 

Redis Cluster 클라우드 환경 배포

 

로컬환경에 배포하는 경우 비교적 특별한 설정 없이 연결이 가능하다. 하지만 클라우드 환경에 배포하는 경우에는 도커 환경과 클라우드 연결 상태에 따라 설정이 바뀌는 경우가 있는데 현재 연결 방식은 클라우드 외부 ip를 이용해 redis-cluster를 구축할 예정이다. 외부 ip로 연결하는 방식이 마음에 들지 않을 경우  해당 테스트를 마친 후에는 .conf 파일별로 고정 로컬 ip(도커 컨테이너에 할당되는 ip를 말함)를 작성해주고 고정 할당된 로컬 ip를 매핑에 컨테이너 브릿지를 구축해 연결하는 방식으로도 cluster 구축이 가능할 것이다.

 

포트를 동일하게 사용한다는 가정하에 변경해야되는 것은 conf 에 작성된 값을 조금 수정해주면 된다. 

 

port 9001
bind 0.0.0.0
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000

cluster-announce-ip 외부 ip 주소
cluster-announce-port 9001       

protected-mode no
appendonly yes

 

cluster-announce-ip에는 클라우드 환경에서 사용하는 외부 ip를 작성해주면 된다.

추가로 구현과 별개로 로컬에서는 비교적 자동으로 master와 slave를 연결해주는 작업이 잘 수행되었지만 클라우드 환경에서는 자동 구축 명령어가 잘 이행되지 않아 수동으로 master노드와 slave 연결을 수행해 cluster 연결을 하였습니다. 해당 명령어는 하단 명령어 목록과 깃허브에 참고 되어 있는 자료를 참고해 주세요.

 

Spring boot에 redis-cluster 연결해 캐시로 사용하기

 

 

properties 설정

 

application.yml

spring:  
  data:
    redis:
      cluster:
        nodes:
          - ${SERVER_URL}:${REDIS_MASTER_PORT_1},
          - ${SERVER_URL}:${REDIS_MASTER_PORT_2},
          - ${SERVER_URL}:${REDIS_MASTER_PORT_3},
        max-redirects: 3
  cache:
    type: redis
    redis:
      enable-statistics: true

 

application-api-key.properties

REDIS_MASTER_PORT_1 = 포트번호
REDIS_MASTER_PORT_2 = 포트번호
REDIS_MASTER_PORT_3 = 포트번호
SERVER_URL = 서버 ip

 

RedisClusterProperties

@Setter @Getter
@Configuration
@ConfigurationProperties(prefix = "spring.data.redis.cluster")
public class RedisClusterProperties {

    private int maxRedirects;
    private List<String> nodes;
}

 

Application.java 설정

@SpringBootApplication
@EnableCaching
@EnableConfigurationProperties(RedisClusterProperties.class)
public class ManagementApplication {

	public static void main(String[] args) {
		SpringApplication.run(ManagementApplication.class, args);
	}
}

 

RedissonConfig

@Configuration
@RequiredArgsConstructor
public class RedissonConfig {

    private final RedisClusterProperties redisClusterProperties;

    @Bean
    public RedissonClient redissonClient() {
        Config config = new Config();
        // Redis 클러스터 설정
        String[] nodeAddresses = redisClusterProperties.getNodes().stream()
                .map(node -> "redis://" + node.replace(",", ""))
                .toArray(String[]::new);

        config.useClusterServers()
                .addNodeAddress(nodeAddresses)
                .setScanInterval(2000)  // 클러스터 스캔 주기 설정 (밀리초)
                .setConnectTimeout(5000)  // 연결 시간 제한 (밀리초)
                .setIdleConnectionTimeout(10000);  // 유휴 연결 시간 제한 (밀리초)

        return Redisson.create(config);
    }

    @Bean
    public RedisCacheManager redisCacheManager(RedisConnectionFactory cf) {
        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()))
                .entryTtl(Duration.ofMinutes(3L)); // 캐시 수명 설정

        return RedisCacheManager.RedisCacheManagerBuilder
                .fromConnectionFactory(cf)
                .cacheDefaults(redisCacheConfiguration)
                .build();
    }

    @Bean
    public RedisTemplate<String, String> redisTemplate(RedissonClient redissonClient) {
        RedisTemplate<String, String> template = new RedisTemplate<>();
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        template.setConnectionFactory(new RedissonConnectionFactory(redissonClient));
        return template;
    }
}

 

캐싱 사용 방법 - 프로젝트 내에서 사용한 조회 쿼리

@Cacheable(value = "productSearch", key = "#condition.productName + ':' + #condition.factory + ':' + #condition.modelClassification + ':' + #pageable.pageNumber", cacheManager = "redisCacheManager")
public PageCustom<ProductDto.productSearchResult> searchProducts(ProductDto.productCondition condition, Pageable pageable) {
    return productRepository.searchProduct(condition, pageable);
}

 

하단에 사용한 조회 쿼리를 참고해 각각의 사용자가 필요한 형태로 커스텀 해서 사용하면 된다. 이 상태로 서버를 실행하게 된다면 빌드 과정에서 해당 값이 정상적으로 보인다면 spring-boot에 redis-cluster를 성공적으로 연결한 것이다.

 

Redis cluster nodes configuration got from 외부ip/175.117.4.180:8016:
97fbc82cc73588eefd0218c502e22514386fa43a 외부ip:8016@18016 myself,slave 0e3b51404ffc197bded7551a464183a21c859bbb 0 1736132327000 28 connected
0ee3caa9c1c9a467c81e706c67d7a58c86070014 외부ip:8015@18015 master - 0 1736132327561 27 connected 5461-10922
d72fdb35670d917f588a88630e4aa9073f44fac1 외부ip:8013@18013 master - 0 1736132328565 14 connected 10923-16383
0e3b51404ffc197bded7551a464183a21c859bbb 외부ip:8011@18011 master - 0 1736132327561 28 connected 0-5460
2b4faa2ba3ea662afe5cd6d0231c5048f55fc4a8 외부ip:8014@18014 slave d72fdb35670d917f588a88630e4aa9073f44fac1 0 1736132328000 14 connected
53294702d92e0e78c9bab296c5466779f9322ef0 외부ip:8012@18012 slave 0ee3caa9c1c9a467c81e706c67d7a58c86070014 0 1736132328063 27 connected

 

개인정보가 담겨있어 모자이크 처리했지만 위에 조회 쿼리를 이용해 사용자가 요청한 경우 cluster에 정상적으로 데이터가 저장된 것을 확인할 수 있다. 해당 정보는 redis의 TTL 설정에 따라 설정된 시간이 지나면 자동으로 삭제된다 (Redis Desktop Manager App)

Redis Desktop Manager App

 

캐싱 전 조회 속도 548ms -> 로컬 DB 환경

 

캐싱 후 조회 속도 77ms -> 클라이언트 redis 환경

 

로컬 DB를 이용한 환경에서 나온 속도와 비교했을 때도 꽤 많은 차이를 보여준다. 만일 사용자가 자주 조회하게 되는 대표 페이징 값들을 기본적으로 redis에 저장해둔다면 기존 DB에 조회해 얻어내는 결과보다 사용자에게 더 빠른 결과를 제공해 줄 수 있을 것입니다. 

 

일반적인 토이 프로젝트 수준의 경우 cluster모드는 과하다 생각이 들고 redis의 standalone 모드로 진행해도 무방할 것입니다. 부족하다면 master/slave 모드까지 확장한다면 대부분의 프로젝트에서는 커버가 가능할 것이라 생각이 듭니다. 

 

redis-cluster 설정을 하며 사용한 명령어 모음

### 클러스터 마스터 수동 생성
##### --cluster-replicas 0 -> 수동으로 slave 추가 (1의 경우 슬레이브를 뒤에 추가하면 마스터 노드에 자동으로 슬레이브 추가)
```
docker exec -it redis-master-1 redis-cli --cluster create redis-master-1 [ip 주소]:[포트번호] redis-master-2 [ip 주소]:[포트번호] redis-master-3 [ip 주소]:[포트번호] --cluster-replicas 0
```
### 클러스터 마스터에 슬레이브 추가 
- 로컬 환경인 경우 127.0.0.1 or localhost or host.docker.internal 삽입
- 외부 클라우드에 배포 (AWS, 개인 서버등등) 해당 서버의 외부 ip 주소를 삽입
```
docker exec -it redis-cli --cluster add-node [ip 주소]:[슬레이브 포트번호] [ip 주소]:[마스터 포트번호] --cluster-slave
```

### 클러스터 상태 확인
```
docker exec -it redis-master-1 redis-cli -p [포트번호] cluster info
```

```
cluster_state:ok
cluster_slots_assigned:16384
cluster_slots_ok:16384
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:6
cluster_size:3
```

### 클러스터 노드 상태 확인
```
docker exec -it redis-master-1 redis-cli -p [포트번호] cluster nodes
```

### 외부 접근 테스트
```
redis-cli -c -h [외부 ip 주소] -p [포트번호]
```

### 클러스터 생성 중 오류 발생 시 노드들을 초기화 한 상태로 처음부터 다시 시작
#### 마스터 노드 초기화
```
docker exec -it redis-master-1 redis-cli -p [포트번호] cluster reset hard
```
### 슬레이브 노드 초기화
```
docker exec -it redis-slave-1 redis-cli -p [포트번호] cluster reset hard
```

클러스터 마스터 수동 생성 -> 클러스터 마스터에 슬레이브 추가 -> 클러스터 상태 확인

 

 

GitHub - lsg1024/redis

Contribute to lsg1024/redis development by creating an account on GitHub.

github.com

 

'Redis' 카테고리의 다른 글

Redis 운영 방식 3가지 Standalone, Sentinel, Cluster의 장단점  (0) 2024.11.29