KR_RabbitMQ - somaz94/python-study GitHub Wiki
RabbitMQλ κ°λ ₯ν μ€νμμ€ λ©μμ§ λΈλ‘컀λ‘, AMQP νλ‘ν μ½μ κΈ°λ°μΌλ‘ μμ μ μΈ λ©μμ§ νμ μμ€ν
μ μ 곡νλ€. Pythonμμλ pika λΌμ΄λΈλ¬λ¦¬λ₯Ό ν΅ν΄ μ½κ² μ°κ²°νκ³ μ¬μ©ν μ μλ€.
import pika
from typing import Optional, Callable, Dict, Any
import json
import logging
import time
from contextlib import contextmanager
class RabbitMQClient:
"""RabbitMQ μλ²μμ μ°κ²°μ κ΄λ¦¬νλ ν΄λΌμ΄μΈνΈ ν΄λμ€"""
def __init__(
self,
host: str = 'localhost',
port: int = 5672,
username: str = 'guest',
password: str = 'guest',
virtual_host: str = '/',
connection_attempts: int = 3,
retry_delay: int = 5,
heartbeat: int = 600
):
"""
RabbitMQ ν΄λΌμ΄μΈνΈ μ΄κΈ°ν
Args:
host: RabbitMQ μλ² νΈμ€νΈ
port: RabbitMQ μλ² ν¬νΈ
username: μΈμ¦ μ¬μ©μ μ΄λ¦
password: μΈμ¦ λΉλ°λ²νΈ
virtual_host: κ°μ νΈμ€νΈ
connection_attempts: μ°κ²° μλ νμ
retry_delay: μ¬μλ κ°κ²©(μ΄)
heartbeat: ννΈλΉνΈ κ°κ²©(μ΄)
"""
self.logger = logging.getLogger(__name__)
# μΈμ¦ μ 보 μ€μ
self.credentials = pika.PlainCredentials(username, password)
# μ°κ²° νλΌλ―Έν° μ€μ
self.parameters = pika.ConnectionParameters(
host=host,
port=port,
virtual_host=virtual_host,
credentials=self.credentials,
connection_attempts=connection_attempts,
retry_delay=retry_delay,
heartbeat=heartbeat,
socket_timeout=10
)
self.connection = None
self.channel = None
def create_connection(self):
"""
RabbitMQ μλ²μμ μ μ°κ²° μμ±
Returns:
BlockingConnection: RabbitMQ μ°κ²° κ°μ²΄
"""
try:
self.logger.info("RabbitMQ μλ²μ μ°κ²° μ€...")
connection = pika.BlockingConnection(self.parameters)
self.logger.info("RabbitMQ μλ²μ μ°κ²°λ¨")
self.connection = connection
return connection
except pika.exceptions.AMQPConnectionError as e:
self.logger.error(f"RabbitMQ μ°κ²° μ€ν¨: {e}")
raise
def create_channel(self):
"""
RabbitMQ μ±λ μμ±
Returns:
BlockingChannel: RabbitMQ μ±λ κ°μ²΄
"""
if not self.connection or self.connection.is_closed:
self.create_connection()
self.channel = self.connection.channel()
return self.channel
def close(self):
"""μ°κ²° λ° μ±λ λ«κΈ°"""
if self.channel and self.channel.is_open:
self.logger.info("RabbitMQ μ±λ λ«λ μ€...")
self.channel.close()
if self.connection and self.connection.is_open:
self.logger.info("RabbitMQ μ°κ²° λ«λ μ€...")
self.connection.close()
@contextmanager
def connection_context(self):
"""
컨ν
μ€νΈ λ§€λμ λ₯Ό ν΅ν RabbitMQ μ°κ²° κ΄λ¦¬
μ¬μ© μ:
```
with client.connection_context() as channel:
channel.queue_declare(queue='hello')
channel.basic_publish(exchange='', routing_key='hello', body='Hello World!')
```
"""
connection = None
channel = None
try:
connection = self.create_connection()
channel = connection.channel()
yield channel
finally:
if channel and channel.is_open:
channel.close()
if connection and connection.is_open:
connection.close()
def check_queue_exists(self, queue_name: str) -> bool:
"""
νκ° μ‘΄μ¬νλμ§ νμΈ
Args:
queue_name: νμΈν ν μ΄λ¦
Returns:
bool: ν μ‘΄μ¬ μ¬λΆ
"""
with self.connection_context() as channel:
try:
channel.queue_declare(queue=queue_name, passive=True)
return True
except pika.exceptions.ChannelClosedByBroker:
return False
β
νΉμ§:
- μ μ°ν μ°κ²° μ€μ λ° νλΌλ―Έν° ꡬμ±
- μΈμ¦ μ 보 κ΄λ¦¬ λ° λ³΄μ μ°κ²°
- μ¬μλ λ‘μ§μ ν΅ν λ΄κ²°ν¨μ± μ 곡
- 컨ν μ€νΈ λ§€λμ λ₯Ό ν΅ν μμ κ΄λ¦¬
- μ±λ μμ± λ° κ΄λ¦¬
- λ‘κΉ μ ν΅ν μ°κ²° μν μΆμ
- κ°μ νΈμ€νΈ μ§μ
- νμ νν μ ν΅ν μ½λ κ°λ μ± ν₯μ
RabbitMQμμ λ©μμ§ μ£Όκ³ λ°κΈ°λ νλ‘λμμ 컨μλ¨ΈλΌλ λ ν΅μ¬ μ»΄ν¬λνΈλ₯Ό ν΅ν΄ μ΄λ£¨μ΄μ§λ€. νλ‘λμλ λ©μμ§λ₯Ό λ°ννκ³ , 컨μλ¨Έλ λ©μμ§λ₯Ό μλΉνλ€.
class RabbitMQProducer:
"""RabbitMQ λ©μμ§ λ°νμ λ΄λΉνλ νλ‘λμ ν΄λμ€"""
def __init__(self, client: RabbitMQClient):
"""
RabbitMQ νλ‘λμ μ΄κΈ°ν
Args:
client: RabbitMQ ν΄λΌμ΄μΈνΈ μΈμ€ν΄μ€
"""
self.client = client
self.logger = logging.getLogger(__name__)
def publish(
self,
queue_name: str,
message: Dict[str, Any],
durable: bool = True,
exchange: str = '',
routing_key: str = None,
properties: pika.BasicProperties = None,
mandatory: bool = False
):
"""
λ©μμ§ λ°ν
Args:
queue_name: ν μ΄λ¦
message: λ°νν λ©μμ§ (λμ
λ리)
durable: ν μ§μμ± μ¬λΆ
exchange: κ΅νκΈ° μ΄λ¦
routing_key: λΌμ°ν
ν€ (μ§μ νμ§ μμΌλ©΄ queue_name μ¬μ©)
properties: λ©μμ§ μμ±
mandatory: λ©μμ§ μ λ¬ λ³΄μ₯ μ¬λΆ
Returns:
bool: λ©μμ§ λ°ν μ±κ³΅ μ¬λΆ
"""
if routing_key is None:
routing_key = queue_name
if properties is None:
# κΈ°λ³Έ μμ± μ€μ - λ©μμ§ μ§μμ± λ³΄μ₯
properties = pika.BasicProperties(
delivery_mode=2, # λ©μμ§ μ§μμ±
content_type='application/json',
timestamp=int(time.time()),
message_id=f"{int(time.time())}-{id(message)}"
)
try:
with self.client.connection_context() as channel:
# ν μ μΈ
channel.queue_declare(queue=queue_name, durable=durable)
# λ©μμ§ λ°ν
channel.basic_publish(
exchange=exchange,
routing_key=routing_key,
body=json.dumps(message),
properties=properties,
mandatory=mandatory
)
self.logger.info(f"λ©μμ§ λ°ν μ±κ³΅: queue={queue_name}, routing_key={routing_key}")
return True
except Exception as e:
self.logger.error(f"λ©μμ§ λ°ν μ€ν¨: {e}")
return False
def publish_batch(
self,
queue_name: str,
messages: list,
batch_size: int = 100,
durable: bool = True
):
"""
λ°°μΉ λ©μμ§ λ°ν
Args:
queue_name: ν μ΄λ¦
messages: λ°νν λ©μμ§ λͺ©λ‘
batch_size: λ°°μΉ ν¬κΈ°
durable: ν μ§μμ± μ¬λΆ
Returns:
dict: μ±κ³΅/μ€ν¨ λ©μμ§ μ
"""
with self.client.connection_context() as channel:
# ν μ μΈ
channel.queue_declare(queue=queue_name, durable=durable)
success_count = 0
failure_count = 0
for i in range(0, len(messages), batch_size):
batch = messages[i:i+batch_size]
for message in batch:
try:
# λ©μμ§ λ°ν
channel.basic_publish(
exchange='',
routing_key=queue_name,
body=json.dumps(message),
properties=pika.BasicProperties(
delivery_mode=2, # λ©μμ§ μ§μμ±
content_type='application/json'
)
)
success_count += 1
except Exception as e:
self.logger.error(f"λ°°μΉ λ©μμ§ λ°ν μ€ν¨: {e}")
failure_count += 1
return {
'total': len(messages),
'success': success_count,
'failure': failure_count
}
class RabbitMQConsumer:
"""RabbitMQ λ©μμ§ μλΉλ₯Ό λ΄λΉνλ 컨μλ¨Έ ν΄λμ€"""
def __init__(self, client: RabbitMQClient):
"""
RabbitMQ 컨μλ¨Έ μ΄κΈ°ν
Args:
client: RabbitMQ ν΄λΌμ΄μΈνΈ μΈμ€ν΄μ€
"""
self.client = client
self.logger = logging.getLogger(__name__)
self.should_stop = False
def consume(
self,
queue_name: str,
callback: Callable,
durable: bool = True,
prefetch_count: int = 1,
auto_ack: bool = False
):
"""
λ©μμ§ μλΉ
Args:
queue_name: μλΉν ν μ΄λ¦
callback: λ©μμ§ μ²λ¦¬ μ½λ°± ν¨μ
durable: ν μ§μμ± μ¬λΆ
prefetch_count: ν λ²μ κ°μ Έμ¬ λ©μμ§ μ
auto_ack: μλ νμΈ μ¬λΆ
"""
def wrapped_callback(ch, method, properties, body):
"""
μ½λ°± λνΌ ν¨μ
Args:
ch: μ±λ
method: μ λ¬ λ©μλ
properties: λ©μμ§ μμ±
body: λ©μμ§ λ³Έλ¬Έ
"""
try:
message = json.loads(body)
self.logger.info(f"λ©μμ§ μμ : {queue_name}")
# μ¬μ©μ μ μ μ½λ°± μ€ν
result = callback(ch, method, properties, message)
# μλ νμΈμ΄ μλ κ²½μ° λ©μμ§ νμΈ
if not auto_ack:
ch.basic_ack(delivery_tag=method.delivery_tag)
return result
except json.JSONDecodeError as e:
self.logger.error(f"λ©μμ§ νμ μ€λ₯: {e}")
# λ©μμ§ νμ μ€λ₯ μ κ±°λΆ (μ¬νμ μμ)
if not auto_ack:
ch.basic_reject(delivery_tag=method.delivery_tag, requeue=False)
except Exception as e:
self.logger.error(f"λ©μμ§ μ²λ¦¬ μ€λ₯: {e}")
# κΈ°ν μ€λ₯ μ μ¬νμ
if not auto_ack:
ch.basic_nack(delivery_tag=method.delivery_tag, requeue=True)
# μ°κ²° λ° μ±λ μμ±
connection = self.client.create_connection()
channel = connection.channel()
try:
# ν μ μΈ
channel.queue_declare(queue=queue_name, durable=durable)
# QoS μ€μ
channel.basic_qos(prefetch_count=prefetch_count)
# μ½λ°± μ€μ
channel.basic_consume(
queue=queue_name,
on_message_callback=wrapped_callback,
auto_ack=auto_ack
)
self.logger.info(f"ν {queue_name}μμ λ©μμ§ λκΈ° μ€...")
# λ©μμ§ μλΉ μμ
while not self.should_stop:
connection.process_data_events(time_limit=1) # μ΄λ²€νΈ μ²λ¦¬
except KeyboardInterrupt:
self.logger.info("컨μλ¨Έ μ€μ§ μμ²")
self.should_stop = True
except Exception as e:
self.logger.error(f"컨μλ¨Έ μ€λ₯: {e}")
finally:
self.logger.info("컨μλ¨Έ μ’
λ£")
if channel and channel.is_open:
channel.close()
if connection and connection.is_open:
connection.close()
def stop(self):
"""컨μλ¨Έ μ€μ§"""
self.should_stop = True
# μ¬μ© μμ
def message_handler(ch, method, properties, message):
"""
λ©μμ§ μ²λ¦¬ νΈλ€λ¬ ν¨μ
Args:
ch: μ±λ
method: μ λ¬ λ©μλ
properties: λ©μμ§ μμ±
message: λμ½λ©λ λ©μμ§ (dict)
"""
print(f"λ©μμ§ μ²λ¦¬: {message}")
# λ©μμ§ μ²λ¦¬ λ‘μ§...
return True
# μμ μ€ν
if __name__ == "__main__":
# λ‘κΉ
μ€μ
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
# ν΄λΌμ΄μΈνΈ μμ±
client = RabbitMQClient(
host='localhost',
port=5672,
username='guest',
password='guest'
)
# νλ‘λμ μμ± λ° λ©μμ§ λ°ν
producer = RabbitMQProducer(client)
producer.publish(
queue_name='example_queue',
message={'data': 'Hello RabbitMQ!', 'timestamp': time.time()}
)
# 컨μλ¨Έ μμ± λ° λ©μμ§ μλΉ
consumer = RabbitMQConsumer(client)
try:
consumer.consume(
queue_name='example_queue',
callback=message_handler,
prefetch_count=10
)
except KeyboardInterrupt:
consumer.stop()
β
νΉμ§:
- κ°λ ₯ν λ©μμ§ λ°ν λ° μλΉ κΈ°λ₯
- 컨ν μ€νΈ λ§€λμ λ₯Ό ν΅ν μμ κ΄λ¦¬
- λ°°μΉ μ²λ¦¬ μ§μμΌλ‘ μ±λ₯ μ΅μ ν
- λ©μμ§ νμ κ²μ¦ λ° μ€λ₯ μ²λ¦¬
- QoS μ€μ μ ν΅ν λΆν κ΄λ¦¬
- λ©μμ§ νμΈ λ° κ±°λΆ λ©μ»€λμ¦
- μ¬μλ λ‘μ§κ³Ό λ΄κ²°ν¨μ±
- λ‘κΉ μ ν΅ν λ©μμ§ μν μΆμ
- νμ νν μΌλ‘ μ½λ κ°λ μ± λ° μμ μ± ν₯μ
- 컨μλ¨Έ μ°μν μ’ λ£ μ§μ
RabbitMQλ λ¨μν μ λμ λ©μμ§ μ΄μμ λ€μν κ³ κΈ λ©μμ§ ν¨ν΄μ μ 곡νλ€. κ΅νκΈ°(Exchange)λ λ©μμ§ λΌμ°ν
μ ν΅μ¬ κ΅¬μ± μμλ‘, μ¬λ¬ μ νμ ν΅ν΄ 볡μ‘ν λ©μμ§ μꡬμ¬νμ ꡬνν μ μλ€.
class RabbitMQExchange:
"""RabbitMQ κ΅νκΈ°λ₯Ό κ΄λ¦¬νλ ν΄λμ€"""
def __init__(self, client: RabbitMQClient):
"""
RabbitMQ κ΅νκΈ° κ΄λ¦¬μ μ΄κΈ°ν
Args:
client: RabbitMQ ν΄λΌμ΄μΈνΈ μΈμ€ν΄μ€
"""
self.client = client
self.logger = logging.getLogger(__name__)
def setup_fanout(self, exchange_name: str, durable: bool = True):
"""
ν¬μμ κ΅νκΈ° μ€μ - λͺ¨λ νμ λ©μμ§ λΈλ‘λμΊμ€νΈ
Args:
exchange_name: κ΅νκΈ° μ΄λ¦
durable: κ΅νκΈ° μ§μμ± μ¬λΆ
Returns:
pika.channel.Channel: μ€μ λ μ±λ
"""
with self.client.connection_context() as channel:
channel.exchange_declare(
exchange=exchange_name,
exchange_type='fanout',
durable=durable
)
self.logger.info(f"ν¬μμ κ΅νκΈ° '{exchange_name}' μ€μ μλ£")
return channel
def setup_direct(self, exchange_name: str, durable: bool = True):
"""
λ€μ΄λ νΈ κ΅νκΈ° μ€μ - λΌμ°ν
ν€ κΈ°λ° λ©μμ§ μ λ¬
Args:
exchange_name: κ΅νκΈ° μ΄λ¦
durable: κ΅νκΈ° μ§μμ± μ¬λΆ
Returns:
pika.channel.Channel: μ€μ λ μ±λ
"""
with self.client.connection_context() as channel:
channel.exchange_declare(
exchange=exchange_name,
exchange_type='direct',
durable=durable
)
self.logger.info(f"λ€μ΄λ νΈ κ΅νκΈ° '{exchange_name}' μ€μ μλ£")
return channel
def setup_topic(self, exchange_name: str, durable: bool = True):
"""
ν ν½ κ΅νκΈ° μ€μ - ν¨ν΄ λ§€μΉ κΈ°λ° λ©μμ§ μ λ¬
Args:
exchange_name: κ΅νκΈ° μ΄λ¦
durable: κ΅νκΈ° μ§μμ± μ¬λΆ
Returns:
pika.channel.Channel: μ€μ λ μ±λ
"""
with self.client.connection_context() as channel:
channel.exchange_declare(
exchange=exchange_name,
exchange_type='topic',
durable=durable
)
self.logger.info(f"ν ν½ κ΅νκΈ° '{exchange_name}' μ€μ μλ£")
return channel
def setup_headers(self, exchange_name: str, durable: bool = True):
"""
ν€λ κ΅νκΈ° μ€μ - ν€λ μμ± κΈ°λ° λ©μμ§ μ λ¬
Args:
exchange_name: κ΅νκΈ° μ΄λ¦
durable: κ΅νκΈ° μ§μμ± μ¬λΆ
Returns:
pika.channel.Channel: μ€μ λ μ±λ
"""
with self.client.connection_context() as channel:
channel.exchange_declare(
exchange=exchange_name,
exchange_type='headers',
durable=durable
)
self.logger.info(f"ν€λ κ΅νκΈ° '{exchange_name}' μ€μ μλ£")
return channel
def bind_queue_to_exchange(
self,
queue_name: str,
exchange_name: str,
routing_key: str = '',
headers: dict = None
):
"""
νλ₯Ό κ΅νκΈ°μ λ°μΈλ©
Args:
queue_name: ν μ΄λ¦
exchange_name: κ΅νκΈ° μ΄λ¦
routing_key: λΌμ°ν
ν€
headers: ν€λ μμ± (ν€λ κ΅νκΈ°μ©)
Returns:
bool: λ°μΈλ© μ±κ³΅ μ¬λΆ
"""
try:
with self.client.connection_context() as channel:
# ν μ μΈ
channel.queue_declare(queue=queue_name, durable=True)
# λ°μΈλ© μ€μ
if headers:
channel.queue_bind(
queue=queue_name,
exchange=exchange_name,
arguments=headers
)
else:
channel.queue_bind(
queue=queue_name,
exchange=exchange_name,
routing_key=routing_key
)
self.logger.info(f"ν '{queue_name}'λ₯Ό κ΅νκΈ° '{exchange_name}'μ λ°μΈλ© μλ£")
return True
except Exception as e:
self.logger.error(f"λ°μΈλ© μ€ν¨: {e}")
return False
def publish_to_exchange(
self,
exchange_name: str,
routing_key: str,
message: dict,
headers: dict = None
):
"""
κ΅νκΈ°μ λ©μμ§ λ°ν
Args:
exchange_name: κ΅νκΈ° μ΄λ¦
routing_key: λΌμ°ν
ν€
message: λ°νν λ©μμ§
headers: ν€λ μμ± (ν€λ κ΅νκΈ°μ©)
Returns:
bool: λ°ν μ±κ³΅ μ¬λΆ
"""
properties = pika.BasicProperties(
delivery_mode=2, # λ©μμ§ μ§μμ±
content_type='application/json',
headers=headers
)
try:
with self.client.connection_context() as channel:
channel.basic_publish(
exchange=exchange_name,
routing_key=routing_key,
body=json.dumps(message),
properties=properties
)
self.logger.info(f"κ΅νκΈ° '{exchange_name}'μ λ©μμ§ λ°ν μ±κ³΅")
return True
except Exception as e:
self.logger.error(f"κ΅νκΈ° λ©μμ§ λ°ν μ€ν¨: {e}")
return False
# κ΅νκΈ° ν¨ν΄ μμ
class ExchangePatterns:
"""RabbitMQ κ΅νκΈ° ν¨ν΄ ꡬν μμ """
def __init__(self, client: RabbitMQClient):
self.client = client
self.exchange_manager = RabbitMQExchange(client)
self.logger = logging.getLogger(__name__)
def setup_pub_sub(self, exchange_name: str):
"""
λ°ν-ꡬλ
ν¨ν΄ μ€μ (ν¬μμ κ΅νκΈ° μ¬μ©)
Args:
exchange_name: κ΅νκΈ° μ΄λ¦
Returns:
list: μμ±λ ν μ΄λ¦ λͺ©λ‘
"""
# ν¬μμ κ΅νκΈ° μ€μ
self.exchange_manager.setup_fanout(exchange_name)
# μ¬λ¬ ν μμ± λ° λ°μΈλ©
queues = []
for i in range(3):
queue_name = f"{exchange_name}_queue_{i}"
with self.client.connection_context() as channel:
result = channel.queue_declare(queue=queue_name, exclusive=True)
queue_name = result.method.queue
queues.append(queue_name)
# νλ₯Ό κ΅νκΈ°μ λ°μΈλ©
channel.queue_bind(
exchange=exchange_name,
queue=queue_name
)
self.logger.info(f"λ°ν-ꡬλ
ν¨ν΄ μ€μ μλ£: {queues}")
return queues
def setup_routing(self, exchange_name: str, routing_keys: list):
"""
λΌμ°ν
ν¨ν΄ μ€μ (λ€μ΄λ νΈ κ΅νκΈ° μ¬μ©)
Args:
exchange_name: κ΅νκΈ° μ΄λ¦
routing_keys: λΌμ°ν
ν€ λͺ©λ‘
Returns:
dict: λΌμ°ν
ν€λ³ ν μ΄λ¦ λ§€ν
"""
# λ€μ΄λ νΈ κ΅νκΈ° μ€μ
self.exchange_manager.setup_direct(exchange_name)
# λΌμ°ν
ν€λ³ ν μμ± λ° λ°μΈλ©
routing_map = {}
for key in routing_keys:
queue_name = f"{exchange_name}_{key}"
with self.client.connection_context() as channel:
result = channel.queue_declare(queue=queue_name, exclusive=True)
queue_name = result.method.queue
routing_map[key] = queue_name
# νλ₯Ό κ΅νκΈ°μ λ°μΈλ©
channel.queue_bind(
exchange=exchange_name,
queue=queue_name,
routing_key=key
)
self.logger.info(f"λΌμ°ν
ν¨ν΄ μ€μ μλ£: {routing_map}")
return routing_map
def setup_topics(self, exchange_name: str, topic_patterns: list):
"""
ν ν½ ν¨ν΄ μ€μ (ν ν½ κ΅νκΈ° μ¬μ©)
Args:
exchange_name: κ΅νκΈ° μ΄λ¦
topic_patterns: ν ν½ ν¨ν΄ λͺ©λ‘
Returns:
dict: ν ν½ ν¨ν΄λ³ ν μ΄λ¦ λ§€ν
"""
# ν ν½ κ΅νκΈ° μ€μ
self.exchange_manager.setup_topic(exchange_name)
# ν ν½ ν¨ν΄λ³ ν μμ± λ° λ°μΈλ©
topic_map = {}
for pattern in topic_patterns:
queue_name = f"{exchange_name}_{pattern.replace('.', '_').replace('*', 'star').replace('#', 'hash')}"
with self.client.connection_context() as channel:
result = channel.queue_declare(queue=queue_name, exclusive=True)
queue_name = result.method.queue
topic_map[pattern] = queue_name
# νλ₯Ό κ΅νκΈ°μ λ°μΈλ©
channel.queue_bind(
exchange=exchange_name,
queue=queue_name,
routing_key=pattern
)
self.logger.info(f"ν ν½ ν¨ν΄ μ€μ μλ£: {topic_map}")
return topic_map
β
νΉμ§:
- λ€μν κ΅νκΈ° μ ν μ§μ (ν¬μμ, λ€μ΄λ νΈ, ν ν½, ν€λ)
- μ μ°ν λ©μμ§ λΌμ°ν ꡬμ±
- λ°μΈλ©μ ν΅ν λ©μμ§ νν°λ§
- λ°ν-ꡬλ ν¨ν΄ ꡬν
- λΌμ°ν ν¨ν΄ ꡬν
- ν ν½ κΈ°λ° ν¨ν΄ λ§€μΉ
- ν€λ κΈ°λ° λ©μμ§ λΌμ°ν
- 컨ν μ€νΈ λ§€λμ λ₯Ό ν΅ν μμ κ΄λ¦¬
- λ‘κΉ μ ν΅ν κ΅νκΈ° μμ μΆμ
λ©μμ§ μ²λ¦¬λ RabbitMQ μμ€ν
μ ν΅μ¬ λΆλΆμΌλ‘, μμ μ μΈ μ²λ¦¬μ μ€λ₯ 볡ꡬ λ©μ»€λμ¦μ΄ νμμ μ΄λ€. μ μ ν νμΈ(ack)κ³Ό κ±°λΆ(nack) μ²λ¦¬λ₯Ό ν΅ν΄ λ©μμ§κ° μμ€λμ§ μλλ‘ λ³΄μ₯ν μ μλ€.
class MessageHandler:
"""RabbitMQ λ©μμ§ μ²λ¦¬ λ° νμΈ κ΄λ¦¬ ν΄λμ€"""
def __init__(self):
self.logger = logging.getLogger(__name__)
@staticmethod
def process_message(
channel,
method,
properties,
body
):
"""
λ©μμ§ μ²λ¦¬ κΈ°λ³Έ νΈλ€λ¬
Args:
channel: RabbitMQ μ±λ
method: μ λ¬ λ©μλ
properties: λ©μμ§ μμ±
body: λ©μμ§ λ³Έλ¬Έ
"""
try:
# λ©μμ§ νμ±
message = json.loads(body)
print(f"Received message: {message}")
# λ©μμ§ μ²λ¦¬ λ‘μ§ (μμ)
# μ€μ ꡬνμμλ λΉμ¦λμ€ λ‘μ§μ λ§κ² μ²λ¦¬
processing_time = 0.5 # λ©μμ§ μ²λ¦¬μ 걸리λ μκ° (μμ)
time.sleep(processing_time)
# λ©μμ§ μ²λ¦¬ μ±κ³΅ μ νμΈ(ack)
channel.basic_ack(delivery_tag=method.delivery_tag)
print(f"μ²λ¦¬ μλ£ λ° νμΈ(ack): {message.get('id', 'unknown')}")
except json.JSONDecodeError as e:
# JSON νμ± μ€λ₯ - λ©μμ§ νμμ΄ μλͺ»λ κ²½μ°
print(f"JSON νμ± μ€λ₯: {e}")
# νμ μ€λ₯λ μ¬μλν΄λ λμΌνλ―λ‘ μ¬νμνμ§ μμ
channel.basic_reject(
delivery_tag=method.delivery_tag,
requeue=False
)
except Exception as e:
# κΈ°ν μ²λ¦¬ μ€λ₯ - μΌμμ μΈ λ¬Έμ μΌ μ μμΌλ―λ‘ μ¬νμ
print(f"λ©μμ§ μ²λ¦¬ μ€λ₯: {e}")
channel.basic_nack(
delivery_tag=method.delivery_tag,
requeue=True
)
@staticmethod
def process_with_retry(
channel,
method,
properties,
body,
max_retries: int = 3,
retry_exchange: str = None,
retry_queue: str = None,
dead_letter_exchange: str = None
):
"""
μ¬μλ λ‘μ§μ΄ ν¬ν¨λ λ©μμ§ μ²λ¦¬ νΈλ€λ¬
Args:
channel: RabbitMQ μ±λ
method: μ λ¬ λ©μλ
properties: λ©μμ§ μμ±
body: λ©μμ§ λ³Έλ¬Έ
max_retries: μ΅λ μ¬μλ νμ
retry_exchange: μ¬μλ κ΅νκΈ° μ΄λ¦
retry_queue: μ¬μλ ν μ΄λ¦
dead_letter_exchange: λ°λλ ν° κ΅νκΈ° μ΄λ¦
"""
try:
# ν€λμμ μ¬μλ νμ νμΈ
headers = properties.headers or {}
retry_count = headers.get('x-retry-count', 0)
# λ©μμ§ νμ±
message = json.loads(body)
print(f"μ²λ¦¬ μ€ (μλ {retry_count+1}/{max_retries+1}): {message}")
# λ©μμ§ μ²λ¦¬ λ‘μ§ (μμ)
# μ€μ ꡬνμμλ λΉμ¦λμ€ λ‘μ§μ λ§κ² μ²λ¦¬
if random.random() < 0.3: # 30% νλ₯ λ‘ μ€λ₯ λ°μ (μμ)
raise Exception("λλ€ μ²λ¦¬ μ€λ₯ (ν
μ€νΈμ©)")
# λ©μμ§ μ²λ¦¬ μ±κ³΅ μ νμΈ(ack)
channel.basic_ack(delivery_tag=method.delivery_tag)
print(f"μ²λ¦¬ μλ£: {message.get('id', 'unknown')}")
except Exception as e:
print(f"μ²λ¦¬ μ€λ₯: {e}")
# μ΅λ μ¬μλ νμ μ΄κ³Ό μ¬λΆ νμΈ
if retry_count >= max_retries:
print(f"μ΅λ μ¬μλ νμ μ΄κ³Ό: {message.get('id', 'unknown')}")
# λ°λλ ν° κ΅νκΈ°κ° μ€μ λ κ²½μ° ν΄λΉ κ΅νκΈ°λ‘ μ μ‘
if dead_letter_exchange:
print(f"λ°λλ ν° νλ‘ μ΄λ: {message.get('id', 'unknown')}")
properties.headers = headers
properties.headers['x-error'] = str(e)
properties.headers['x-failed-at'] = time.time()
channel.basic_publish(
exchange=dead_letter_exchange,
routing_key='dead_letter',
body=body,
properties=properties
)
# μλ³Έ λ©μμ§ νμΈ(ack)
channel.basic_ack(delivery_tag=method.delivery_tag)
else:
# μ¬μλ νλ‘ λ€μ μ μ‘
if retry_exchange and retry_queue:
print(f"μ¬μλ νλ‘ μ΄λ: {message.get('id', 'unknown')}")
# μ¬μλ μ 보 μ
λ°μ΄νΈ
headers['x-retry-count'] = retry_count + 1
headers['x-original-exchange'] = properties.headers.get('x-original-exchange', '')
headers['x-original-routing-key'] = properties.headers.get('x-original-routing-key', method.routing_key)
headers['x-last-retry-at'] = time.time()
new_properties = pika.BasicProperties(
delivery_mode=2,
headers=headers
)
# μ¬μλ νλ‘ λ°ν
channel.basic_publish(
exchange=retry_exchange,
routing_key=retry_queue,
body=body,
properties=new_properties
)
# μλ³Έ λ©μμ§ νμΈ(ack)
channel.basic_ack(delivery_tag=method.delivery_tag)
else:
# μ¬μλ μ€μ μ΄ μλ κ²½μ° μλ³Έ νμ μ¬νμ
print(f"μλ³Έ νμ μ¬νμ: {message.get('id', 'unknown')}")
channel.basic_nack(
delivery_tag=method.delivery_tag,
requeue=True
)
@staticmethod
def setup_retry_mechanism(
channel,
queue_name: str,
retry_delay: int = 30000, # μ¬μλ μ§μ° μκ° (λ°λ¦¬μ΄)
max_retries: int = 3
):
"""
μ¬μλ λ©μ»€λμ¦ μ€μ
Args:
channel: RabbitMQ μ±λ
queue_name: μλ³Έ ν μ΄λ¦
retry_delay: μ¬μλ μ§μ° μκ° (λ°λ¦¬μ΄)
max_retries: μ΅λ μ¬μλ νμ
Returns:
tuple: (μ¬μλ ν μ΄λ¦, λ°λλ ν° ν μ΄λ¦)
"""
# λ°λλ ν° κ΅νκΈ° λ° ν μ€μ
dlx_name = f"{queue_name}.dlx"
dl_queue_name = f"{queue_name}.deadletter"
channel.exchange_declare(
exchange=dlx_name,
exchange_type='direct',
durable=True
)
channel.queue_declare(
queue=dl_queue_name,
durable=True
)
channel.queue_bind(
queue=dl_queue_name,
exchange=dlx_name,
routing_key='dead_letter'
)
# μ¬μλ κ΅νκΈ° λ° ν μ€μ
retry_exchange = f"{queue_name}.retry"
retry_queue = f"{queue_name}.retry"
channel.exchange_declare(
exchange=retry_exchange,
exchange_type='direct',
durable=True
)
# μ¬μλ ν μ€μ (TTL μ μ©)
channel.queue_declare(
queue=retry_queue,
durable=True,
arguments={
'x-dead-letter-exchange': '', # κΈ°λ³Έ κ΅νκΈ°λ‘ μ€μ
'x-dead-letter-routing-key': queue_name, # μλ³Έ νλ‘ λ€μ λΌμ°ν
'x-message-ttl': retry_delay # μ¬μλ μ§μ° μκ°
}
)
channel.queue_bind(
queue=retry_queue,
exchange=retry_exchange,
routing_key=retry_queue
)
# μλ³Έ νμ λ°λλ ν° κ΅νκΈ° μ€μ
channel.queue_declare(
queue=queue_name,
durable=True,
arguments={
'x-dead-letter-exchange': dlx_name,
'x-dead-letter-routing-key': 'dead_letter'
}
)
print(f"μ¬μλ λ©μ»€λμ¦ μ€μ μλ£: {queue_name}")
return retry_queue, dl_queue_name
β
νΉμ§:
- μ€λ₯ μ²λ¦¬ λ° λ³΅κ΅¬ λ©μ»€λμ¦
- μ¬μλ λ‘μ§ κ΅¬ν
- λ©μμ§ μλ΅ (ack, nack, reject)
- λ°λλ ν° ν μ€μ λ° κ΄λ¦¬
- μ§μ° μ¬μλ (TTL νμ©)
- μ§ν μν μΆμ (ν€λ μ¬μ©)
- μ΅λ μ¬μλ νμ μ€μ
- μ€λ₯ μ 보 κΈ°λ‘
- λ©μμ§ μ²λ¦¬ μν λ‘κΉ
RabbitMQλ κ°λ ₯ν λͺ¨λν°λ§ κΈ°λ₯κ³Ό κ΄λ¦¬ λꡬλ₯Ό μ 곡νμ¬ λ©μμ§ μΈνλΌμ μνλ₯Ό μΆμ νκ³ μ΅μ νν μ μλ€. Pythonμμλ κ΄λ¦¬ APIλ₯Ό ν΅ν΄ μ΄λ¬ν κΈ°λ₯μ νλ‘κ·Έλλ§€ν±νκ² μ κ·Όν μ μλ€.
import requests
import base64
import urllib.parse
import time
from typing import Dict, List, Any, Optional
class RabbitMQMonitor:
"""RabbitMQ λͺ¨λν°λ§ λ° κ΄λ¦¬λ₯Ό μν ν΄λμ€"""
def __init__(
self,
host: str = 'localhost',
port: int = 15672,
username: str = 'guest',
password: str = 'guest',
protocol: str = 'http'
):
"""
RabbitMQ λͺ¨λν° μ΄κΈ°ν
Args:
host: RabbitMQ κ΄λ¦¬ μΈν°νμ΄μ€ νΈμ€νΈ
port: RabbitMQ κ΄λ¦¬ μΈν°νμ΄μ€ ν¬νΈ (κΈ°λ³Έ 15672)
username: κ΄λ¦¬μ μ¬μ©μ μ΄λ¦
password: κ΄λ¦¬μ λΉλ°λ²νΈ
protocol: ν΅μ νλ‘ν μ½ (http λλ https)
"""
self.base_url = f"{protocol}://{host}:{port}/api"
self.auth = (username, password)
self.logger = logging.getLogger(__name__)
def _request(
self,
method: str,
endpoint: str,
data: dict = None,
params: dict = None
) -> dict:
"""
RabbitMQ API μμ² λ©μλ
Args:
method: HTTP λ©μλ (GET, POST, PUT, DELETE)
endpoint: API μλν¬μΈνΈ
data: μμ² λ°μ΄ν° (μλ κ²½μ°)
params: URL νλΌλ―Έν° (μλ κ²½μ°)
Returns:
dict: API μλ΅
"""
url = f"{self.base_url}/{endpoint}"
try:
response = requests.request(
method=method,
url=url,
auth=self.auth,
json=data,
params=params
)
response.raise_for_status()
if response.content:
return response.json()
return {}
except requests.exceptions.HTTPError as e:
self.logger.error(f"API μμ² μ€λ₯ ({method} {url}): {e}")
if response.content:
self.logger.error(f"μλ΅: {response.text}")
raise
except Exception as e:
self.logger.error(f"API μμ² μ€ μμΈ λ°μ: {e}")
raise
def get_overview(self) -> dict:
"""
RabbitMQ μλ² κ°μ μ 보 μ‘°ν
Returns:
dict: μλ² κ°μ μ 보
"""
return self._request('GET', 'overview')
def get_nodes(self) -> List[dict]:
"""
RabbitMQ λ
Έλ λͺ©λ‘ μ‘°ν
Returns:
List[dict]: λ
Έλ μ 보 λͺ©λ‘
"""
return self._request('GET', 'nodes')
def get_queues(self, vhost: str = None) -> List[dict]:
"""
ν λͺ©λ‘ μ‘°ν
Args:
vhost: κ°μ νΈμ€νΈ (μ§μ νμ§ μμΌλ©΄ λͺ¨λ κ°μ νΈμ€νΈ)
Returns:
List[dict]: ν μ 보 λͺ©λ‘
"""
endpoint = 'queues'
if vhost:
endpoint = f"queues/{urllib.parse.quote_plus(vhost)}"
return self._request('GET', endpoint)
def get_queue(self, vhost: str, queue_name: str) -> dict:
"""
νΉμ ν μ 보 μ‘°ν
Args:
vhost: κ°μ νΈμ€νΈ
queue_name: ν μ΄λ¦
Returns:
dict: ν μ 보
"""
endpoint = f"queues/{urllib.parse.quote_plus(vhost)}/{urllib.parse.quote_plus(queue_name)}"
return self._request('GET', endpoint)
def get_exchanges(self, vhost: str = None) -> List[dict]:
"""
κ΅νκΈ° λͺ©λ‘ μ‘°ν
Args:
vhost: κ°μ νΈμ€νΈ (μ§μ νμ§ μμΌλ©΄ λͺ¨λ κ°μ νΈμ€νΈ)
Returns:
List[dict]: κ΅νκΈ° μ 보 λͺ©λ‘
"""
endpoint = 'exchanges'
if vhost:
endpoint = f"exchanges/{urllib.parse.quote_plus(vhost)}"
return self._request('GET', endpoint)
def get_bindings(self, vhost: str = None) -> List[dict]:
"""
λ°μΈλ© λͺ©λ‘ μ‘°ν
Args:
vhost: κ°μ νΈμ€νΈ (μ§μ νμ§ μμΌλ©΄ λͺ¨λ κ°μ νΈμ€νΈ)
Returns:
List[dict]: λ°μΈλ© μ 보 λͺ©λ‘
"""
endpoint = 'bindings'
if vhost:
endpoint = f"bindings/{urllib.parse.quote_plus(vhost)}"
return self._request('GET', endpoint)
def get_connections(self) -> List[dict]:
"""
μ°κ²° λͺ©λ‘ μ‘°ν
Returns:
List[dict]: μ°κ²° μ 보 λͺ©λ‘
"""
return self._request('GET', 'connections')
def get_channels(self) -> List[dict]:
"""
μ±λ λͺ©λ‘ μ‘°ν
Returns:
List[dict]: μ±λ μ 보 λͺ©λ‘
"""
return self._request('GET', 'channels')
def purge_queue(self, vhost: str, queue_name: str) -> bool:
"""
ν λ΄μ© μ κ±°
Args:
vhost: κ°μ νΈμ€νΈ
queue_name: ν μ΄λ¦
Returns:
bool: μ±κ³΅ μ¬λΆ
"""
try:
endpoint = f"queues/{urllib.parse.quote_plus(vhost)}/{urllib.parse.quote_plus(queue_name)}/contents"
self._request('DELETE', endpoint)
self.logger.info(f"ν '{queue_name}' λ΄μ© μ κ±° μλ£")
return True
except Exception as e:
self.logger.error(f"ν λ΄μ© μ κ±° μ€ν¨: {e}")
return False
def create_user(self, username: str, password: str, tags: str = "management") -> bool:
"""
μ¬μ©μ μμ±
Args:
username: μ¬μ©μ μ΄λ¦
password: λΉλ°λ²νΈ
tags: μ¬μ©μ νκ·Έ (μΌνλ‘ κ΅¬λΆ)
Returns:
bool: μ±κ³΅ μ¬λΆ
"""
try:
data = {
"password": password,
"tags": tags
}
self._request('PUT', f"users/{urllib.parse.quote_plus(username)}", data)
self.logger.info(f"μ¬μ©μ '{username}' μμ± μλ£")
return True
except Exception as e:
self.logger.error(f"μ¬μ©μ μμ± μ€ν¨: {e}")
return False
def set_permissions(
self,
vhost: str,
username: str,
configure: str = ".*",
write: str = ".*",
read: str = ".*"
) -> bool:
"""
μ¬μ©μ κΆν μ€μ
Args:
vhost: κ°μ νΈμ€νΈ
username: μ¬μ©μ μ΄λ¦
configure: μ€μ κΆν μ κ·μ
write: μ°κΈ° κΆν μ κ·μ
read: μ½κΈ° κΆν μ κ·μ
Returns:
bool: μ±κ³΅ μ¬λΆ
"""
try:
data = {
"configure": configure,
"write": write,
"read": read
}
endpoint = f"permissions/{urllib.parse.quote_plus(vhost)}/{urllib.parse.quote_plus(username)}"
self._request('PUT', endpoint, data)
self.logger.info(f"μ¬μ©μ '{username}'μ '{vhost}' κΆν μ€μ μλ£")
return True
except Exception as e:
self.logger.error(f"κΆν μ€μ μ€ν¨: {e}")
return False
def get_queue_stats(self, vhost: str, queue_name: str) -> dict:
"""
ν ν΅κ³ μ 보 μ‘°ν
Args:
vhost: κ°μ νΈμ€νΈ
queue_name: ν μ΄λ¦
Returns:
dict: ν ν΅κ³ μ 보
"""
try:
queue_info = self.get_queue(vhost, queue_name)
stats = {
"name": queue_info.get("name"),
"messages": queue_info.get("messages", 0),
"messages_ready": queue_info.get("messages_ready", 0),
"messages_unacknowledged": queue_info.get("messages_unacknowledged", 0),
"consumers": queue_info.get("consumers", 0),
"message_stats": queue_info.get("message_stats", {}),
"memory": queue_info.get("memory", 0),
"state": queue_info.get("state", "unknown"),
"node": queue_info.get("node", "unknown")
}
return stats
except Exception as e:
self.logger.error(f"ν ν΅κ³ μ‘°ν μ€ν¨: {e}")
return {}
def monitor_queues(
self,
vhost: str = '/',
interval: int = 5,
limit: int = 10,
output: bool = True
):
"""
ν λͺ¨λν°λ§ μ€ν
Args:
vhost: κ°μ νΈμ€νΈ
interval: λͺ¨λν°λ§ κ°κ²©(μ΄)
limit: λͺ¨λν°λ§ νμ (0=무μ ν)
output: μΆλ ₯ μ¬λΆ
"""
count = 0
try:
while limit == 0 or count < limit:
queues = self.get_queues(vhost)
if output:
print(f"\n===== ν λͺ¨λν°λ§ ({time.strftime('%Y-%m-%d %H:%M:%S')}) =====")
print(f"{'μ΄λ¦':30} {'λ©μμ§':10} {'μ€λΉ':10} {'λ―ΈνμΈ':10} {'μλΉμ':5}")
print("-" * 70)
for q in queues:
name = q.get("name", "")
messages = q.get("messages", 0)
messages_ready = q.get("messages_ready", 0)
messages_unack = q.get("messages_unacknowledged", 0)
consumers = q.get("consumers", 0)
if output:
print(f"{name[:30]:30} {messages:10} {messages_ready:10} {messages_unack:10} {consumers:5}")
count += 1
if limit == 0 or count < limit:
time.sleep(interval)
except KeyboardInterrupt:
print("\nλͺ¨λν°λ§ μ€λ¨λ¨")
except Exception as e:
self.logger.error(f"λͺ¨λν°λ§ μ€λ₯: {e}")
# μ¬μ© μμ
if __name__ == "__main__":
# λ‘κΉ
μ€μ
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
# RabbitMQ λͺ¨λν° μμ±
monitor = RabbitMQMonitor(
host='localhost',
port=15672,
username='guest',
password='guest'
)
# μλ² κ°μ μ 보 μ‘°ν
overview = monitor.get_overview()
print(f"RabbitMQ λ²μ : {overview.get('rabbitmq_version')}")
print(f"Erlang λ²μ : {overview.get('erlang_version')}")
print(f"λ
Έλ: {overview.get('node')}")
# ν λͺ©λ‘ μ‘°ν
queues = monitor.get_queues('/')
print(f"\nν λͺ©λ‘ ({len(queues)}κ°):")
for q in queues:
print(f"- {q.get('name')}: {q.get('messages')} λ©μμ§")
# ν λͺ¨λν°λ§ (5μ΄ κ°κ²©, 3ν)
print("\nν λͺ¨λν°λ§ μμ...")
monitor.monitor_queues(interval=5, limit=3)
β
νΉμ§:
- RabbitMQ κ΄λ¦¬ API νμ©
- μλ² μν λͺ¨λν°λ§
- νμ κ΅νκΈ° μ 보 μ‘°ν
- 리μμ€ μ¬μ©λ μΆμ
- λ©μμ§ ν΅κ³ λΆμ
- μ¬μ©μ κ΄λ¦¬ λ° κΆν μ€μ
- μ€μκ° λͺ¨λν°λ§ κΈ°λ₯
- λμ보λ μ 보 νλ‘κ·Έλλ§€ν± μ κ·Ό
- REST API κΈ°λ° ν΅ν©
- λͺ¨λν°λ§ μλν
β
λͺ¨λ² μ¬λ‘:
- μ°κ²° κ΄λ¦¬: μ₯κΈ° μ€ν μ ν리μΌμ΄μ μ μ°κ²° νλ§κ³Ό μλ μ¬μ°κ²° λ©μ»€λμ¦μ ꡬννμ¬ μμ μ±μ λμΈλ€.
- λ©μμ§ μ§μμ±: μ€μ λ©μμ§λ μ§μμ±μ μ€μ νκ³ νλ λ΄κ΅¬μ± μκ² κ΅¬μ±νμ¬ μλ² μ¬μμ μμλ λ°μ΄ν° μμ€μ λ°©μ§νλ€.
- QoS μ€μ : prefetch_count κ°μ μ μ ν μ‘°μ νμ¬ μ컀 κ° λΆν κ· νμ μ μ§νκ³ λ©λͺ¨λ¦¬ μ¬μ©λμ κ΄λ¦¬νλ€.
- κ΅νκΈ° μ ν: λ©μμ§ ν¨ν΄μ λ§λ μ μ ν κ΅νκΈ° μ νμ μ ννμ¬ λΌμ°ν ν¨μ¨μ±μ κ·Ήλννλ€.
- μ¬μλ λ©μ»€λμ¦: λ°λλ ν° νμ TTLμ νμ©ν μ§μ° μ¬μλ μ λ΅μΌλ‘ μΌμμ μ€λ₯λ₯Ό μ°μνκ² μ²λ¦¬νλ€.
- λͺ¨λν°λ§: ν κΈΈμ΄, λ©μμ§ μ²λ¦¬μ¨, λ©λͺ¨λ¦¬ μ¬μ©λμ μ κΈ°μ μΌλ‘ λͺ¨λν°λ§νμ¬ λ³λͺ© νμμ κ°μ§νλ€.
- μ±λ₯ μ΅μ ν: λ°°μΉ μ²λ¦¬, λ©μμ§ μμΆ, μ±λ μ¬μ¬μ©μΌλ‘ μ²λ¦¬λμ μ΅μ ννλ€.
- 보μ μ€μ : κ°λ ₯ν μνΈμ TLS μ°κ²°λ‘ λ©μμ§ μ μ‘μ 보νΈνκ³ , VHOSTμ μ¬μ©μ κΆνμ μΈλΆννμ¬ μ κ·Όμ μ ννλ€.
- ν΄λ¬μ€ν°λ§: κ³ κ°μ©μ±μ μν΄ RabbitMQ ν΄λ¬μ€ν°λ₯Ό ꡬμ±νκ³ μ μ ν μ μ± μ μ€μ νλ€.
- λ°±μ μ λ΅: μ κΈ°μ μΈ κ΅¬μ± λ°±μ κ³Ό λ©μμ§ μ€λ μ·μΌλ‘ μ¬ν΄ 볡ꡬ λλΉμ± μ λ§λ ¨νλ€.
- λ‘κΉ μ λ΅: λ©μμ§ μ²λ¦¬ κ³Όμ μμ μΆ©λΆν 컨ν μ€νΈλ₯Ό λ‘κΉ νμ¬ λ¬Έμ ν΄κ²°κ³Ό κ°μ¬λ₯Ό μ©μ΄νκ² νλ€.