레디스(Redis, remote dictionary Server) Session ClusteringPub-Sub에서의 RedisRedis StreamMaster-Slave 구조와 Redis Sentinel
레디스(Redis, remote dictionary Server)
- 키-값 구조의 비정형 데이터를 저장하고 관리하기 위한 오픈소스 기반 비관계형 DMBS이다.
- 모든 데이터를 메모리에 저장하고 조회한다. 메모리 접근이 디스크 접근보다 빠르기 때문에, 기존 관계형 데이터베이스에 비해 속도가 빠르다
- 다양한 자료구조를 Key-Value 형태로 저장한다.
- 다양한 자료구조를 제공한다는 것은, 개발의 편의성과 난이도에서 이점을 주고, 개발시간 단축이 가능하다.
- 사용 사례
- 캐싱: 자주 조회되는 데이터를 메모리에 저장하여 데이터베이스에 대한 부하를 줄이고 응답 시간을 단축하는 데 사용된다.
- 세션 스토리지: 사용자 세션 데이터를 관리하여 분산된 환경에서 빠르게 세션 정보를 읽고 쓸 수 있도록 한다.
- 실시간 분석: 실시간 데이터를 수집하고 분석하는 데 사용되어, 빠르게 통계나 분석 결과를 제공한다.
- 큐잉 시스템: 작업이나 메시지를 큐에 넣어 비동기적으로 처리하는 데 사용된다.
- 분산 락: 여러 인스턴스에서 동일한 리소스에 접근할 때 동시성 문제를 해결하기 위해 사용된다.
- 카운터 및 레이트 리미팅: 특정 이벤트의 발생 횟수를 추적하거나, 트래픽을 제한하는 데 사용된다.
- 추천 시스템: 사용자 행동 데이터를 저장하고 이를 기반으로 빠르게 추천을 생성하는 데 사용된다.
- 배치 처리: 실시간 업데이트가 빈번한 데이터를 임시로 저장하고 일정 시간 간격으로 데이터베이스에 반영하는 데 사용된다.
- Pub/Sub 시스템: 실시간 메시징이나 알림 시스템을 구현하는 데 사용된다.
- 자료 구조
- String
- key-value 형태로 저장된다
- set/get 사용
# 한개 조회
set <key> <value>
get <key> <value>
# 여러개 조회
mset <key> <value> <key> <value> ...
mget <key> <key> <key> ...
# 왼쪽에 삽입
lpush <key> <value>
# 오른쪽에 삽입
rpush <key> <value>
# 삭제
lpop <key>
rpop <key>
- 유일한 값 → Access Token 저장 시 set 사용
sadd <key> <item>
# 존재 여부를 체크, 있으면 1 없으면 0 반환
sismember <key> <item>
# 삭제
srem <key> <value>
# key의 모든 item 조회
smembers <key>
- 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>
Session Clustering
- 로드 밸런서를 사용할 때 같이 여러 서버나 인스턴스에서 공유되는 사용자 세션 데이터를 중앙화된 Redis 인스턴스에 저장하여 관리하려면, Session Clustering을 사용한다.
- 이를 통해 분산환경에서의 일관성을 유지하고, 확장성을 높일 수 있다.
- 작동 방식
- 사용자가 웹 애플리케이션에 처음 접속하면
- Redis에 세션을 생성하여 고유 세션 ID 할당
- 세션 데이터가 갱신되면, 이 데이터는 Redis에 저장/업데이트 되고, 빠르게 조회할 수 있다.
- 사용자가 다른 서버로 요청을 보낸다 해도, Redis에서 관리되기 때문에 일관된 세션 상태 조회가 가능하다
- 구현 방법
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
- application.yaml
session:
store-type: redis
...
redis:
host: localhost
password:
port: 6379
@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;
}
}
@Configuration
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 1800) // 세션 타임아웃을 30분으로 설정
public class RedisHttpSessionConfig {
// 이 클래스에는 추가적인 세션 관련 설정이 들어갈 수 있다.
}
Pub-Sub에서의 Redis
- 리소스 집약적인 작업 수행의 경우 Queue에 추가 후 후에 처리함으로 백엔드 작업 처리를 확장할 수 있다.
- 여기서 Queue의 역할을 Redis가 함으로써 Pub-Sub 메시징 모델을 지원할 수 있다.
- 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