메시징과 메시징 브로커

고동현·2024년 9월 13일
0

Redis

목록 보기
2/5

Redis를 사용하거나 카프카를 사용하면 메시지와 메시지 브로커에 대한 내용을 볼 수 있다.
처음에 필자는 메시지가 무엇을 의미하는지 메시지 브로커를 왜 써야하는지 전혀 알지 못했다.

메시지? 문자메시지를 보낸다는건가? 근데 이걸 왜써? 이런 궁금증들이 생겼다.

우선 용어 정리부터 하고 넘어가자.

  • MOM(Message Oriented Middleware): 메시지 미들웨어의 이론,개념,설계도를 의미
  • Message Queue: 단순히 자료구조로서는 메시지를 큐에다가 담는 구조를 의미한다. 하지만 더 나아가 애플리케이션간의 메시지 큐를 통한 단순한 형태의 통신 솔루션으로 보면 되겠다.
  • Message Broker: 메시지 큐에서 더 확장된 기능, 더 광범위한 전송, 메시지 내용을 위한 라우팅 및 추가, 고급기능을 지원하고 구현체 별로 어떻게 메시지를 전달하는지, 내부 구조를 어떻게 구현했는지에 따라 방식,목적,성능이 달라진다.

참고) 라우팅: 데이터의 IP 패킷이 원본에서 대상으로 이동하는 방식

여기까지 보면 메시지 큐와 메시지 브로커의 의미 차이가 모호하다. 실제로 융합해서 보는 사람도 많다.
둘의 차이에 집중하기 보다는 메시지 브로커, 메시지 큐의 역할에 집중하는것이 좋겠다.

MOM

메시지 지향 미들웨어의 역할은 무엇일까? 이름에서 알 수 있듯이 애플리케이션의 메시지를 중간에서 관리해주는 시스템이다.

해당 그림처럼 송신자와 수신자 사이에서 메시지를 관리해주는 시스템이다.

그런데 애초에 이런생각이 든다.
중간(미들웨어)에 위치하면서 메시지를 전달받기 위해서 큐를 구성하고, 해당 큐에서 메시지를 전송하는 하나의 서버구나!

근데 여기서 궁금증이 수신자, 송신자가 있으면 송신자가 수신자한테 바로 보내면 되는데, 왜 중간에 미들웨어를 위치시키느냐 이것이다.

그러면 아? 혹시 송신자가 메시지를 너무 많이 보내면 수신자가 터질 수 있어서 그런건가?
그런데 이건 메시지 브로커를 둬도 마찬가지 아닌가?
그럼 메시지 브로커를 고성능 서버를 사용하면 안터지나?
그럴꺼면 애초에 수신자를 고성능 서버로 사용하면 되는거 아닌가? 싶을 수 있다.

메시지 브로커 사용이유

메시지 브로커를 사용하는 이유는
1. 애플리케이션 간의 의존성 제거
2. 메시지 처리 시점
3. 다양하고 유연한 통신

서비스간의 의존성 제거
예를 들어 봅시다. 트위터에서 계시글을 올리면, 해당 팔로워한테 "My new Post is updated. come and see"라는 피드를 날린다고 치자.

이러면 글을 올린 사람은 Sender, 내 팔로워들이 Receiver가 될것이다.

1번 경우처럼 ClientA가 ClientB 한명만 팔로우가 되어있다면, CLientA는 ClientB의 수신측 주소만 입력해주면 뭐 운영이 될 것이다.
그러나, 2번 경우처럼 ClientA가 ClientB 뿐만 아니라 여러명을 알아야 한다면, 수신측 모든 주소값들을 알고, 해당 메시지로 보내야한다.

그러나 이런 경우는 너무 수고스러운 일이고, 사람이 할 수 있는일이 아니다.

다시 돌아가서,

동기방식으로 메시지를 전송했다고 치자.
송신측이 메시지를 보니면 Response를 받을 때까지 대기를 해야한다.
만약 여기서 수신측 서버가 뻑이 나버리면, 정해둔 timeout시간 까지 기다려야만 한다.
만약 여기서 송신측에서 계속 request가 들어오면 요청들이 쌓이고 송신측 서버마저 죽어버릴 수 있다.

비동기 방식으로 메시지를 전송하더라도 메시지가 수신측에 정상적으로 전달되지 않아, 도중에 메시지가 유실될 우려가 있다.
즉, 메시지 전달이 보장되지 않는다.

고로, 메시지 브로커 없이는 송신 및 수신 서버가 모두 동작해야만 메시지 전송이 동작하게 된다.

