Redis - 총정리

choko's avatar
Jun 29, 2024
Redis - 총정리
 
 

레디스(Redis, remote dictionary Server)

  • 키-값 구조의 비정형 데이터를 저장하고 관리하기 위한 오픈소스 기반 비관계형 DMBS이다.
      1. 모든 데이터를 메모리에 저장하고 조회한다. 메모리 접근이 디스크 접근보다 빠르기 때문에, 기존 관계형 데이터베이스에 비해 속도가 빠르다
      1. 다양한 자료구조를 Key-Value 형태로 저장한다.
          • 다양한 자료구조를 제공한다는 것은, 개발의 편의성과 난이도에서 이점을 주고, 개발시간 단축이 가능하다.
  • 사용 사례
    • 캐싱: 자주 조회되는 데이터를 메모리에 저장하여 데이터베이스에 대한 부하를 줄이고 응답 시간을 단축하는 데 사용된다.
    • 세션 스토리지: 사용자 세션 데이터를 관리하여 분산된 환경에서 빠르게 세션 정보를 읽고 쓸 수 있도록 한다.
    • 실시간 분석: 실시간 데이터를 수집하고 분석하는 데 사용되어, 빠르게 통계나 분석 결과를 제공한다.
    • 큐잉 시스템: 작업이나 메시지를 큐에 넣어 비동기적으로 처리하는 데 사용된다.
    • 분산 락: 여러 인스턴스에서 동일한 리소스에 접근할 때 동시성 문제를 해결하기 위해 사용된다.
    • 카운터 및 레이트 리미팅: 특정 이벤트의 발생 횟수를 추적하거나, 트래픽을 제한하는 데 사용된다.
    • 추천 시스템: 사용자 행동 데이터를 저장하고 이를 기반으로 빠르게 추천을 생성하는 데 사용된다.
    • 배치 처리: 실시간 업데이트가 빈번한 데이터를 임시로 저장하고 일정 시간 간격으로 데이터베이스에 반영하는 데 사용된다.
    • Pub/Sub 시스템: 실시간 메시징이나 알림 시스템을 구현하는 데 사용된다.
  • 자료 구조
    • String
      • key-value 형태로 저장된다
      • set/get 사용
      • # 한개 조회 set <key> <value> get <key> <value> # 여러개 조회 mset <key> <value> <key> <value> ... mget <key> <key> <key> ...
    • List
      • # 왼쪽에 삽입 lpush <key> <value> # 오른쪽에 삽입 rpush <key> <value> # 삭제 lpop <key> rpop <key>
    • Set
      • 유일한 값 → Access Token 저장 시 set 사용
      • sadd <key> <item> # 존재 여부를 체크, 있으면 1 없으면 0 반환 sismember <key> <item> # 삭제 srem <key> <value> # key의 모든 item 조회 smembers <key>
      • Sorted Set → set에 우선순위들을 가짐
    • Hash
      • key-value
      • # 한개 값 삽입 및 삭제 hset <key> <subkey> <value> hget <key> <subkey> # 여러 값 삽입 및 삭제 hmset <key> <subkey> <value> <subkey> <value> ... hnget <key> <subkey> <subkey> <subkey> ... # 모든 subkey와 value 가져오기, Collection에 너무 많은 key가 있으면 장애의 원인이 됨 hgetall <key> # 모든 value값만 가져오기 hvlas <key>
    • 이외에도 Stream, BitMap, HyperLogLog.. 등의 다양한 데이터 형식을 제공한다.
    •  

