💬
목차
< 뒤로가기
인쇄

캐모마일 캐시(레디스) 모듈 가이드

개요

chamomile-cache-redis 모듈은 chamomile-cache와 마찬가지로 spring cache를 제공하며 Redis를 지원한다.
spring cache와 관련한 내용은 chamomile-cache 모듈 가이드 문서를 참고한다.

레디스는 메모리 위에서 동작하는 Key/value 저장소인 Redis는 NoSQL DBMS로 분류되며 동시에 Memcached와 같은 인메모리 솔루션으로 분리된다.

  • 다중 서버 구성 가능(샤딩, 레플리케이션 등)
  • 데이터베이스로 사용될 수 있으며, Cache 로도 사용 가능
  • 성능은 머신 스펙에 따라 다르나 초당 2만 ~ 10만회 수행 가능
  • 상대적으로 큰 데이터 캐싱 가능
  • 네트워크 트래픽이 발생해 속도가 저하될 수 있으나 이중화를 통해 장애에 유연하게 대응 가능

사용법

dependency

<dependency>
 <groupId>net.lotte.chamomile.module</groupId>
 <artifactId>chamomile-cache-redis</artifactId>
</dependency>
spring:
  redis:
      host: 127.0.0.1
      port: 6379

@EnableCaching 어노테이션으로 해당 어플리케이션이 캐시를 사용할 것임을 명시한다.

@ChamomileBootApplication
@EnableCaching
public class ChamomileAdminApplication {
    public static void main(String[] args) {
        ChamomileApplication.run(ChamomileAdminApplication.class, args);
    }
}

코드 예제

Redis 캐시 예제 Controller

@GetMapping("/chmm/board/redis-cache/{boardId}")
public Map<String, Object> getBoard(@PathVariable Long boardId) {
    Map<String ,Object> map = new HashMap<>();
    map.put("cnt", boardService.getBoard(boardId));
    return map;
}

@PostMapping("/chmm/board/update/redis-cache/{boardId}")
public Map<String, Object> updateBoardViewCnt(@RequestBody BoardVO request, @PathVariable Long boardId) {
    Map<String ,Object> map = new HashMap<>();
    map.put("cnt", boardService.updateBoard(request, boardId));
    return map;
}

Redis 캐시 예제 Service

public class BoardCacheTestService {
    private final BoardRepository repository;

    ..

    @Cacheable(value = "board", key = "#boardId", cacheManager = "redisCacheManager")
    public Integer getBoard(Long boardId) {
        BoardVO boardVO = repository.findByBoardId(boardId).orElseThrow(() -> new RuntimeException("not found"));
        log.info("find = {}", boardVO);

        return Math.toIntExact(boardVO.getViewCnt());
    }

    @CachePut(value = "board", key = "#boardId", cacheManager = "redisCacheManager")
    public Integer updateBoard(BoardVO request, Long boardId) {
        BoardVO findOne = repository.findByBoardId(boardId).get();
        findOne.setViewCnt(findOne.getViewCnt() + 1);
        return Math.toIntExact(findOne.getViewCnt());
    }
}

위 예제 애플리케이션을 테스트 해보면 @Cacheable 어노테이션이 포함된 메서드 호출 시
처음에는 Redis에 저장된 값이 없으므로 DB에서 값을 조회하지만 이후에는 Redis에 캐시 데이터를 생성하며 캐시 값을 리턴하는 것을 확인 할 수 있다.

redis-cache-ex.png
[캐시가 생성된 redis-cli 화면]

레디스 클러스터링

Redis cluster는 데이터를 자동으로 여러 개의 Redis 노드에 나누어 저장할 수 있는 방법을 제공한다. 또한 일부 노드가 죽거나 통신이 되지 않을 때에도 작업을 계속할 수 있는 가용성을 제공한다

장점

  • 샤딩(Sharding)
    • 여러 노드 간에 데이터 세트를 자동으로 분할할 수 있는 기능
  • 가용성
    • 노드의 하위 집합에서 장애가 발생하거나 클러스터의 나머지 부분과 통신할 수 없을 때 작업을 계속할 수 있는  기능이다.
      즉, 실제적인 측면에서 일부 노드가 실패하거나 통신할 수 없을 때도 정상적으로 작동한다. 그러나 더 큰 고장이 발생할 경우(예: 대부분의 마스터를 사용할 수 없는 경우) 클러스터 작동이 중지된다.

