1. 도커 컨테이로 redis-test 를 만든다

docker run -d --name redis-test -p 6379:6379 redis
d819dab48c79ac486cba7351ece464566e2f2e42c47867b37db1d023031930b3

devwonny@devwonnyui-MacBookAir Documents % docker logs d819dab48c79
-> 결과
1:C 24 Mar 2025 08:20:16.830 * oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
1:C 24 Mar 2025 08:20:16.830 * Redis version=7.4.2, bits=64, commit=00000000, modified=0, pid=1, just started
1:C 24 Mar 2025 08:20:16.830 # Warning: no config file specified, using the default config. In order to specify a config file use redis-server /path/to/redis.conf
1:M 24 Mar 2025 08:20:16.830 * monotonic clock: POSIX clock_gettime
1:M 24 Mar 2025 08:20:16.831 * Running mode=standalone, port=6379.
1:M 24 Mar 2025 08:20:16.831 * Server initialized
1:M 24 Mar 2025 08:20:16.831 * Ready to accept connections tcp
2. SubscriberA : redis-cli(A) : 구독하기 mychannel

기존 터미널에서 실행(A)
docker exec -it redis-test redis-cli ---> redis-test 에 접근해서 redis-cli 실행(A)
3. Publisher : redis-cli(B) : 발행하기 mychannel

새로운 터미널 생성(B)
docker exec -it redis-test redis-cli
127.0.0.1:6379> PUBLISH mychannel "안녕 레디스!"
(integer) 1
127.0.0.1:6379> PUBLISH mychannel "?hello redis?!"
(integer) 1
127.0.0.1:6379> PUBLISH mychannel "hello redis"

3.1. SubscriberA : redis-cli(A) : 구독자는 메세지 받음

1) "message"
2) "mychannel"
3) "\xec\x95\x88\xeb\x85\x95 \xeb\xa0\x88\xeb\x94\x94\xec\x8a\xa4!" -> "안녕 레디스!"
1) "message"
2) "mychannel"
3) "\xechello redis\xa4!" -> "?hello redis?!"
1) "message"
2) "mychannel"
3) "hello redis" -> "hello redis"
4. SubscriberA : redis-cli(C) 추가 : 구독하기 mychannel

새로운 터미널 생성(C)
docker exec -it redis-test redis-cli
5. Publisher : redis-cli(B) : 발행하기 mychannel

PUBLISH mychannel "hello redis22"
5-1. SubscriberA : redis-cli(A) : 구독자 "hello redis22" 메세지 받음

5-2. SubscriberA : redis-cli(C) : 구독자 "hello redis22" 메세지 받음

6. 도커 컨테이너 접근
apt update && apt install -y procps
ps aux

