개인의 기록
  • 소개
  • 프로젝트
  • 글
  • 링크

© 2026 newgirok

← 글 목록

Redis Pub/Sub — 실시간 메시지 브로드캐스트

2026년 2월 5일
RedisPub/Sub실시간메시지이벤트

여러 서버에서 발생한 이벤트를 실시간으로 다른 서버에 알려야 합니다. Redis Pub/Sub를 쓰면 발행자가 채널에 메시지를 보내는 순간 모든 구독자가 즉시 받습니다.

발행자(Publisher): 메시지를 채널에 보내는 주체. 구독자 목록을 알 필요가 없습니다.

구독자(Subscriber): 특정 채널을 구독해 메시지를 받는 주체. 메시지를 보내지 않고 받기만 합니다.

기본 개념

Publisher → [채널: chat:room:1] → Subscriber 1
                               → Subscriber 2
                               → Subscriber 3

발행자는 누가 구독하는지 모르고, 구독자는 누가 발행하는지 모릅니다. 채널이라는 공통 이름을 통해 연결됩니다.

기본 명령

구독 (Subscribe)

# 특정 채널 구독
SUBSCRIBE chat:room:1

# 여러 채널 구독
SUBSCRIBE chat:room:1 chat:room:2 notifications:user:1000

구독 명령은 블로킹 상태로 메시지를 기다립니다. 메시지가 오면 출력합니다.

1) "message"
2) "chat:room:1"
3) "안녕하세요!"

발행 (Publish)

# 채널에 메시지 전송
PUBLISH chat:room:1 "안녕하세요!"
# (integer) 3  ← 수신한 구독자 수

패턴 구독

와일드카드로 여러 채널을 한 번에 구독합니다.

# chat: 으로 시작하는 모든 채널 구독
PSUBSCRIBE chat:*

# notifications: 로 시작하는 채널
PSUBSCRIBE notifications:*

# 구독 해제
PUNSUBSCRIBE chat:*
1) "pmessage"
2) "chat:*"          ← 패턴
3) "chat:room:5"     ← 실제 채널
4) "새 메시지입니다"

Node.js 예시

import { createClient } from "redis";

// 구독 전용 클라이언트
const subscriber = createClient();
await subscriber.connect();

// 발행 전용 클라이언트
const publisher = createClient();
await publisher.connect();

// 채널 구독
await subscriber.subscribe("notifications", (message) => {
  console.log("받은 메시지:", message);
});

// 메시지 발행
await publisher.publish("notifications", JSON.stringify({
  type: "new_comment",
  postId: 1000,
  userId: 500,
}));

구독과 발행에 클라이언트를 분리하는 이유는, 구독 상태에 들어간 클라이언트는 다른 명령을 실행할 수 없기 때문입니다.

실전 사례

실시간 채팅

// 메시지 발송 (서버 A)
await publisher.publish(
  `chat:room:${roomId}`,
  JSON.stringify({ userId, message, timestamp })
);

// 메시지 수신 (서버 B, C...)
await subscriber.subscribe(`chat:room:${roomId}`, (msg) => {
  const data = JSON.parse(msg);
  io.to(roomId).emit("message", data);  // WebSocket 전파
});

서버 간 캐시 무효화

// 상품 정보 변경 시 (서버 A)
await db.products.update(id, data);
await publisher.publish("cache:invalidate", `product:${id}`);

// 모든 서버에서 캐시 삭제
await subscriber.subscribe("cache:invalidate", async (key) => {
  await redis.del(key);
});

실시간 알림

// 특정 사용자에게 알림 발송
await publisher.publish(
  `notifications:user:${userId}`,
  JSON.stringify({ type: "like", postId: 1000 })
);

// 해당 사용자 세션이 있는 서버에서 수신
await subscriber.subscribe(`notifications:user:${userId}`, (msg) => {
  socket.emit("notification", JSON.parse(msg));
});

Pub/Sub의 한계

Redis Pub/Sub는 Fire and Forget 방식입니다.

  • 구독자가 없으면 메시지가 사라짐
  • 구독자가 오프라인이면 그동안의 메시지를 놓침
  • 메시지 전달 보장이 없음
  • 히스토리 조회 불가

Fire and Forget: 메시지를 보내고 수신 확인을 기다리지 않는 방식. 성능이 높지만 전달 보장이 없습니다.

이런 한계가 문제라면 Redis Stream을 사용합니다. Stream은 메시지를 저장하고, 구독자가 재접속해도 놓친 메시지를 받을 수 있습니다.

Pub/Sub vs Stream

항목Pub/SubStream
메시지 저장없음영구 저장
놓친 메시지 수신불가가능
소비자 그룹없음있음
처리 확인없음ACK
적합한 용도실시간 브로드캐스트신뢰성 있는 메시지 처리

구독자 모두에게 즉시 전달만 하면 된다면 Pub/Sub, 메시지 유실 없이 처리해야 한다면 Stream을 선택합니다.

← 이전 글Redis 파이프라인 — 네트워크 왕복 줄이기
다음 글 →Redis Streams — 신뢰성 있는 메시지 큐