Session Clustering

  • 로드 밸런서를 사용할 때 같이 여러 서버나 인스턴스에서 공유되는 사용자 세션 데이터를 중앙화된 Redis 인스턴스에 저장하여 관리하려면, Session Clustering을 사용한다.
  • 이를 통해 분산환경에서의 일관성을 유지하고, 확장성을 높일 수 있다.
  • 작동 방식
    • 사용자가 웹 애플리케이션에 처음 접속하면
        1. Redis에 세션을 생성하여 고유 세션 ID 할당
        1. 세션 데이터가 갱신되면, 이 데이터는 Redis에 저장/업데이트 되고, 빠르게 조회할 수 있다.
        1. 사용자가 다른 서버로 요청을 보낸다 해도, Redis에서 관리되기 때문에 일관된 세션 상태 조회가 가능하다
  • 구현 방법
    • implementation 'org.springframework.boot:spring-boot-starter-data-redis'
    • application.yaml
      • session: store-type: redis ... redis: host: localhost password: port: 6379
    • redisTemplate
      • @Configuration public class RedisConfig { @Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) { RedisTemplate<String, Object> template = new RedisTemplate<>(); template.setConnectionFactory(redisConnectionFactory); template.setKeySerializer(new StringRedisSerializer()); template.setValueSerializer(new GenericJackson2JsonRedisSerializer()); return template; } }
    • @EnableRedisHttpSession
      • @Configuration @EnableRedisHttpSession(maxInactiveIntervalInSeconds = 1800) // 세션 타임아웃을 30분으로 설정 public class RedisHttpSessionConfig { // 이 클래스에는 추가적인 세션 관련 설정이 들어갈 수 있다. }
 
 

Pub-Sub에서의 Redis

  • 리소스 집약적인 작업 수행의 경우 Queue에 추가 후 후에 처리함으로 백엔드 작업 처리를 확장할 수 있다.
  • pub-sub에서 Redis vs Kafka
    • Redis는 Kafka보다 더 단순하고, 가볍다. 또 메세지가 실시간으로 전송된다.
      • 구현 시 기능이 직관적이고, 복잡한 프로세스 없이 간단하게 설정할 수 있다.
      • 복잡한 브로커 설정 없이 운영할 수 있어 경량적이다. 메세지는 바로 구독자에게 전달된다.
    • 하지만 Redis는 Kafka보다 내구성이 약하고, 수평적 확장이 제한적이다.
      • Redis는 실시간, 일회성으로 구독자에게 메세지를 넘긴다. Redis는 영구적 저장이 아니기 때문에 일시적으로 연결이 끊길 경우 데이터가 유실될 수 있다.
        • Kafka의 경우 메세지가 Disk에 영구적으로 저장되고, 원하는 위치부터 메세지를 읽을 수 있다.
      • Kafka는 필요에 따라 파티션/브로커의 확장을 통한 수평적 확장이 가능하다.
    • 결론
      • Redis는 단순하고 실시간성이 중요한 경량 메시징 시스템에 적합하며, Pub/Sub 모델을 쉽게 구현할 수 있다. 그러나 내구성, 확장성, 메시지 전달 보장 측면에서 한계가 있다.
      • Kafka는 대규모 메시징, 로그 수집, 데이터 스트리밍 등 고성능, 고확장성을 요구하는 시스템에 적합하며, 내구성 있는 메시지 전달을 보장한다. 다만, 설정과 운영이 더 복잡하다.
 
 
 

