일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- cookie
- Python
- class
- typeORM
- Nest.js
- 공룡게임
- 자료구조
- AWS
- JavaScript
- nestjs
- Queue
- 게임
- react
- 정렬
- OCR
- jest
- Express
- Sequelize
- mongoose
- nodejs
- MongoDB
- game
- dfs
- flask
- TypeScript
- GIT
- Bull
- MySQL
- Dinosaur
- Today
- Total
포시코딩
4월5일 - RabbitMQ, Kafka 본문
개요
어제 브로커에 메시지 브로커와 이벤트 브로커가 있고
각각 RabbitMQ와 Kafka가 유명하다는걸 알아봤는데
각각에 대해 더 자세히 살펴보자.
RabbitMQ
AMQP를 구현한 메시지 브로커.
AMQP란?
Advanced Message Queueing Protocol의 줄임말로 MQ의 오픈소스에 기반한 표준 프로토콜을 의미.
가끔 AMQP 프로토콜이라 부르는 곳도 있는데 AMQP의 P가 프로토콜 이므로
프로토콜 프로토콜이 되어 맞지 않다.
AMQP를 구성하는 요소로는 Exchange, Queue, Binding이 있다.
Exchange
- 생산자로부터 수신한 메시지를 큐나 다른 exchange로 분배하는 라우터 역할
- 수신한 메시지를 분배하기 위해 Exchange Type이라는 라우팅 알고리즘을 사용한다.
- 브로커는 여러 개의 Exchange Type 인스턴스를 가질 수 있다.
- 메시지 브로커에서는 큐에 메시지를 전달하는 역할을 한다.
Queue
- 메모리나 디스크에 메시지를 저장하고, 그것을 소비자에게 전달하는 역할을 한다.
Binding
- Exchange와 Queue의 관계를 정의한 일종의 라우팅 테이블
- 동일한 Queue가 여러 개의 Exchange에 Binding 될 수도 있고
단일 Exchange에 여러 개의 큐가 Binding 될 수도 있다. - Exchange에 전달된 메시지가 어떤 큐에 저장되어야 하는지를 정의한다.
Exchange Type을 Binding과 혼동할 수 있는데
Exchange Type은 받은 메시지를 어떤 방법으로 라우팅 할지 결정하는 것이고
Binding은 이러한 방법으로 결정된 메시지를 어느 Queue에 전달할지를 결정하는 라우팅 테이블이라는 것을 기억하자.
예를 들어, 주소 정보를 받는 브로커가 존재하면 해당 주소 정보의 시, 도를 보고
Queue를 결정하는 방식을 Exchange Type이라고 하고
서울은 1번 Queue, 인천은 2번 Queue 등과 같이 큐를 결정하는 것이 Binding이다.
메시지 브로커에 대해 그림으로 표현하면 다음과 같다.
특징
- Queue가 소비자에게 메시지를 전달한 후 ACK(수신자가 송신자에게 받은 데이터를 확인하였음을 알리는 신호)
를 받았을 때, 해당 메시지를 dequeue 한다. - 소비자가 ACK를 Queue에 전달하지 못하는 경우는
받은 메시지가 너무 커 아직 처리 중이거나 소비자 서버가 죽었을 때로 생각할 수 있다. - RabbitMQ에서는 ACK를 받지 못한 메시지의 경우, 대기를 하고 있다가
전달한 소비자 서버의 상태를 확인한 후 Disconnected와 같은 신호를 받았을 경우
해당 소비자를 제외한 다른 소비자에게 동일한 메시지를 전달한다. - 만약 메시지를 Queue에 넣은 다음 소비자에게 전달하기 전에 RabbitMQ 서버가 죽는다면
Queue는 메모리에 데이터를 쓰기 때문에 모든 데이터가 소멸한다. - 위의 문제를 해결하기 위해 'Message Durability'라는 영속성이라는 개념을 가지고 있는데,
메시지가 Queue에 저장될 때, disk의 파일에도 동시에 저장하는 방법이다.
해당 방법을 사용하면 서버가 죽었을 때 Queue의 데이터를 어느 정도 복구할 수 있지만
메시지가 disk의 파일에 쓰는 도중 서버가 죽을 경우 일부 데이터의 소실이 발생할 수 있다. - Message Queue가 도착하기 전에 라우팅 되며 플러그인을 통해 더 복잡한 라우팅이 가능하다. (유연한 라우팅)
- 로컬 네트워크에 있는 여러 RabbitMQ 서버를 논리적으로 클러스터링 할 수 있고 논리적인 브로커도 가능하다.
(이 부분은 말로만 봤을 때 잘 이해가 가지 않는데 직접 써봐야 알 듯..) - 거의 모든 언어와 운영체제 지원
- 오픈소스이며 상업적 지원
- Queue에 저장되어 있던 메시지에 대해 Event Consumer가 가져가게 되면 queue에서 해당 메시지를 삭제한다.
- Manage UI를 제공하기 때문에 관리적인 측면이나 다양한 기능 구현을 위한 서비스를 구축할 때 장점이 부각된다.
Kafka
Kafka는 LinkedIn에서 개발된 메시지 큐 방식 기반 분산 메시징 시스템이다.
Kafka는 pub/sub 모델 기반으로 크게 보면 publisher(=producer), subscriber(=consumer), kafka cluster로 구성된다.
특징
- 분산 시스템을 기본으로 설계되었기에 기존 메시징 시스템에 비해 분산 및 복제 구성을 손쉽게 할 수 있다.
- 메시지를 메모리에 저장하는 기존 메시징 시스템과 달리 메시지를 파일 시스템에 저장한다.
- 위의 이유로 별도의 설정을 하지 않아도 데이터의 영속성(durability)이 보장된다.
(메시지 브로커에서 Message Durability를 고려해야 하는 것과 대조) - 기존 메시징 시스템에선 처리되지 않고 남아있는 메시지의 수가 많을수록 시스템의 성능이 크게 감소했으나
Kafka에서는 메시지를 파일 시스템에 저장하기 때문에 메시지를 많이 쌓아두어도 성능이 크게 감소하지 않는다.
또한, 많은 메시지를 쌓아둘 수 있기 때문에 실시간 처리뿐만 아니라
주기적인 batch 작업에 사용할 데이터를 쌓아두는 용도로도 사용할 수 있다. - Consumer에 의해 처리된 메시지(acknowledged message)를 곧바로 삭제하는 기존 메시징 시스템과 달리
처리된 메시지를 삭제하지 않고 파일 시스템에 그대로 두었다가 설정된 수명이 지나면 삭제한다. - 위의 이유로 메시지 처리 도중 문제가 발생했거나 처리 로직이 변경되었을 경우
consumer가 메시지를 처음부터 다시 처리(rewind)하도록 할 수 있다. - 기존 메시징 시스템은 broker가 consumer에게 메시지를 push 해주는 방식인데 반해,
Kafka는 consumer가 broker로부터 직접 메시지를 가지고 가는 pull 방식으로 동작한다.
따라서, consumer는 자신의 처리능력만큼의 메시지만 broker로부터 가져오기 때문에 최적의 성능을 낼 수 있다. - 기존의 push 방식의 메시징 시스템은 broker가 직접 각 consumer가 어떤 메시지를 처리해야 하는지 계산하고
어떤 메시지를 처리 중인지 트랙킹 하였는데, Kafka에선 consumer가 직접 필요한 메시지를 broker로부터 pull 하므로
broker의 consumer와 메시지 관리에 대한 부담이 경감되었다. - 메시지를 pull 방식으로 가져오므로 메시지를 쌓아두었다가 주기적으로 처리하는 batch consumer의 구현이 가능하다.
- 생성자로부터 메시지가 들어오면 해당 메시지를 topic으로 분류하고 이를 event streamer에 저장한다.
그 후, 수신인이 특정 topic에 대한 메시지를 가져가더라도 event streamer는 해당 topic을 계속 유지하기 때문에
특정 상황이 발생하더라도 재생이 가능하다. - 클러스터를 통한 병렬 처리가 주요 차별점인 만큼 방대한 양의 데이터를 처리할 때 장점이 부각된다.
정리
자, 위의 내용만으로 봤을 때
그럼 어떤 상황에서든 이벤트 브로커를 쓰는게 유리한거 아님? 이란 생각이 들 수 있다. 나도 그랬고
어떤 상황에서 뭘 쓰는게 좋은지 정리하면 다음과 같다.
메시지 브로커를 고려해야 하는 경우
메시지 처리 순서가 중요한 경우
이벤트 브로커는 이벤트를 동시에 처리하기 때문에, 처리 순서가 중요한 경우에는 메시지 브로커를 사용하는 것이 적합하다.
메시지 브로커는 FIFO 처리 방식을 사용하기 때문에 메시지 처리 순서가 중요한 경우 유용하다.
하지만 네트워크 병목 현상이 발생할 수 있다는 단점이 있다.
메시지 특성이 다른 경우
이벤트 브로커는 특정 유형의 이벤트를 처리하기 위해 최적화 되어 있다.
하지만 메시지 브로커는 다양한 유형의 메시지를 처리할 수 있다.
메시지 특성이 다양하거나 불규칙한 경우 메시지 브로커를 사용하는 것이 적합하다.
이벤트 브로커를 고려해야 하는 경우
반면 이벤트 브로커는 이벤트 처리에 최적화되어 있으며,
이벤트에 대한 실시간 처리가 필요한 경우에는 이벤트 브로커를 사용하는 것이 적합하다.
또한 채용 공고에 볼 때 우대사항에서 kafka를 자주 봤던 기억으로 봐선
그만큼 쓰는 데에는 이유가 있지 않을까 싶다.
그래서 뭘 쓸건대?
내가 참고한 포스팅 작성자는
분산, 대용량, 고성능, 노드 장애 대응에 대한 목적이 필요하면 Kafka를 사용하겠지만
사용하려는 프로젝트에 너무 오버스펙인 데다 큐를 잘 관리할 수 있고 Manage UI가 있는 메시지 브로커인 RabbitMQ를 사용..
하는 것으로 포스팅이 마무리되나 싶었지만
비용적인 측면에서 제한이 있는 RabbitMQ에 반해
AWS ElastiCache는 프리 티어인 t2.micro의 케시 노드를 제한 없이 이용할 수 있다는 점에서
Redis를 통한 Queue를 구축하는 것으로 방향을 전환했다.
결론적으로 작성자는 많은 고민 끝에
Redis 기반이며, Node.js에서 Queue를 할 수 있는 Bull을 쓰는 것으로 결정하였는데 반해
메시지 큐의 필요성을 깨닫고 Nest.js 환경에서 공식문서의 추천으로 Bull을 바로 접해
RabbitMQ, Kafka의 존재도 모르고 사용했던 내 프로젝트는 참 운이 좋았다고 할 수 있겠다.
이제라도 알아서 다행이려나..
마치며
이렇게 이틀에 걸쳐 메시지 큐, 브로커, RabbitMQ와 Kafka에 대해 알아봤다.
처음엔 Bull에 대해 공부하며 비슷한 건 뭐가 있고 어떤 경우에 쓰임새가 다를까에서 가볍게 시작한 서칭이었는데
너무 좋은 블로그 글을 발견해
내용을 그대로 가져오는걸 극도로 싫어함에도 불구하고 줄줄 따라 적으며 정리했던 것 같다.
아직 끝나지 않았다!
이어서 사용 방법에 대해서만 급하게 공부했던 Bull에 대해 좀 더 자세히 알아보고자 한다.
물론 RabbitMQ, Kafka를 공부할 때 참고한 포스팅의 내용을 계속 참고할 듯한데
여기에 모든 내용이 다 정리되어 있어 어쩔 수 없다.
아무튼 내가 Bull을 쓰며 했던 고민, 사용했던 방법, 개발 환경 등 판박이인 게 많아서
당분간의 TIL은 신세를 질 생각이다.
참고한 곳
'TIL' 카테고리의 다른 글
트랜잭션(Transaction)의 원자성(Atomicity), 일관성(Consistency), 고립성(Isolation), 지속성(Durability) (0) | 2023.04.06 |
---|---|
4월6일 - Nest.js의 Request lifecycle (0) | 2023.04.06 |
4월4일 - 메시지 큐와 브로커 (0) | 2023.04.05 |
4월1일 - [React] useState, useRef, redux의 state 비교 (0) | 2023.04.02 |
3월31일 - FCM, Firebase (0) | 2023.04.01 |