잡학(雜學)

RabbitMQ

쪼랩전사 2021. 5. 14. 20:59
728x90

회사에서 RabbitMQ를 사용하는 일을 담당하게 되었다.

Go언어로 특정 토픽을 subscribe 하여 가져와 파싱 하는 것인데...

 

그런데 응?

Exchange? Bind? Durable? Routing key?

 

처음 보는 용어가 등장했다.

 

이 포스트는 이러한 용어가 무엇을 뜻하고, 왜 존재하는 것인지에 대한 것들을 정리하겠다.

RabbitMQ 개요

rabbit message queue, 직역하자면 "토끼 메시지 큐"이다.

토끼? 빠르다는 의미인 줄 알았는데.. 빠른 메시지 큐를 의미하는 줄 알았는데

그냥 Rabbit Technologies Ltd.라는 곳에서 만들기 시작해서 Rabbit이 들어간 거였다.[각주:1]

 

자 그러면 Message Queue 라는 것만 남는다.

다른 사이트에서 보면 장점 단점 이러한 것들을 정리하고 있는데, 그런 것들은 그냥 넘기겠다.[각주:2]

Message Queue

메시지를 위한 큐이다.

[각주:3]는 자료구조 중 하나로써 선입선출구조로 데이터를 저장한다.

이때, 데이터는 Message이므로, Message Queue(이하 MQ) 라고 불린다.

여기서 Message는 어떠한 데이터이다.

어떤 데이터가 담길지는 만드는 사람 마음이다.

 

그럼 그냥 큐랑 뭐가 달라?

아래에서 설명한다.

 

그럼 이 Message는 누가 보내고, 누가 받는가?

Producer(공급자)와 Consumer(소비자)이며, 아래 그림과 같다.

생산자-소비자 thanks to http://www.plantuml.com/plantuml/

1. 위와 같은 상황에서 producer가 계속 Message를 보내고, Consumer는 받지 않는다면 어떻게 되는가?

2. 반대로 Producer는 Message를 보내지 않고, Consumer에서는 계속 받을려고 한다면 어떻게 될 것인가?

3. 혹은, Producer와 Consumer가 계속, 아주 짧은 간격을 가지고 Queue에 접근한다면 어떻게 될 것인가?

이것에 대한 문제는 생산자-소비자 문제라 하여 이 사이트[각주:4]에 굉장히 자세히 설명되었다.

 

보통의 경우에는 Queue에서 데이터를 넣거나, 뺄려고 할때 Queue에 락을 걸어 데이터의 무결성을 꾀할려고 할 것이다.

하지만 구현 방법도 여러가지이고, 구현 방법에 따라 성능이 천차만별이다. 또한, 큐가 여러개로 늘어남에 따라 유지보수에 어려움이 생길 수 있다.

 

이러한 문제를 쉽게 해결해주는 것이 MQ이다.

MQ는 간단히 말하면 위에서 언급하는 3가지 문제를 생각하지 않고도 쉽게 사용할 수 있게 해주는 것 정도로 이해하면 된다.

다시 RabbitMQ

수만명의 사용자를 보유하고 있고, 가장 유명한 Open Source Message Broker 중 하나이다. 작은 startup 회사부터 큰 기업까지 사용된다.

장점

- 가볍다

- 사내에서 혹은 클라우드에서 배포가 쉽다

- 다수의 메시지 프로토콜을 지원한다

- 큰 규모, 고가용성을 위한 분산, 연합 설정된 상태로 배포가 가능하다

- 다수의 OS와 클라우드 환경에서 실행할 수 있다

- 다수의 언어를 위한 개발 툴이 존재한다

 

위의 내용은 RabbitMQ 공식 사이트를 참조[각주:5]한 것이다.

 

자 그러면 인제 Message Broker 는 뭘까?

Message Broker

Message Broker는 발신자의 공식(formal) Message Protocol에서 수신자의 공식(formal) Message Protocol로 Message를 변환하는 중개 컴퓨터 프로그램 모듈이다.

 

Message Broker의 목적은 다음과 같다.

- Message 검토

- Message 변환

- Message 전달

 

Wiki를 참조했다.

