RabbitMQ : AMQP (Advanced Message Queuing Protocol) 표준을 구현한 오픈소스 메시지 브로커 미들웨어.
분산 시스템 환경에서 서로 다른 프로세스나 서비스 간에 데이터를 직접 주고받지 않고, 메시지 큐를 통해 비동기적으로 데이터를 중개하는 역할을 수행한다.
OpenStack에서 RabbitMQ는 컴포넌트 간의 RPC (Remote Procedure Call, 원격 프로시저 호출)을 구현하는 전송 계층으로 사용된다.
- AMQP 모델의 구조
- RabbitMQ의 내부 동작은 AMQP 모델을 따르며, 데이터 흐름은 다음 4가지 요소에 의해 제어된다.
- Producer(생산자) : 메시지를 생성하고 발송하는 주체 (ex. 요청을 보내는 nova-api)
- Exchange(교환기) : Producer로부터 메시지를 수신하여, 적절한 Queue로 라우팅하는 논리적 엔티티. 메시지 헤더에 포함된 Rouing Key를 기반으로 라우팅 규칙을 처리한다.
- Queue(큐) : 메시지가 Consumer에 의해 소비되기 전까지 저장되는 메모리 또는 디스크 상의 버퍼. FIFO 구조를 가진다.
- Consumer(소비자) : Queue를 구독하고 있다가 메시지를 가져와 처리하는 주체 (ex. 작업을 수행하는 nova-compute)
- Binding(바인딩) : Exchange와 Queue 사이의 연결 규칙. 특정 Routing Key를 가진 메시지는 이 Queue로 보내라는 규칙을 정의한다.
OpenStack 내부 통신 원리 : oslo.messaging과 RPC
- OpenStack 서비스들은 RabbitMQ를 직접 제어하는 대신,
oslo.messaging 라이브러리를 통해 추상화된 RPC(Remote Procedure Call) 패턴을 사용한다.
- 이는 다른 노드에 있는 함수를 로컬 함수처럼 호출하는 메커니즘으로, RabbitMQ를 통해 다음 두 가지 방식으로 구현된다.
- RPC Cast (비동기 단방향 통신)
- 호출자(Caller)가 메시지를 보내고 응답을 기다리지 않고 즉시 다음 작업을 수행하는 방식.
- 상태 변경이나 작업을 지시할 때 사용된다.
- 프로세스
- Client(nova-api) : 실행할 함수와 인자를 JSON으로 직렬화한다.
- Publish : 이 메시지를 RabbitMQ의 Exchange로 전송한다. 이때 특정 노드를 지정하거나 브로드캐스트하기 위한 Routing Key를 지정한다.
- Routing : Exchange는 Routing Key와 일치하는 Queue에 메시지를 복사한다.
- Consume : 해당 Queue를 리스닝하던 Server(nova-compute)가 메시지를 가져간다.
- Excution : 메시지를 역직렬화하여 해당 함수를 로컬에서 실행한다. 응답은 생성하지 않는다.
- RPC Call (동기 양방향 통신)
- 호출자(Caller)가 메시지를 보내고, 작업 결과(Return Value)가 올 때까지 대기(Block)하는 방식.
- 정보 조회 등에 사용됩니다.
- 프로세스
- Client (nova-api) : 메시지를 생성할 때, 임시 Queue (Reply Queue)를 하나 생성한다.
- Publish : 메시지 헤더의
reply_to 속성에 이 임시 Queue의 주소를, correlation_id 속성에 고유 요청 ID를 포함하여 Exchange로 전송한다.
- Routing & Consume : (Cast와 동일하게) Exchange를 거쳐 Server(nova-conductor)로 전달된다.
- Processing : Server가 요청을 처리하고 결과값을 생성한다.
- Response : Server는 메시지 헤더에 있던 reply_to Queue로 결과값을 전송한다.
- Receive : Client는 임시 Queue를 모니터링하다가 응답이 오면 처리를 재개한다.
- VM 생성을 요청했을 때, nova-api와 nova-compute가 RabbitMQ를 통해 통신하는 과정
- Topic Exchange 사용
- OpenStack은 주로
Topic 타입의 Exchange를 사용한다. 이는 Routing Key의 패턴 매칭을 지원한다. (ex. compute.* 또는 compute.hostname)
- Message Publish
- nova-api는 스케줄러가 지정한 호스트(ex. compute-1)를 타겟으로 메시지를 발행한다.
Payload : {"method": "build_and_run_instance", "args": {...}}
Rouing Key : compute.compute-1
- Queuing
- RabbitMQ 내부의 nova Exchange는 compute.compute-1 이라는 Routing Key를 확인한다.
- 이 Key는 compute-1 노드의 nova-compute 데몬이 구독하고 있는 전용 Queue와 바인딩되어 있으므로, 메시지가 해당 Queue로 라우팅된다.
- Message Ack
- compute-1의 nova-compute 데몬이 메시지를 가져가고, 처리가 완료되거나 안전하게 수신되었음을 RabbitMQ에 알리는 ACK 신호를 보낸다.
- rabbitMQ는 Queue에서 해당 메시지를 삭제한다.
- 이러한 메시지 큐 기반의 통식 방식을 채택함으로써 OpenStack은 다음과 같은 시스템적 특성을 가진다.
- Decoupling (결합도 감소) : 송신자와 수신자가 서로의 IP 주소나 상태를 실시간으로 알 필요가 없다.
- Asynchrony (비동기성) : 요청을 보내는 즉시 제어권이 반환되므로, 무거운 작업이 API 서버의 응답 속도를 저하시키지 않는다.
- Buffering (버퍼링) : 수신 측 서비스가 다운되거나 부하가 걸려도, 메시지는 Queue에 보존되므로 데이터 유실 없이 나중에 처리될 수 있다.
정리
OpenStack 내의 프로젝트들은 RabbitMQ를 통해서 서로 통신을 하는데, 그 과정은 특정 프로젝트가 다른 프로젝트에게 명령을 내릴 때 해당 명령은 메시지로 이루어져 있고, 그 메시지는 RabbitMQ 내에 있는 Exchange가 라우팅 키를 확인해서 해당 키의 데몬이 구독하고 있는 큐로 메시지가 라우팅 되고, 데몬이 큐 안에 있는 메시지를 가져가서 처리를 하고, 처리가 끝나 ACK 신호를 보내면 RabbitMQ가 해당 메시지를 큐에서 제거한다.