[Redis]레디스를 메시지 브로커로 사용하기

Jeongyeon Kim·2024년 2월 29일
0

Redis

목록 보기
5/7
post-thumbnail
  • 모듈이 서로 느슨하고 적절하게 연결시킨 구조 선호 ➡️ 메시지 브로커 필요
  • 모듈 간의 통신에서는 되도록 비동기 통신(async) 사용 권장
  • 메시지 브로커의 종류
    • 메시징 큐
    • 이벤트 스트림

1. 메시징 큐와 이벤트 스트림


메시징 큐

  • 생산자(producer): 데이터를 생성
  • 소비자(consumer): 데이터를 수신

이벤트 스트림

  • 발행자(publisher): 데이터 생성
  • 수신자(subscriber): 데이터 조회

메시징 큐 vs. 이벤트 스트림

  1. 방향성
    • 메시징 쿠의 생산자는 소비자의 큐로 데이터를 직접 푸시하기 때문에 2개의 서비스에 같은 메시지를 보낼 때 2번 푸시해야 함
    • 이벤트 스트림에서 발행자는 스트림의 특정 저장소에 하나의 메시지를 보낼 수 있고, 메시지를 읽어가고자 하는 수신자들은 스트림에서 같은 메시지를 풀(pull)해 갈 수 있기 때문에 메시지를 복제해서 저장하지 않아도 됨
  2. 데이터의 영속성
    • 메시징 큐에서는 소비자가 데이터를 읽어갈 때 큐에서 데이터 삭제
    • 이벤트 스트림에서 구독자가 읽어간 데이터는 바로 삭제되지 않고, 저장소의 설정에 따라 특정 기간 동안 저장됨

메시징 큐는 일대일 상황에서 한 서비스가 다른 서비스에게 동작을 지시할 때 유용
스트림은 다대다 상황에서 유리함

레디스를 메시지 브로커로 사용하기

  • 레디스의 pub/sub 기능을 이용해 메시지 브로커 구현
  • 레디스 pub/sub
    • 모든 데이터는 한 번 채널 전체에 전파된 뒤 삭제(일회성)
    • 메시지가 잘 전달됐는지 등의 정보 보장 X(fire-and-forget 패턴에 사용됨)
    • c.f) fire-and-forget 패턴: 비동기 프로그래밍에서 사용되는 디자인 패턴으로, 어떤 작업을 실행하고 그 결과에 대한 응답을 기다리지 않고 바로 다음 코드를 실행하는 것
  • 레디스의 list와 stream을 이용해 각각 메시징 큐와 이벤트 스트림으로 사용하기 알맞음

2. 레디스의 pub/sub


  • 레디스의 pub/sub은 매우 가볍기 때문에 최소한의 메시지 전달 기능만 제공
    • 발행자는 메시지를 채널로 보낼 수 있을 뿐, 어떤 구독자가 메시지를 읽어가는지, 정상적으로 모든 구독자에게 메시지가 전달됐는지 확인할 수 없음
    • 구독자는 메시지를 받을 수 있지만 해당 메시지가 언제 어떤 발행자에 의해 생성되었는지 등 메타데이터는 알 수 없음
    • 한 번 전파된 데이터는 레디스에 저장 X ➡️ 정합성이 중요한 데이터를 전달하기에는 적합하지 않음
명령어설명
PUBLISH데이터 전파(발행자)
SUBSCRIBE특정 채널 구독(구독자)

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

  • 클러스터: 레디스가 자체적으로 제공하는 데이터 분산 형태의 구조
  • 메시지를 발행하면 해당 메시지는 클러스터에 속한 모든 노드에 자동으로 전달
  • 클러스터는 주로 대규모 서비스에서 데이터를 분산해서 저장하고 처리하기 위해 도입
  • 레디스 클러스터 내에서 pub/sub을 사용할 때 메시지가 모든 레디스 노드에 복제되는 방식은 클러스터 환경의 핵심 목표와는 부합하지 않음
    ➡️ 불필요한 리소스 사용, 네트워크 부하

sharded pub/sub

  • 각 채널은 슬롯에 매핑
  • 클러스터에서 키가 슬롯에 할당되는 것과 동일한 방식으로 채널 할당, 같은 슬롯을 가지고 있는 노드 간에만 pub/sub 메시지 전파
  • 클러스터 구조에서 pub/sub 되는 메시지는 모든 노드로 전파되지 않기 때문에 불필요한 복제를 줄여 자원 절약 가능

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


list의 EX 기능