위에 프로토콜 어쩌구 모듈 어쩌구하는데 솔직히 크게 와닿지는 않는다.

 

Broker는 직역하면, 중개인(명사) 혹은 중개하다(동사)라는 뜻을 가진다.

중개인하면 부동산 정도를 생각하면 되는데

부동산 중개인이 집주인과 세입자 간을 연결해주는 역할을 한다.

이때, 집주인은 중개인에게 매물을 알려주고, 세입자도 중개인에게 매물이 뭐가 있는지를 확인한다.

중개인은 서로를 연결해주는데에 대한 보수를 받는 것뿐만 아니라

만약 세입자가 싹싹하게 다가오면 좋은 매물은 먼저 알려주기도 하고, 가끔 집주인이 등록한 매물을 잊어서 늦게 처리하기도 하는 것처럼 메시지 브로커도 프로그램 모듈로서 처리가 빠른 컴퓨터에게는 빨리 메시지를 던져주고 하는 등의 일들을 한다.

 

발행-구독 모델 thanks to http://www.plantuml.com/plantuml

Message Broker는 위와 같은 형태로 사용된다.

MQ는 Producer/Consumer를 가지는데 반해, Message Broker는 Publisher/Subscriber를 가지며

Message Broker는 중개를 해주는 것이기 때문에 다수의 Queue를 가질 수 있다.

Message Queue도 시점에 따라 다수의 Queue 가지는건 마찬가지이나, 너무 깊어지므로 패스

또한, Consumer와 Subscriber가 큐에 가지는 방향성이 반대가 된다.

 

Broker는 다수의 Queue를 가지기 때문에 이를 구분하기 위한 방법이 필요하다.

또한, Publisher가 하나의 QueueA에만 Message를 전달하는 것이 아니라 QueueB, QueueC에도 publish를 하는 경우

다수의 Publisher가 하나의 Queue에 Message를 전달하는 경우

다수의 Subscriber가 하나의 Queue에서 Message를 전달받는 경우 등등 많은 처리가 필요하다.

 

이러한 처리를 위해 RabbitMQ는 어떻게 했을까?

 

RabbitMQ 개념

지금부터 시작이다. 참고로 이렇게 포스팅하는게 처음이라 위에 쓰는데 일주일 걸렸다. 살려줘

 

RabbitMQ 구조 thanks to http://www.plantuml.com/plantuml

RabbitMQ는 발행-구독 모델과 비슷하나 exchange 라는 것과 exchange와 queue를 잇는 방법이 추가되었다.

또한 Publisher는 queue에 직접적으로 연결되는 것이 아니라 exchange를 거쳐 queue에 연결된다는 것에 주의깊게 보기 바란다.

 

이때, publisher와 subscriber는 특정한 queue에 어떻게 접속할까?

RabbitMQ에서는 특정 queue를 식별하기 위해서 Queue에 이름을 부여한다.

Exchange도 마찬가지로 이름을 통해 특정 exchange를 식별한다.

 

exchanger[각주:6]는 직역하면 교환해 주는 것, 교환해주는 사람을 뜻한다. 옛날 한국에서 1920~1970년대 즈음[각주:7]. 이러한 것 처럼 RabbitMQ의 Exchanger는 Publisher가 Exchange에 접속하면 어떠한 조건에 따라 Queue에 연결/교환의 역할을 수행한다.

 

RabbitMQ는 Exchange와 Queue를 연결하기 위해서 binding key라는 것을 부여한다. 그림(RabbitMQ 구조)에서 "R.svg", "G.jpg" 같은 것들이 binding key에 해당한다. Exchange와 Queue는 그림처럼 M:N의 관계를 가진다.

 

RabbitMQ에서 binding key를 매칭 방법을 통해 메시지를 전달하기 위한 방법은 4가지가 존재한다.

여기서 매칭 대상은 routing key를 말하며, routing key는 publisher가 publish를 할때 지정할 수 있다.

  • Direct 타입
    • routing key와 binding key가 정확히 일치해야 한다.
    • 정확히 하나의 큐에 메시지가 전달된다.
  • Topic 타입
    • 패턴을 통해 routing key와 binding key를 매칭한다.
    • 하나 이상의 큐에 메시지가 전달된다.
  • Header 타입
    • routing key를 무시하고, key:value 로 이루어진 헤더 값으로 매칭을 한다.
    • 이때 헤더는 Publish할때와 Queue와 Exchange를 바인딩할때 설정된다.
  • Fanout 타입
    • routing key를 무시하고, 연결된 모든 큐에 메시지를 전달한다.