클러스터 구성도

  • 3개 서버의 가상환경을 구축하고 아래와 같이 구조로 클러스터를 구성하겠습니다.

  •  서버 1에 문제가 생겨서 더 이상 통신이 되지 않는다면 자동으로 slave 노드 중 하나가 master가 되어 작업을 계속 할 수 있다. 

  • 특징

    • 자동 분할: 클러스터에서는 데이터가 자동으로 분할되어 각 마스터 노드에 저장됩니다. 이는 키의 해시 값을 통해 결정됩니다.

    • 데이터 접근: 클라이언트는 어느 노드에 접속하든 클러스터 전체의 데이터에 접근할 수 있습니다. 요청된 키가 해당 노드에 없는 경우, 클라이언트는 해당 키를 가진 노드로 리디렉션됩니다.

    • 복제 및 고가용성: 각 마스터 노드는 하나 이상의 슬레이브 노드를 가질 수 있으며, 이들은 마스터 노드의 데이터를 복제합니다. 이를 통해 데이터의 안정성과 고가용성이 보장됩니다.

    • 동기화: 마스터 노드와 슬레이브 노드 간에는 지속적인 데이터 동기화가 일어납니다. 이 과정을 통해 데이터의 일관성과 무결성이 보장됩니다.

    • 자동 장애 전환: Redis 클러스터에서는 자동 장애 전환(Failover)이 가능합니다. 마스터 노드에 장애가 발생하면, 슬레이브 노드 중 하나가 자동으로 마스터 노드의 역할을 맡게 됩니다.

Docker 환경에서 Redis 클러스터 구축하기

  • docker-compose.yml 설정

    version: '3'
    services:
    redis-master1:
    image: redis:6.0.9-alpine
    command: redis-server --port 3000 --cluster-enabled yes --cluster-config-file nodes.conf --cluster-node-timeout 5000 --appendonly yes
    ports:
      - 3000:3000
      - 13000:13000
    volumes:
      - redis-master1-data:/data
    networks:
      redis-net:
        ipv4_address: 172.27.0.10
    
    redis-master2:
    image: redis:6.0.9-alpine
    command: redis-server --port 3001 --cluster-enabled yes --cluster-config-file nodes.conf --cluster-node-timeout 5000 --appendonly yes
    ports:
      - 3001:3001
      - 13001:13001
    volumes:
      - redis-master2-data:/data
    networks:
      redis-net:
        ipv4_address: 172.27.0.3
    
    redis-master3:
    image: redis:6.0.9-alpine
    command: redis-server --port 3002 --cluster-enabled yes --cluster-config-file nodes.conf --cluster-node-timeout 5000 --appendonly yes
    ports:
      - 3002:3002
      - 13002:13002
    volumes:
      - redis-master3-data:/data
    networks:
      redis-net:
        ipv4_address: 172.27.0.4
    
    redis-slave1:
    image: redis:6.0.9-alpine
    command: redis-server --port 3003 --cluster-enabled yes --cluster-config-file nodes.conf --cluster-node-timeout 5000 --appendonly yes
    ports:
      - 3003:3003
      - 13003:13003
    volumes:
      - redis-slave1-data:/data
    networks:
      redis-net:
        ipv4_address: 172.27.0.5
    
    redis-slave2:
    image: redis:6.0.9-alpine
    command: redis-server --port 3004 --cluster-enabled yes --cluster-config-file nodes.conf --cluster-node-timeout 5000 --appendonly yes
    ports:
      - 3004:3004
      - 13004:13004
    volumes:
      - redis-slave2-data:/data
    networks:
      redis-net:
        ipv4_address: 172.27.0.6
    
    redis-slave3:
    image: redis:6.0.9-alpine
    command: redis-server --port 3005 --cluster-enabled yes --cluster-config-file nodes.conf --cluster-node-timeout 5000 --appendonly yes
    ports:
      - 3005:3005
      - 13005:13005
    volumes:
      - redis-slave3-data:/data
    networks:
      redis-net:
        ipv4_address: 172.27.0.7
    
    redis-cluster-init:
    image: redis:6.0.9-alpine
    command: >
      sh -c "sleep 10 && 
      echo yes | redis-cli --cluster create 10.131.4.85:3000 10.131.4.85:3001 10.131.4.85:3002 10.131.4.85:3003 10.131.4.85:3004 10.131.4.85:3005 --cluster-replicas 1 --cluster-yes"
    depends_on:
      - redis-master1
      - redis-master2
      - redis-master3
      - redis-slave1
      - redis-slave2
      - redis-slave3
    networks:
    redis-net:
    driver: bridge
    ipam:
      config:
        - subnet: 172.27.0.0/16
    volumes:
    redis-master1-data:
    redis-master2-data:
    redis-master3-data:
    redis-slave1-data:
    redis-slave2-data:
    redis-slave3-data:
  • Redis 마스터 노드: redis-master1, redis-master2, redis-master3이라는 세 개의 마스터 노드가 있다. 각각 다른 포트(3000, 3001, 3002)와 볼륨을 사용한다.

  • Redis 슬레이브 노드: redis-slave1, redis-slave2, redis-slave3이라는 세 개의 슬레이브 노드가 있으며, 이들도 각각 다른 포트(3003, 3004, 3005)와 볼륨을 사용한다.

  • 네트워킹: 모든 노드는 redis-net이라는 동일한 네트워크에 연결되어 있으며, 각 노드에는 고정 IP 주소가 할당되어 있다.

  • 클러스터 초기화: redis-cluster-init 서비스는 클러스터를 초기화하는 데 사용한다. 이 서비스는 모든 마스터와 슬레이브 노드가 시작된 후에 실행되며, redis-cli --cluster create 명령을 사용하여 클러스터를 구성한다.

  • 볼륨: 각 Redis 노드는 데이터를 저장하기 위해 독립적인 볼륨을 사용한다. 이를 통해 컨테이너가 재시작되어도 데이터가 유지된다.

  • 브리지 네트워크: 사용자 정의 브리지 네트워크 (redis-net)가 설정되어 있으며, 이를 통해 컨테이너 간의 격리와 네트워킹이 관리된다.

  • Redis 클러스터를 효율적으로 관리하고 운영할 수 있게 해주며, Docker Compose를 사용함으로써 여러 컨테이너를 쉽게 설정하고 관리할 수 있다.

  • docker-compose 명령어

    // 실행
    docker-compose up
    // 백그라운드에서 실행
    docker-compose up -d
    // 서비스 중지
    docker-compose stop
    // 서비스 다운
    docker-compose down
    // 실행중인 서비스 확인하기
    docker-compose ps
    // 서비스 로그 확인하기
    docker-compose logs
  • 실행 확인

  • 3개의 master와 3개의 slave 가 실행 된 걸 확인 할 수 있다.