명령어설명
RPUSHX데이터를 저장하고자 하는 list가 이미 존재할 때에만 아이템 추가
  • SNS 타임라인의 경우 이미 캐시된(이미 키가 존재하는) 타임라인에만 데이터를 추가할 수 있음(자주 사용하지 않는 사람의 타임라인 캐시 데이터를 관리할 필요가 없음)
  • 사용자의 캐시가 이미 존재하는지 유무를 애플리케이션에서 확인하는 불필요한 확인 과정이 없어 성능 향상 가능

list의 블로킹 기능

  • 이벤트 기반(event-driven) 구조: 이벤트 루프를 돌며 신규로 처리할 이벤트가 있는지 체크, 새로운 이벤트가 없을 경우 정해진 시간(polling interval) 동안 대기한 뒤 다시 이벤트 큐에 데이터가 있는지 확인하는 과정 반복(polling)
  • 단점: 폴링 프로세스가 진행되는 동안 애플리케이션과 큐의 리소스가 불필요하게 소모, 폴링 인터벌 동안 대기한 뒤 다시 확인하는 과정을 거치기 때문에 이벤트를 즉시 처리할 수 없음
  • BRPOP, BLPOP: list에 데이터가 있으면 즉시 반환, 만약 데이터가 없을 경우 기다려서 들어온 값을 반환 or 클라이언트가 설정한 타임아웃 시간 만큼 대기한 후 nil 반환

list를 이용한 원형 큐

  • 특정 아이템을 반복 접근해야 하는 클라이언트, 혹은 여러 개의 클라이언트가 병렬적으로 같은 아이템에 접근 해야 하는 클라이언트의 경우 원형 큐(circular queue)를 이용해 아이템 처리
  • RPOPPUSH

4. Stream


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

  • Stream: 대용량, 대규모의 메시징 데이터를 빠르게 처리할 수 있도록 설계됨, 데이터를 계속해서 추가하는 방식으로 저장되는(append-only) 자료 구조
  • stream 활용
    • 백엔드 개발자들은 대량의 데이터를 효율적으로 처리하는 플랫폼으로 활용
    • 데이터 엔지니어들은 여러 생산자가 생성한 데이터를 다양한 소비자가 처리할 수 있게 지원하는 데이터 저장소 및 중간 큐잉 시스템으로 활용

스트림이란?

  • 연속적인 데이터의 흐름, 일정한 데이터 조각의 연속

데이터의 저장

메시지의 저장과 식별

  • 카프카
    • 토픽: 각각의 분리된 스트림, 같은 데이터를 관리하는 하나의 그룹
    • 각 메시지는 0부터 시작해 증가하는 시퀀스 넘버로 식별
    • 시퀀스 넘저는 토픽 내의 파티션 안에서만 유니크하게 증가하기 때문에 토픽이 1개 이상의 파티션을 갖는다면 메시지는 하나의 토픽 내에서 유니크하게 식별되지 않음
  • 레디스 stream
    • 각 메시지는 시간과 관련된 유니크한 ID를 가지며, 이 값은 중복되지 않음

스트림 생성과 데이터 입력

  • 카프카
    • 각 스트림은 토픽으로 관리됨
    • 생성자는 데이터를 토픽에 푸시, 소비자는 토픽에서 데이터 읽음
    • 토픽 생성 후 프로듀서를 이용해 메시지 보냄
  • 레디스 stream
    • 따로 stream을 생성하는 과정 필요 X
    • XADD 커맨드 이용
    • 데이터는 hash 자료 구조처럼 필드-값 쌍으로 저장되므로 각 메시지마다 유동적인 데이터 저장 가능

데이터의 조회

  • 카프카
    • 소비자는 특정 토픽을 실시간으로 리스닝하며, 새롭게 토픽에 저장되는 메시지를 받을 수 있음
  • 레디스 stream
    • 실시간으로 처리되는 데이터 리스닝(XREAD)
    • ID를 이용해 필요한 데이터 검색(XRANGE, XREVRANGE)

소비자와 소비자 그룹

  • 팬아웃(fan-out): 같은 데이터를 여러 소비자에게 전달하는 것
  • 같은 데이터를 여러 소비자가 나눠서 가져가기 위해서는?
    • 같은 역할을 하는 여러 개의 소비자를 이용해 메시지를 병렬 처리함으로써 서비스의 처리 성능을 높일 수 있음
  • 레디스 stream
    • 데이터가 저장될 때마다 고유한 ID(시간)를 부여받아 순서대로 저장됨
    • 소비자에게 데이터가 전달될 때 순서 항상 보장(시간순)
  • 카프카
    • 유니크 키는 파티션 내에서만 보장되기 때문에 소비자가 여러 파티션에서 토픽을 읽어갈 때에는 데이터의 순서를 보장할 수 없음
    • 데이터의 정렬이 보장되지 않기 때문에 메시지 순서 보장을 위해 소비자 그룹 사용