코딩

자, 이제 코딩이다. 본 포스트에서는 코드에 대해서 API는 참조하지만 동작하는 코드를 서술하지는 않는다.

Golang으로 개발하고 있으므로, Go API를 기준으로 설명하겠다.

Go RabbitMQ API에는 아래와 같은 API들이 있다.

func (ch *Channel) QueueDeclare(name string, durable, autoDelete, exclusive, noWait bool, args Table) (Queue, error)
func (ch *Channel) ExchangeDeclare(name, kind string, durable, autoDelete, internal, noWait bool, args Table) error
func (ch *Channel) QueueBind(name, key, exchange string, noWait bool, args Table) error
func (ch *Channel) Publish(exchange, key string, mandatory, immediate bool, msg Publishing) error
func (ch *Channel) Consume(queue, consumer string, autoAck, exclusive, noLocal, noWait bool, args Table) (<-chan Delivery, error)

사용하는 순서대로 정렬했다.

큐 선언

먼저 Queue를 선언한다.

큐의 이름, durable 여부, 자동 삭제 여부, exclusive 여부, noWait 여부, args 테이블이 있는데

큐의 이름은 큐들끼리 서로 구분하기 위해서 사용하며 가장 이해가 쉽다.

나머지 부분이 이해가 안되는데[각주:8], 이를 순서대로 설명하겠다.

 

durable은 직역하면 내구성이 있는, 오래가는 이라는 뜻을 가진다.

이 파라미터는 이 뜻처럼 RabbitMQ 서비스가 종료되었을 때, 이 큐에 있는 메시지들을 보존할 것인가? 에 대한 물음이다. true를 하는 경우, 메시지의 속성에 따라 메시지를 보존하며, false의 경우에는 모든 메시지가 사라진다.

 

자동 삭제 여부는 큐에 대한 subscriber가 없을 때, 큐를 삭제할 것인지에 대한 여부이다.

false일 경우에는 Queue를 삭제하기 위해서 QueueDelete같은 함수를 호출해 삭제해야 한다.

 

exclusive 여부는 해당 Queue를 혼자 쓸 것인지에 대한 여부이다.

true일 경우, 오직 하나의 subscriber만 이를 사용할 수 있으며, 해당 subscriber가 연결을 종료하면 Queue는 삭제된다.

 

noWait 여부는 async 모드로 동작할지에 대한 여부를 묻는 것이다.

만약 false일 경우, 큐 선언 생성 함수는 서버에 큐를 생성하라 요청하고 그에 대한 결과를 받은 이후에 함수가 종료된다.

반대로 true인 경우, 큐 선언 함수는 서버에 큐를 생성하라고 요청하고, 즉시 함수가 종료된다.

만약 문제가 발생하는 경우에는 Channel 사용 도중에 에러가 발생하므로, 그냥 쓰면 된다.

다른 함수에도 noWait이 있는데, 동작은 다 동일하므로, 이후 설명에서는 제외한다.

 

args 테이블은 exchange 타입에서 설명한 header 타입일때 사용하는 것과 Queue optional 설정들을 하기 위한 테이블이다. opional은 설명에서 제외한다.

헤더는 key:value로 된다고 설명했는데, Table은 map[string]interface{}를 type(alias?)한 것이다.

exchange 선언

이름, 종류, durable, 자동 삭제 여부, internal, noWait, args 테이블 파라미터가 있는데,

이름, durable, 자동 삭제 여부, noWait, args 테이블은 위에서 큐 선언과 동일하나, 대상만 큐에서 exchange로 변경됬다고 보면된다. 자동 삭제 여부는 subscriber가 아니라, publisher의 접속 여부로 결정된다. 라는 것만 집고 넘어가겠다.

종류 또한, exchange의 타입에서 설명했으므로 패스한다.

 

