본문 바로가기

[개발자를 위한 레디스] 6장 레디스를 메시지 브로커로 사용하기

@bum0w02025. 11. 15. 20:54

메시징 큐와 이벤트 스트림

메시징 큐

메시징 큐 (Message Queue)

  • 생산자가 소비자에게 데이터를 직접 푸시하는 구조이다.
  • 소비자가 메시지를 읽으면 큐에서 해당 데이터가 삭제된다.
  • 한 메시지를 하나의 소비자가 처리하는 일대일(1:1) 처리에 적합하다.

이벤트 스트림

이벤트 스트림 (Event Stream)

  • 생산자는 데이터를 스트림 저장소에 기록하고, 소비자들은 그 저장소에서 메시지를 pull 방식으로 가져간다.
  • 소비자가 메시지를 읽어도 데이터는 일정 기간 동안 저장소에 그대로 남아 있다.
  • 여러 생산자와 여러 소비자가 동시에 데이터를 주고받는 다대다(n:n) 구조에 적합하다.

 

Redis를 메시지 브로커로 사용하기

 

1. Pub/Sub

  • Redis의 Pub/Sub을 이용하면 빠르고 간단한 메시지 전달이 가능하다.
  • 메시지는 채널 전체에 즉시 전파된 뒤 저장되지 않고 사라지는 일회성 구조이다.
  • 따라서 fire-and-forget 패턴이 필요한 경우에 적합하다.
    • Ex. 간단한 알림, 이벤트 트리거 등

2. Redis 자료구조 활용한 메시지 브로커

  • List 자료구조 → 메시징 큐 구현에 사용
    • 데이터를 push/pop 하며 작업 큐 패턴을 구현할 수 있다.
  • Stream 자료구조 → 이벤트 스트림 플랫폼 구현에 사용
    • 메시지를 일정 기간 저장하고, 여러 소비자가 독립적으로 읽을 수 있다.

 

레디스의 pub/sub

레디스의 pub/sub 기능

  • Redis Pub/Sub은 매우 가벼운 메시지 전달 방식이다.
  • 메시지를 저장하거나 메타데이터를 관리하지 않는다.
  • 메시지가 발행되면 구독 중인 클라이언트에게 즉시 전달되며 이후엔 남지 않는다. (단순히, 통로 역할만 수행)

메시지 publish하기

> PUBLISH hello world
(integer) 1 # 메시지를 수신한 구독자의 수

'hello'라는 채널을 수신하고 있는 모든 서버에 'world'라는 메시지가 전파된다.

 

메시지 구독하기

> SUBSCRIBE event1 event2
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "event1"
3) (integer) 1
1) "subscribe"
2) "event2"
3) (integer) 2

SUBSCRIBE 커맨드를 사용하면 여러 채널을 동시에 구독할 수 있다.

 

> PSUBSCRIBE mail-*
Reading messages... (press Ctrl-C to quit)
1) "psubscribe"
2) "mail-*"
3) (integer) 1

PSUBSCRIBE를 사용하면, mail-*처럼 앞부분이 mail-로 시작하는 모든 채널에 전파된 메시지를 모두 수신할 수 있다.

message가 아닌 pmessage 타입으로 전달되며, SUBSCRIBE 커맨드를 이용해 메시지를 구독하는 방식과 구분된다.

PSUBSCRIBE 동작 방식

 

클러스터 구조에서의 pub/sub

클러스터는 레디스가 자체적으로 제공하는 데이터 분산 형태의 구조다.

 

클러스터 구성에서 pub/sub의 동작 방식

 

  • 하나의 노드에 메시지를 발행하면 모든 노드로 전파됨
  • 모든 노드에 복제되므로 불필요한 리소스 사용과 네트워크 부하 발생

sharded pub/sub

위와 같은 비효율을 개선하기 위해, 레디스 7.0부터 sharded pub/sub 기능이 도입되었다.

sharded pub/sub의 동작 방식

 

SPUBLISH 커맨드로 발행된 메시지는 모든 노드에 전파되지 않고, 각 노드의 복제본에만 전달된다.

10.0.0.1:6379> SPUBLISH apple a
-> Redirected to slot [7092] located at 10.0.0.2:6379
(integer) 1
10.0.0.2:6379>

데이터를 전파하려 할 때 연결된 노드에서 지정한 채널로 전달할 수 없으면 해당 메시지와 함께 다른 연결된 노드로 리다이렉트된다.

 

10.0.0.1:6379> SSUBSCRIBE apple
Reading messages... (press Ctrl-C to quit)
-> Redirected to slot [7092] located at 10.0.0.2:6379
Reading messages... (press Ctrl-C to quit)
1) "ssubscribe"
2) "apple"
3) (integer) 1
1) "smessage"
2) "apple"
3) "a"

apple 채널은 apple 키 값을 할당받을 수 있는 슬롯을 포함한 마스터 노드에 연결될 수 있도록 리다이렉트된다.

 

 

레디스의 list를 메시징 큐로 사용하기

레디스의 자료 구조 중 하나인 list는 큐로 사용하기 적절한 자료 구조다.

  • 레디스에는 큐의 tail과 head에서 데이터를 넣고 뺄 수 있는 LPUSH, LPOP, RPUSH, RPOP 커맨드가 존재하기 때문에 애플리케이션 특성에 맞는 메시징 큐를 직접 구현할 수 있다는 장점이 있다.

list의 EX 기능 (with 트위터의 웨비나 발표 내용)

트위터는 각 유저의 타임라인 캐시 데이터를 레디스에서 list 자료 구조로 관리한다.

트위터의 타임라인 캐시 동작 방법

 