Redis Stream

  • Redis 5.0부터 Redis Stream이라는 새 데이터 구조가 추가되었다.
  • Redis Streams는 Kafka, RabbitMQ와 같은 메시지 브로커의 역할을 일부 수행할 수 있으며, 고성능과 단순성을 특징으로 하는 Redis의 특성을 유지하면서도 강력한 스트리밍 기능을 제공한다.
  • append only로 저장된다.(추가만 가능하다)
  • 주요 개념
    • Stream
      • XADD 명령어로 생성되며, 각 Stream은 Entries로 구성된다.
    • Entries
      • Stream 내의 각 항목은 하나의 엔트리로, 고유한 ID와 함께 데이터를 저장한다.
      • XADD <stream-key> <message-id> <field> <name> ... <field> <name>
    • Consumer group
      • Consumer group을 지정할 수 있다. 여러 컨슈머가 동일한 Stream을 동시에 읽을 수 있도록 해준다.
  • 예시 사용 코드
    • Stream 구성, 메세지 구독
      • @Bean public Subscription streamSubscription(RedisConnectionFactory factory) { StreamMessageListenerContainer.StreamMessageListenerContainerOptions<String, MapRecord<String, String, String>> options = StreamMessageListenerContainer.StreamMessageListenerContainerOptions.builder() .pollTimeout(Duration.ofSeconds(1)) .build(); StreamMessageListenerContainer<String, MapRecord<String, String, String>> listenerContainer = StreamMessageListenerContainer.create(factory, options); Subscription subscription = listenerContainer.receiveAutoAck( // 컨슈머 지정 Consumer.from("group-1", "consumer-1"), // 스트림 지정 StreamOffset.create("mystream", ReadOffset.lastConsumed()), messageListener ); listenerContainer.start(); return subscription; }
    • 메세지 리스너
      • @Component public class MessageListener implements StreamListener<String, MapRecord<String, String, String>> { @Override public void onMessage(MapRecord<String, String, String> message) { System.out.println("Received message: " + message.getValue()); } }
    • 메시지 발행 (컨트롤러 등을 통해 호출한다.)
      • @Autowired private RedisTemplate<String, String> redisTemplate; public void sendMessage(String stream, String key, String value) { StreamOperations<String, String, String> streamOps = redisTemplate.opsForStream(); Map<String, String> message = new HashMap<>(); message.put(key, value); streamOps.add(ObjectRecord.create(stream, message)); System.out.println("Message sent to stream: " + stream); }
 
 

Master-Slave 구조와 Redis Sentinel

  • Redis에도 Master-Slave 구조가 존재한다. redis를 여러대 구축하여 데이터 복제와 가용성을 높일 수 있다.
    • Master 노드:
      • 모든 쓰기 작업(SET, INCR 등)이 이루어지는 주 노드
      • Master 노드는 데이터를 변경하고, 변경된 데이터를 Slave 노드로 전파한다.
    • Slave 노드:
      • Master 노드의 데이터를 읽기 전용으로 복제한다.
      • 여러 개의 Slave 노드가 하나의 Master 노드를 복제할 수 있다.
      • Slave 노드는 Master로부터 데이터를 받아 실시간으로 동기화하며, 읽기 작업(GET 등)을 분산 처리하여 Master의 부하를 줄인다.
  • Redis Sentinel
    • Redis에서 Master-Slave 구조의 고가용성을 보장하기 위해 사용되는 시스템
    • Master 노드에 장애가 발생했을 때, Sentinel은 투표를 통해 자동으로 새로운 Master 노드를 선출하고 기존 Slave 노드들을 새로운 Master에 연결한다.
    • Sentinel은 Master와 Slave 노드의 상태를 지속적으로 모니터한다.
  • 구성 docker-compose.yaml 파일 예시(2개의 Redis와 3개의 Sentinel)
version: "3" services: my-redis-a: hostname: redis-master container_name: redis-master image: "bitnami/redis" environment: - REDIS_REPLICATION_MODE=master - ALLOW_EMPTY_PASSWORD=yes ports: - 5002:6379 my-redis-b: hostname: redis-replicas-1 container_name: redis-replicas-1 image: "bitnami/redis" environment: - REDIS_REPLICATION_MODE=slave - REDIS_MASTER_HOST=redis-master - ALLOW_EMPTY_PASSWORD=yes ports: - 5001:6379 depends_on: - my-redis-a redis-sentinel-1: container_name: sentinel1 image: "bitnami/redis-sentinel:latest" environment: - REDIS_SENTINEL_DOWN_AFTER_MILLISECONDS=3000 - REDIS_MASTER_HOST=redis-master - REDIS_MASTER_PORT_NUMBER=6379 - REDIS_MASTER_SET=mymaster - REDIS_SENTINEL_QUORUM=2 ports: - 26379:26379 depends_on: - my-redis-a - my-redis-b redis-sentinel-2: container_name: sentinel2 image: "bitnami/redis-sentinel:latest" environment: - REDIS_SENTINEL_DOWN_AFTER_MILLISECONDS=3000 - REDIS_MASTER_HOST=redis-master - REDIS_MASTER_PORT_NUMBER=6379 - REDIS_MASTER_SET=mymaster - REDIS_SENTINEL_QUORUM=2 ports: - 26380:26379 depends_on: - my-redis-a - my-redis-b redis-sentinel-3: container_name: sentinel3 image: "bitnami/redis-sentinel:latest" environment: - REDIS_SENTINEL_DOWN_AFTER_MILLISECONDS=3000 - REDIS_MASTER_HOST=redis-master - REDIS_MASTER_PORT_NUMBER=6379 - REDIS_MASTER_SET=mymaster - REDIS_SENTINEL_QUORUM=2 ports: - 26381:26379 depends_on: - my-redis-a - my-redis-b
Share article

Tom의 TIL 정리방