소비자 그룹

  • 카프카
    • 소비자 그룹에 여러 소비자 추가 가능
    • 소비자는 토픽 내의 파티션과 일대일로 연결됨
    • 파티션을 이용해 소비자의 부하 분산 관리
  • 레디스 stream
    • 레디스 stream은 메시지가 전달되는 순서가 보장되기 때문에 카프카의 소비자 그룹과는 약간 다름
    • 소비자 그룹 내의 한 소비자는 다른 소비자가 아직 읽지 않은 데이터만을 읽어감
    • XGROUP: 소비자 그룹 생성
    • XREADGROUP: 소비자 그룹 이용해 데이터 읽음, 마스터에서만 호출 가능
    • stream의 상태를 나타내는 개념으로 간주
    • stream과 소비자 그룹은 독립적으로 동작 가능
    • 하나의 소비자 그룹에서 여러 개의 stream 리스닝 가능
    • 파티션이라는 분할 없이도 소비자 그룹이라는 개념을 이용해 여러 소비자에게 데이터 분산 가능

    ACK와 보류 리스트

  • 레디스 stream
    • 각 소비자별로 읽어간 메시지에 대한 리스트를 새로 생성하며, 마지막으로 읽어간 데이터의 ID로 last_delivered_id 값 업데이트(중복 전달 방지)
    • 보류 리스트를 이용해 소비자가 처리한 데이터 파악
    • 데이터가 처리됐다는 뜻의 ACK를 보내면 보류 리스트에서 해당 메시지 삭제
  • 카프카
    • 파티션별 오프셋 관리
    • __consumer_offsets: 소비자가 지정된 토픽의 특정 파티션의 메시지를 읽으면 소비자 그룹, 토픽, 파티션 내용이 통합되어 저장됨
    • 오프셋은 소비자가 다음으로 읽어야 할 위치(마지막으로 읽은 위치 X)

    레디스 stream의 메시지 보증 전략

  • at most once: 메시지를 최소 한 번 보내는 것, 메시지를 받자마자 실제 처리하기 전에 먼저 ACK 보냄
  • at least once: 받은 메시지를 모두 처리한 뒤 ACK, 실제로 메시지가 처리됐지만 ACK를 전송하기 전에 소비자가 종료되는 상황 발생 가능
  • exactly once: 모든 메시지가 무조건 한 번씩 전송되는 것 보장, 이미 처리된 메시지인지 아닌지를 확인하는 과정 필요

메시지의 재할당

  • XCLAIM: 메시지의 소유권을 다른 소비자에게 할당, 최소 대기 시간 지정
  • 메시지가 보류 상태로 머무른 시간이 최소 대기 시간을 초과한 경우에만 소유권을 변경할 수 있도록 해서 같은 메시지가 2개의 다른 소비자에게 중복으로 할당되는 것 방지

메시지의 자동 재할당

  • XAUTOCLAIM
    • 소비자가 직접 보류했던 메시지 중 하나를 자동으로 가져와서 처리
    • 할당 대기 중인 다음 메시지의 ID를 반환하는 방식으로 동작하기 때문에 반복적 호출 가능
    • 지정한 소비자 그룹에서 최소 대기 시간을 만족하는 보류 중인 메시지가 있다면 지정한 소비자에게 소유권을 재할당하는 방식으로 동작

메시지의 수동 재할당

  • stream 내 각 메시지는 counter 값 가짐
  • counter는 XREADGROUP을 이용해 소비자에게 할당하거나 XCLAIM을 이용해 재할당할 경우 1씩 증가
  • counter가 특정 값에 도달하면 이 메시지를 특수한 다른 stream으로 보내, 관리자가 추후에 처리할 수 있도록 함 ➡️ dead letter

stream 상태 확인

  • XINFO
    • XINFO consumer: 특정 소비자 그룹에 속한 소비자의 정보
    • XINFO GROUPS: stream에 속한 전체 소비자 그룹 list
    • XINFO STREAM: stream 자체의 정보
profile
Backend Developer👩🏻‍💻

0개의 댓글