유저A가 새로운 트윗을 작성하면 그 데이터는 A를 팔로우하는 유저의 타임라인 캐시에 저장된다.

 

> RPUSHX Timelinecache:userB data3
(integer) 26

> RPUSH Timelinecache:userC data3
(integer) 5

> RPUSH Timelinecache:userD data3
(integer) 0

RPUSH는 리스트가 이미 존재할 때에만 아이템을 추가한다. 이는 자주 접근하지 않는 사용자의 캐시 라인은 별도로 관리할 필요가 없기 때문이다.

 

 

list의 블로킹 기능

이벤트 큐 동작 방식

 

 

이벤트 기반 구조에서 시스템은 이벤트 루프를 돌며 신규로 처리할 이벤트가 있는지 체크한다.

 

이벤트 루프는 이벤트 큐에 새로운 이벤트가 있는지 확인하며, 새로운 이벤트가 없을 경우 정해진 폴링 간격(Polling Interval) 동안 대기한 뒤 다시 큐를 확인하는 과정을 반복한다.

  • 이러한 과정을 ‘폴링(Polling)’이라고 하며, 폴링이 진행되는 동안 애플리케이션과 큐의 리소스가 불필요하게 소모될 수 있다. 또한 이벤트가 큐에 들어왔더라도 폴링 간격 동안 대기해야 하기 때문에 즉시 처리할 수 없는 단점이 있다.
  • 이때 리스트의 블로킹 기능을 사용하면 이러한 불필요함을 줄일 수 있으며, BRPOP과 BLPOP은 각각 RPUSH와 LPUSH에 블로킹 기능을 추가한 명령어이다.
  • 클라이언트가 BLPOP을 사용해 데이터를 요청하면, 리스트에 데이터가 있으면 즉시 반환하고, 데이터가 없으면 리스트에 데이터가 들어올 때까지 기다리거나 클라이언트가 설정한 타임아웃 시간만큼 대기한 후 nil 값을 반환한다.

 

> BRPOP queue:a 5
1) "queue:a"
2) "data"

위 커맨드는 queue:a에 데이터가 입력될 때까지 최대 5초 동안 대기하고, 5초가 경과하면 nil을 반환하라는 의미이다.

타임아웃 값을 0으로 설정하면 데이터가 리스트에 들어올 때까지 제한 없이 기다리라는 의미로 쓰인다.

 

 

BRPOP 동작 방식

 

BRPOP은 2개의 데이터를 반환한다. (키 값, 반환된 데이터의 값)

 

> BRPOP queue:a queue:b queue:c timeout 1000 
1) "queue:b"
2) "DATA" # queue:b에 새로 들어온 DATA 값을 반환
(19.89s) # 실제로는 19.89초 동안 블로킹 상태로 대기한 후 데이터를 받음

BRPOP은 1,000초 동안 queue:a, queue:b, queue:c 중 어느 하나라도 데이터가 들어올 때까지 기다린 뒤, 그 중 하나의 리스트에 데이터가 들어오면 해당 값을 읽어온다.

 

 

list를 이용한 원형 큐

리스트를 이용한 원형 큐

 

특정 아이템을 계속해서 반복 접근해야 하는 클라이언트, 혹은 여러 개의 클라이언트가 병렬적으로 같은 아이템에 접근해야 하는 클라이언트에서는 원형 큐를 이용해 아이템을 처리할 수 있다.

 

> LPUSH clist A
(integer) 1

> LPUSH clist B
(integer) 2

> LPUSH clist C
(integer) 3

> LRANGE clist 0 -1
1) "C"
2) "B"
3) "A"

> RPOPLPUSH clist clist
"A"

> LRANGE clist 0 -1
1) "A"
2) "C"
3) "B"

list에서 RPOPLPUSH 커맨드를 사용해서 간편하게 원형 큐를 사용할 수 있다.

 

 

Stream

레디스의 Stream과 아파치 카프카

Stream은 레디스 5.0에서 새로 추가된 자료 구조로 대용량, 대규모의 메시징 데이터를 빠르게 처리할 수 있도록 설계됐다.

 

stream 사용 목적:

  1. 대량의 데이터를 효율적으로 처리하는 플랫폼으로 활용할 수 있다.
  2. 여러 생산자가 생성한 데이터를 다양한 소비자가 처리할 수 있게 지원하는 데이터 저장소 및 중간 큐잉 시스템으로 사용할 수 있다.

 

스트림이란?

 연속적인 데이터의 흐름, 일정한 데이터 조각의 연속을 의미한다.

바이트 스트림

 

10GB의 텍스트 파일을 처리하는 애플리케이션에서 바이트 스트림을 처리하는 과정이다. 파일 하나는 유한하지만 이를 읽어올 때 애플리케이션은 단어 단위, 또는 줄 단위로 데이터를 잘게 쪼개서 처리하기 때문에 프로그램은 바이트 스트림을 처리하는 것으로 볼 수 있다.

 

채팅 프로그램에서의 JSON 파일 스트림

 

채팅 프로그램에서 JSON 파일을 스트리밍하는 과정이다. 채팅 앱에서 사용자는 아무때나 채팅을 보낼 수 있으며, 메신저 서버는 사람들이 계속 채팅하는 동안 끝없이 데이터를 처리할 수 있어야 한다.

 

서비스 내부에서의 데이터 스트림

 

외부에서 인풋을 받아오는 스트리밍 처리 방식에서도 애플리케이션 내부에서 서버 간 데이터의 이동이 필요할 수 있다.

 

bum0w0
@bum0w0 :: bum0w0 님의 블로그

bum0w0 님의 블로그 입니다.

목차