그러면, internal 하나만 남는데, exchange가 publisher에 의해 연결되는 것이 아니라, exchange가 다른 exchange에 의해 연결될 수 있다고 한다. 그러나 아쉽게도 혹은 다행히도 버전이 올라가면서 deprecated 되었다고 한다. 그러므로 패스!

queue bind

이름, key, exchange, noWait 여부, args 테이블 파라미터에서

이름은 큐의 이름을 말하며, exchange는 exchange의 이름이다.

key는 binding key를 말하며, noWait, args 는 패스!

publish

exchange, key, mandatory, immediate, msg 파라미터에서

publisher는 exchange에 연결하므로, exchange의 이름을 설정해야 하며,

key는 routing key를 말한다.

 

mandatory는 직역하면 의무적인이라는 뜻을 가지며,

true이면 적어도 하나 이상의 queue에 메시지가 넘어가야 publish가 성공한다.

false이면 큐에 넘어가던 안가던 신경쓰지 않는다.

 

immediate는 한명의 consumer가 이 메시지를 가져갔는지에 대한 확인이다.

만약 true일때, 전달된 메시지가 있는 큐에 한명도 consumer가 없으면 에러가 발생한다.

false이면, consumer가 받던 말던 신경쓰지 않는다.

 

msg는 Publishing이라는 타입으로 되어있는데 이 안에는 exchange가 헤더 일때 key:value를 설정하는 변수와 데이터를 전달하기 위한 data가 따로 정의 되어 있다.

consume

queue, consumer, autoAck, exclusive, noLocal, noWait, args 테이블 파라미터에서

consumer는 queue에 연결되므로 큐의 이름을 설정하고,

subscriber를 identifying 하기 위해서 conumer 이름을 갖는다.

만약 consumer가 빈 문자열이면, RabbitMQ에서 자동으로 unique한 이름을 설정해준다.

 

autoAck는 subscriber가 메시지를 잘 받았는지 확인할지에 대한 플래그로,

만약 true라면, 메시지를 받고 제대로 처리를 못하고 subscriber 프로그램이 죽더라도 잘 받았다는 메시지를 보내게 된다.

 

exclusive는 해당 queue가 consumer 자신 혼자만 쓸지에 대한 플래그이다.

true라면 다른 consumer가 해당 queue에 접근을 할 수 없다.

 

no-local은 RabbitMQ에서 지원하지 않는다.

RabbitMQ에서 AMPQ 프로토콜을 지원하기 때문에 Go언어에서는 AMPQ 라이브러리로 RabbitMQ에 접근하지만 RabbitMQ는 no-local에 대한 처리를 지원하지 않는다. 그러므로 이게 뭔지 모르겠...

마무리하며...

처음으로 티스토리에서 공개적으로 쓰는 거라 제대로 썼는지 잘 모르겠다.

만약, 내용이 이상하다면, 이해가 가지 않는다면 댓글을 달아달라!


  1. https://en.wikipedia.org/wiki/RabbitMQ(아니 왜 한국 wiki는 이 내용이 없냐!) [본문으로]
  2. 써보면 어차피 알게된다. [본문으로]
  3. 언제한번 정리하겠다. [본문으로]
  4. 영어에 자신이 있다면 영문판으로 보기바란다. 훨씬 자세하다. [본문으로]
  5. 직역, 오역 난무 - 댓글로 수정 지적시 감사 [본문으로]
  6. 네이버 영어사진 검색: exchanger [본문으로]
  7. 정확한 날짜는 모른다.[/foonote]에 전화교환원이라는 직업이 존재했다. 전화교환원은 발신자가 누구에게 전화걸어주세요. 라고 말해주면, 해당 사람에게 연결하고 발신자가 수신자와 연결되도록 교환을 한다[footnote]나 이거 어떻게 알지? [본문으로]
  8. 내가 [본문으로]

'잡학(雜學)' 카테고리의 다른 글

Rust 2장 - 2. 기본 데이터 타입  (0) 2021.06.17
Rust 2장 - 1. 변수와 상수  (0) 2021.06.15
억지 기법  (0) 2021.06.13
Rust 1장. Hello World!  (0) 2021.06.12
프로그래밍 언어  (0) 2021.05.23