또한 메시지는 전달이 보장되어야하는데, 메시지 브로커가 없는 상황에서는 송신 또는 수신측에 직접 보장을 해주는 매커니즘을 구현해야하는데, 이는 매우 어렵다.

이런 문제들이 서비스간 의존하기 때문에 발생하고 있으므로, 의존성을 메시지 브로커를 통해서 끊어준다.


이제는 sender는 메시지 브로커만을 바라보고, reciver는 메시지 브로커에서 메시지를 꺼내간다.

이렇게 하면
서버수가 유동적이어도 정상적으로 작동된다.
송신측 서버가 늘어나더라도 메시지 브로커 주소만 알면 메시지를 보내는데 문제가 없다.
또한, 수신측 서버가 늘어나도 메시지 브로커의 주소만 알고 있으면 메시지를 받아오는데 문제가 없다.

수신측 서버가 문제가 생겨도 정상작동한다.
송신측 서버는 수신측 서버가 제대로 받았는지 안받았는지 확인할 필요가없다. 왜냐하면 수신측에게 메시지를 전달하는 기능은 메시지 브로커에게 위임했기 때문이다.
이제, 송신측은 메시지를 메시지 브로커에게만 전달하면된다.
수신측 서버도, 고장났다가 정상적으로 돌아오면, 메시지 브로커에 유지되어있는 메시지를 받아오면 된다.

메시지 브로커가 죽어버리면?
당연히 메시지 전송 기능에는 문제가 발생한다.
그러나 송신,수신측 서버에는 부담이 가지 않는다. 그냥 메시지 전송 출구만 없어진것 뿐이다.

또한, 메시지 브로커는 고가용성을 목표로 만들어져 있어 서버가 이상이 있을 위험이 적다고 한다.
메시지 브로커들은 보통 전달 메커니즘이 내부에 구현되어있다.

수신자는 메시지를 메시지 버퍼링을 이용할 수 있다.
만약 송신측이 보낸 메시지가 잠깐 메시지 브로커에 저장되었다가 바로 수신측에 줘버리면 어떻게 될까? 당연히 이러면 메시지 브로커를 사용할 이유가 없다.

메시지 브로커는 수신측이 원할때 메시지를 가져갈 수 있도록 지원하고있다.

이게 좋은점이 뭐냐면, client A가 메시지를 연속으로 2개를 날린다 치자.
만약 메시지 브로커를 사용하지 않느다면, 메시지1,메시지2를 ClientB한테 주고,
그다음 메시지1,2를 ClientC한테 주고, 메시지 1,2를 Client D한테 주고...
이런식으로 반복해야한다.

그러나, 메시지 브로커를 사용하면
ClientA가 메시지를 그만 보낼때까지 메시지1,2를 큐에다가 저장해뒀다가,
바로 수신측에 보내는게 아니라 여러 Consumer가 동일한 큐를 소비할 수 있게한다.
메시지를 병렬로 처리할 수 있어, CLinetB,C,D가 동시에 동일한 큐에서 메시지를 꺼내가서 시스템의 부하를 분산시키고 처리 속도를 높일 수 있다.

어? 그러면 여기서 이상함을 느껴야한다.

queue라는 자료구조는 한번 pop하면 더이상 해당 데이터가 queue에 남아있지 않은데 어떻게 ClientB가 해당 메시지를 가져가면 ClientC,D,E,,,가 메시지 1,2를 가질 수 있는거지?

라고 생각하면 굉장히 대단하다고 볼 수 있다..

메시지큐

메시지 큐에서는 해당 그림과 같이 m1을 ConsumerA가 가져가버리면 ConsumerB는 m1에 접근하지 못한다.

고로 쇼핑몰 주문처럼 하나의 주문정보가 들어오면 메시지 큐에 있다가 메시지 큐에서 순차적으로 꺼내서 배송 처리를 해주던가,

피자 이벤트를 해서 고객 400명이 피자를 주문시켰다 하면
uid 1 pepparoni, uid 2 cheeze pizza 이렇게 메시지 큐에 넣어놓고 해당 큐에서 하나하나 순차적으로 꺼내서 주문처리를 해주면 된다.

kafka,RabbitMQ등에서 제공한다.

Pub/Sub

메시지 큐와 반대로, pub-sub 아키텍쳐에서는 구독자가 토픽을 구독하고, 토픽에 메시지가 발행되었을때 메시지가 구독자에게 전달되는 방식이다.

특징으로 발행자가 메시지를 게시하면, 그 메시지는 토픽 또는 채널을 구독하는 모든 구독자에게 전달된다.