클러스터 적용

application.yml

chmm:
    cache:  
        instance: redis #none/redis/hazelcast  
        ## 아래 클러스터는 redis cluster 적용시에만 필요 
        mode: cluster  
        redis-nodes:  
            - 10.131.4.85:3002  
            - 10.131.4.85:3001  
            - 10.131.4.85:3000  
        redis-max-redirects: 6  
        host: 10.131.4.85
  • 클러스터 관련 설정은 클러스터 설정 진행시에만 작성한다.
  • mode : cluster -> 레디스 클러스터 진행시 적용 한다.
  • redis-nodes : 클러스터를 구성하는 Redis 노드 들의 주소와 포트 목록이다.
  • redis-max-redirects : 클라이언트가 올바른 노드에 도달할 때까지 리디렉션 되는 최대 횟수이다
  • host : Redis 클러스터에 연결하는 데 사용되는 public IP 주소 이다.

JWT 로그인 레디스사용 예제

JwtTokenUtils.java

@Autowired  
private ChamomileTokenManager tokenManager;

public String Test (DefaultUser userDetails, String accessToken) throws Exception {  

        String key = "tokens:" + userDetails.getUsername();  

        Map<String, String> tokenData = new HashMap<>();  
        tokenData.put("refresh_token", refreshToken);  
        tokenData.put("expiry_date", String.valueOf(expiration));  

        tokenManager.setRefreshToken(key,tokenData);  
        tokenManager.expiredRefreshToken(key);  
}
  • ChamomileTokenManager 인터페이스를 주입받는다.
  • ChamomileTokenManager. setRefreshToken 를 통하여 레디스에 데이터를 등록한다.
이전 캐시