- redis-server (PID 1)
- Redis 서버가 실행 중이며 포트 6379을 리스닝 중
- redis-cli 3개
- PID 22, 28, 33
- 각각:
- 하나는 publisher
- 나머지 둘은 subscriber1, subscriber2
🧩 구조
[도커 컨테이너 하나]
┌────────────────────────────────────┐
│ redis-server (*:6379) │
│ ├─ redis-cli (subscriber1) │
│ ├─ redis-cli (subscriber2) │
│ └─ redis-cli (publisher) │
└────────────────────────────────────┘
✅ 상황 요약
- 하나의 도커 컨테이너에서 Redis 서버 + redis-cli 3개가 동작 중
- 즉, 같은 서버(호스트) 내에서 Redis에 접근하는 구조
- 모든 redis-cli가 같은 Redis 서버(*:6379)에 접근 중
🚀 서버 여러 대에서의 구조 (채팅 시스템)
✅ 다중 채팅 서버, 고려 사항
- 현재 도커 환경에선 한 서버 내에서의 Pub/Sub 테스트 상태
- 실제 채팅 서버 여러 대에서 Redis Pub/Sub을 사용하려면:
- 모든 서버가 단일 Redis 서버에 연결되게 설정 -> 각 서버에 subscriber를 띄우고, 메시지를 받아 처리하면 됨
- 각 서버 별로 redis 설치, redis cluster사용
이라고 생각했으나...
🔹 "모든 노드들한테 다 쏴줘야 안정성이 보장이됨"
클러스터 환경에선 메시지가 모든 노드에 BroadCast되지 않음
메시지를 모든 노드(subscriber가 있을지도 모르는 노드)에 직접 publish 해줘야 하기 때문에,
- 자동으로 퍼지지 않음 → 안정성이 떨어짐
- 결국 모든 노드에 수동으로 보내야 함
🔹 "node1-slave에 쏘면, subscribe 안한 친구들은 못 받아"
클러스터 환경에서 어떤 특정 노드(예: slave)에 메시지를 publish 해도,
- 그 노드에 subscribe 중인 클라이언트가 없으면 → 아무도 못 받는다 😨
- 메시지 브로드캐스트가 안 되기 때문
🔹 "pub/sub만 쓸 거라면 단일노드가 맞긴 하지"
Pub/Sub 기능만 쓸 거면, 단일 Redis 서버를 사용하는 것이 더 정확하고 간단하다
- 하나의 Redis 인스턴스에 모든 서버가 붙어서 Pub/Sub 하면 됨
🔹 "근데 장애났을 때 모든 pub/sub이 장애가 나니"
단일 Redis는 장애가 나면 전체 Pub/Sub이 멈춘다는 단점도 있어요.
- 그래서 고가용성이 필요한 경우엔 더 고민이 필요해요
🔹 "여러 번 쏘면서 중복 방지하고 클러스터로 쓸래, 간단하게 그냥 쓸래 이런 걸 고려해야지"
단일 Redis로 간단하게 | 장애는 리스크 있지만, Pub/Sub은 안정적으로 잘 작동 |
Redis Cluster + 다중 Publish | 고가용성은 확보할 수 있으나, 직접 메시지를 모든 노드에 보내야 하고 복잡 |
항목 | 단일 Redis | Redis Cluster |
Pub/Sub 지원 | ✅ 완전 지원 | ❌ 제대로 안됨 |
메시지 브로드캐스트 | ✅ 자동 | ❌ 직접 여러 노드에 쏴야 함 |
장애 대응 | ❌ 단일 장애점 존재 | ✅ 자동 Failover 가능 (하지만 Pub/Sub엔 영향 적음) |
구현 난이도 | 😄 쉬움 | 😰 복잡하고 제한 많음 |
🔥 그래서 실제로 어떻게 해야 할까?
- Pub/Sub만 쓸 목적이라면 → 단일 Redis + Redis Sentinel 정도로 고가용성 대응하는 게 현실적이에요
- 정말 고가용성 + 메시지 유지 + 확장성 필요하면 → Redis Stream, 혹은 Kafka 고려
✅ 1. 모든 서버가 "단일 Redis 서버"에 연결 (가장 일반적인 방식)
🧩 구조
[서버 A] ──┐
[서버 B] ──┤── Redis 서버 (Pub/Sub 단일 인스턴스)
[서버 C] ──┘
👍 장점
- 구현이 간단함
- 메시지 전달 신뢰성 높음
- subscriber가 여러 서버에 있어도 메시지가 모두 브로드캐스트됨
👎 단점
- Redis 인스턴스 하나에 트래픽이 몰리면 병목 발생
- 고가용성/스케일 아웃이 어려움 (단일 장애 지점, SPOF)
👉 이런 경우에 적합
- 실시간 채팅, 알림 등 브로드캐스트 성격이 강할 때
- 서버 수가 적거나 중간 정도 트래픽
✅ 2. 각 서버마다 Redis 설치 + Redis Cluster 구성
🧩 구조
[서버 A] ─ Redis A (Cluster Node)
[서버 B] ─ Redis B (Cluster Node)
[서버 C] ─ Redis C (Cluster Node)
👍 장점
- 분산 처리 가능 (확장성)
- Redis 자체적으로 데이터 샤딩 및 고가용성
- 클러스터링으로 장애 대응
👎 단점
- Pub/Sub 메시지는 클러스터 간 브로드캐스트되지 않음
- ❗️subscriber와 publisher가 같은 노드에 있어야만 메시지를 수신함
- 즉, 클러스터 환경에서는 Pub/Sub 제대로 작동하지 않음
- 대신에 Redis Stream이나 Kafka를 써야 함
👉 이런 경우에 적합
- 고가용성, 확장성이 중요한 서비스
- 하지만 Pub/Sub 용도보단 일반 데이터 저장/조회에 더 적합
- Pub/Sub 대신 Redis Stream, Kafka 사용을 고려해야 함
선택지 | 추천 여부 | 이유 |
단일 Redis | ✅ 추천 | 간단하고 Pub/Sub 동작 확실 |
Redis Cluster | ❌ 비추천 (Pub/Sub 용도로는) | Pub/Sub 메시지 브로드캐스트 안됨 |
Redis Stream | ✅ 중장기적으로 고려 | 소비자 그룹으로 메시지 보장, 로그 기반 |
Kafka | ✅ 고성능/고신뢰 요구 시 | 메시지 내구성과 확장성 우수 |
🧠 Redis Cluster란?
여러 개의 Redis 노드를 묶어서 하나의 큰 Redis처럼 작동하게 하는 구조
- 하나의 Redis 인스턴스로 감당 못할 만큼 데이터량이 많거나
- Redis 서버에 장애가 나도 서비스를 유지하고 싶을 때
- 여러 Redis를 자동 분산 + 고가용성으로 구성한 것
🧱 구조 그림 (기본적으로 6개 노드가 필요)
┌────────────┐
│ Client │
└────┬───────┘
│
┌──────┴───────┐
▼ ▼
┌────────────┐ ┌────────────┐
│ Redis Node │ │ Redis Node │ ... (최소 3개 Master)
│ (Master) │ │ (Master) │
└────┬───────┘ └────┬───────┘
│ │
▼ ▼
┌────────────┐ ┌────────────┐
│ Redis Node │ │ Redis Node │ ... (각 Master의 Slave)
│ (Replica) │ │ (Replica) │
└────────────┘ └────────────┘
서로 IP가 다른 5개의 Redis 서버를 클러스터로 묶는 방법
예: 5대 서버 각각의 IP가 다음과 같다고 가정해요.
- 192.168.0.1
- 192.168.0.2
- 192.168.0.3
- 192.168.0.4
- 192.168.0.5
- 포트는 모두 7000 사용.
1) 각 서버에 Redis 설치 + 설정 파일 수정
redis.conf (or redis-cluster.conf)에 아래 항목 반드시 수정:
port 7000
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
appendonly yes
bind 0.0.0.0
그리고 redis-server redis.conf로 각 서버에서 Redis 실행
2) 방화벽(보안그룹) 확인
모든 서버가 서로의 7000, 17000 포트를 열어야 함
- Redis 데이터: 7000
- Redis 클러스터 내부 통신: 7000 + 10000 = 17000
7000-7005 / TCP 허용
17000-17005 / TCP 허용
3) 클러스터 생성 명령 실행
어느 한 서버에서 실행:
redis-cli --cluster create \
192.168.0.1:7000 \
192.168.0.2:7000 \
192.168.0.3:7000 \
192.168.0.4:7000 \
192.168.0.5:7000 \
--cluster-replicas 0
--cluster-replicas 0: 단순 5개 마스터 노드 구성
복제본을 만들고 싶으면 --cluster-replicas 1 등으로 설정
4) 정상 작동 확인
redis-cli -c -h 192.168.0.1 -p 7000 cluster nodes
각 노드 ID, IP, 포트, 상태(Master/Slave)가 출력되면 성공!
5) 로컬에서 테스트할 경우
Docker로 5개 Redis 컨테이너 만들고, --net=host or Docker 네트워크 이용
docker network create redis-cluster-net
🔄 클러스터의 핵심 기능
1) 데이터 분산 저장 (Sharding)
- Redis는 기본적으로 단일 노드에서 모든 키를 저장해요.
- 클러스터에서는 키값 해싱 → 슬롯(16384개) → 노드에 분배해서 저장해요.
- 즉, 데이터를 여러 서버에 나눠서 저장함.
2) 장애 복구 (Failover)
- Master가 죽으면 연결된 Replica가 자동으로 승격돼요.
- 클러스터가 알아서 장애를 감지하고 서비스 무중단 유지 가능.
3) 수평 확장
- 노드를 더 추가하면 성능/용량이 증가.
⚠️ 주의할 점
- Pub/Sub은 클러스터에서 제약이 많아요!
- 메시지를 모든 노드에 브로드캐스트하지 않음
- Subscriber가 있는 노드에만 메시지가 전달됨 → 실시간 알림/채팅에는 부적합
목적 |
추천 방식 |
간단한 캐시, Pub/Sub 기반 채팅 | 단일 Redis (혹은 Sentinel) |
데이터량 많고, 고가용성/확장성 필요 | Redis Cluster |
채팅 메시지 브로드캐스트 | 단일 Redis or Redis Stream or Kafka |
단일 Redis or Redis Stream 이 설치랑 코드가 다른가?
항목 | Redis Pub/Sub | Redis Stream |
메시지 저장 | ❌ 안 됨 | ✅ 됨 |
실시간성 | ✅ 아주 좋음 | ✅ 좋음 |
장애 복구 | ❌ 어려움 (메시지 유실) | ✅ 메시지 유지 |
소비자 그룹 | ❌ 없음 | ✅ 있음 |
설치 방식 | 동일 | 동일 |
코드 작성 | 🔁 완전 다름 | 🔁 완전 다름 |
복잡도 | 😄 쉬움 | 😅 복잡함 |
채팅 | ✅ 1:1 or 실시간 알림 | ✅ 대규모 안정적인 채팅 시스템 |
사용 목적 | 추천 방식 |
간단한 실시간 채팅 or 알림 | Pub/Sub |
안정적 메시지 큐, 저장, 다시 처리, 멀티 서버 소비 | Redis Stream |
✅ 설치 측면: 거의 똑같음
Redis 자체는 설치만 하면 Pub/Sub도 가능하고, Stream도 기본 제공돼요.
docker run -d --name redis -p 6379:6379 redis:latest
- PUBLISH, SUBSCRIBE 가능 (Pub/Sub)
- XADD, XREAD, XGROUP 가능 (Stream)
✅ 코드 측면: 완전히 다름
🔹 단일 Redis + Pub/Sub 방식
👉 개념
- 실시간 메시지 전송
- 메시지를 받는 subscriber가 실시간으로 살아 있어야 함
- 메시지를 Redis가 저장하지 않음
PUBLISH chat-room-1 "hello"
SUBSCRIBE chat-room-1
// 발행
redisTemplate.convertAndSend("chat-room-1", "hello");
// 구독
@RedisMessageListener(topics = "chat-room-1")
public void handleMessage(String message) {
System.out.println("수신 메시지: " + message);
}
🔹 Redis Stream 방식
👉 개념
- 메시지 저장 가능 (로그처럼)
- 나중에 읽거나, 소비자 그룹별로 처리 가능
- subscriber가 꺼져 있어도 메시지 손실 없음
XADD mystream * message "hello"
XREAD COUNT 1 STREAMS mystream 0
// 메시지 추가
Map<String, String> message = Map.of("user", "wonny", "text", "hello");
redisTemplate.opsForStream().add("chat-stream", message);
// 메시지 읽기
List<MapRecord<String, Object, Object>> messages = redisTemplate.opsForStream()
.read(StreamReadOptions.empty(), StreamOffset.create("chat-stream", ReadOffset.latest()));
✅ Redis Stream = Redis 안에 브로커 역할을 하는 데이터 구조
- 별도의 Kafka처럼 독립된 브로커가 있는 건 아니고,
- Redis 서버 자체가 메시지를 저장하는 브로커처럼 동작해요.
즉, Redis에 XADD로 메시지를 추가하면, 그게 일종의 로그(append-only stream) 으로 쌓이고, 각 서버(=consumer)는 거기서 XREAD, XREADGROUP으로 메시지를 가져가는 구조예요.
📦 구조 예시
┌────────────┐
[Producer 1]───▶│ │
│ │
[Producer 2]───▶│ Redis │◀── [Consumer Group A]
│ Stream │◀── [Consumer Group B]
[Producer 3]───▶│ (XADD) │
└────────────┘
✅ Redis Stream이 Pub/Sub과 다른 핵심 포인트
항목 | Redis Pub/Sub | Redis Stream |
메시지 저장 | ❌ 안 됨 | ✅ 됨 (디스크에도) |
메시지 재시도 | ❌ 불가 | ✅ 가능 |
메시지 누락 방지 | ❌ (실시간 안 받으면 끝) | ✅ 안 읽은 거 다 있음 |
소비자 그룹 | ❌ 없음 | ✅ 있음 (XGROUP) |
서버 여러 대 처리 | ⚠️ 브로드캐스트 | ✅ 서버 여러 대가 분산해서 소비 가능 |
🚀 서버 여러 대에서 어떻게 사용하나요?
Redis Stream의 핵심은 소비자 그룹 (Consumer Group) 이에요.
Stream 이름: chat-messages
Consumer Group: chat-group
서버 A → consumer name: chat-a-1
서버 B → consumer name: chat-b-1
서버 C → consumer name: chat-c-1
이렇게 설정하면 Redis가 알아서 메시지를 분산 처리해줘요.
- 각 서버는 XREADGROUP을 사용해 자기 순서대로 메시지를 가져감
- 메시지 누락 없이 안정적으로 처리
- 장애가 나면 pending 메시지 다시 확인해서 재시도 가능
//1. 메시지 추가 (생산자)
Map<String, String> msg = Map.of("user", "wonny", "text", "hello");
redisTemplate.opsForStream().add("chat-messages", msg);
//2. 메시지 읽기 (소비자 그룹)
StreamReadOptions options = StreamReadOptions
.empty()
.count(1)
.block(Duration.ofSeconds(2));
StreamOffset<String> offset = StreamOffset
.create("chat-messages", ReadOffset.lastConsumed());
List<MapRecord<String, Object, Object>> messages =
redisTemplate.opsForStream()
.read(Consumer
.from("chat-group", "server-A-1"), options, offset);
✅ Redis Stream의 진짜 강점
- 메시지 유실 거의 없음
- 서버 여러 대가 동시에 안전하게 메시지 처리 가능
- Kafka까지는 필요 없지만 Pub/Sub보다 강력한 보장성이 필요한 시스템에 적합
- 예: 채팅, 알림, 작업 큐 등
'학습 기록 (Learning Logs) > Today I Learned' 카테고리의 다른 글
원자성 (0) | 2025.03.25 |
---|---|
비관적락, 낙관적락 (0) | 2025.03.25 |
✨ Operating Systems: Three Easy Pieces ✨ - 개요 (0) | 2025.03.23 |
머신러닝 (0) | 2025.03.21 |
✨ Operating Systems: Three Easy Pieces ✨ - 프로세스 (0) | 2025.03.19 |