한 메시지가 여러 구독자에게 동시에 전달 될 수 있다. 고로 구독자 각각은 메시지의 독립적인 복사본을 받는다.

이는 새로운 프로모션, 할인 이벤트등에 대한 정보를 발행하여, 이를 구독한 사용자에게 실시간으로 정보를 제공한다.

Kafka,RabbitMQ,Redis등에서 제공한다.

어? 그러면 메시지 큐는 Redis에서 제공하지 않는건가?
그건 아니다. 메시지 큐 역할 을 할 수 있는 데이터 타입을 제공하는데,

Redis의 List를 사용하면 메시지 큐와 유사하게 작동한다.

List는 LPUSH,RPOP 명령을 통해 큐의 앞뒤에서 요소를 추가하고 제거하는 방식으로 메시지 큐기능을 구현할 수 있다.

또한 BLPOP과 BRPOP명령을 통해 리스트가 비어있을때는 대기(blocking)할 수 있어, 메시지가 도착할 때까지 기다리는 기능도 제공한다.

또한 Redis의 Stream 기능도 메시지 큐의 역할을 한다.
Stream은 큐와같이 메시지를 순차적으로 저장하고, 소비자 그룹을 통해 메시지를 처리 할 수 있다.

Redis 의 경우 pub 메시지들을 저장 하지 않는다, 그런이유로 Subscriber 가 존재하지 않으면,
메시지가 사라지게 된다.

즉 Redis pub/sub 은 메시지를 저장, 수신확인 이 필요하지 않은 경우에 사용하면 좋은 방법이다.

소비자 그룹?
Redis Streams에서 여러 소비자가 하나의 스트림을 공동으로 처리할 수 있게 해주는 구조이다.

  1. 메시지 분배: 스트림에 추가된 메시지는 소비자 그룹 내의 각 소비자에게 분배된다.
    각 메시지는 소비자 그룹에 있는 특정 소비자에게만 전달되며, 동일한 메시지가 여러 소비자에게 중복되지 않도록 관리된다.

  2. 메시지 확인: 소비자가 메시지를 처리한 후 이를 Redis에 Ack하면, 해당 메시지를 처리환료로 기록한다. 만약 처리를 하지 못하거나 Ack를 안보내면 메시지는 다른 소비자에게 다시 전달될 수 있다.

  3. 재전송 기능: 소비자가 메시지를 확인하지 않으면, 해당 메시지를 재전송하여 다른 소비자가 처리하게 한다. 이를 통해 메시지를 처리하는것을 보장한다.

소비자 그룹을 통해서 부하를 분산시킬 수 있는데 하나의 소비자만 모든 메시지를 처리하면 부하가 집중되고, 성능저하가 발생한다. 소비자 그룹을 통해 부하를 여러 소비자에게 나눠 처리하므로서 시스템의 안전성을 높일 수 있다.

예를 들어, 웹 애플리케이션에서 로그데이터가 실시간으로 쌓이면, 한 소비자만 로그테이터를 수집하여 처리하는 역할을 담당하면, 너무 빡세다. 그래서 로그데이터를 처리하는 소비자 그룹을 만들어서 처리하면, 로그를 중복처리하지 않게 하나의 로그메시지가 하나의 소비자에게 전달됨이 보장되고, 이를 통해 부하를 분산시킬 수 있게 된다.

결국 정리를 해보자면,

  1. 메시지 브로커를 사용하는이유? 앞에서 설명했다.
  2. 메시지를 전달하는 방법, 메시지큐 pub/sub가 있다.
    각각의 방식이 쓰이는 곳이 다르므로, 원하는 목적에 따라서 설정하면된다.

메시지 큐와 PUB/SUB로 나뉠수 있다.
메시지 큐는 메시지가 들어오면 하나의 컨슈머에게 전달된다. ex). 주문처리시스템
PUB/SUB는 토픽을 구독한 모든 컨슈머에게 메시지가 전달된다. ex). 푸시알림, 실시간 메시지 발송

메시지 큐로는 Redis에서 List와 Stream이 쓰일수 있는데,
LIst는 단순한 메시지 큐에 적합하고, Stream은 복잡한 메시지 처리와 영구저장이 필요할때 적합하다.

참고
https://binux.tistory.com/74
https://velog.io/@itonse/%EB%A9%94%EC%8B%9C%EC%A7%80-%ED%81%90-vs-pubsub%EB%B0%9C%ED%96%89%EA%B5%AC%EB%8F%85
https://www.youtube.com/watch?v=Z8qcpXyMAiA

profile
항상 Why?[왜썻는지] What?[이를 통해 무엇을 얻었는지 생각하겠습니다.]

0개의 댓글