79 lines
4.1 KiB
Python
79 lines
4.1 KiB
Python
import asyncio
|
||
import aio_pika
|
||
from config import RABBITMQ_URI
|
||
|
||
|
||
async def fanout_consumer(queue_name: str, consumer_id: int):
|
||
"""
|
||
Fanout Exchange 消费者:监听单个队列,接收 Fanout 广播的消息
|
||
:param queue_name: 要监听的队列名称(与生产者 setup 中创建的队列一致)
|
||
:param consumer_id: 消费者标识,区分不同队列的消费者
|
||
"""
|
||
# 1. 建立稳健连接(自动重连机制,应对网络波动或 RabbitMQ 重启)
|
||
connection = await aio_pika.connect_robust(RABBITMQ_URI)
|
||
# 2. 创建通信信道(所有消息操作通过信道执行,减少 TCP 连接开销)
|
||
channel = await connection.channel()
|
||
|
||
# 3. 开启公平调度:确保消费者处理完 1 条消息后再接收下 1 条
|
||
# 避免单队列内消息堆积(尤其 Fanout 场景下多队列并行处理需均衡)
|
||
await channel.set_qos(prefetch_count=1)
|
||
|
||
# 4. 声明要监听的队列(与生产者 setup_fanout_exchange 中创建的队列完全一致)
|
||
# 若队列未初始化(未执行 setup),会报错提醒先完成交换器和队列创建
|
||
queue = await channel.declare_queue(
|
||
queue_name,
|
||
durable=True, # 与生产者一致:队列持久化(重启不丢失)
|
||
auto_delete=False # 队列不自动删除(即使无消费者也保留,确保广播消息不丢失)
|
||
)
|
||
|
||
# 5. 定义消息处理逻辑(Fanout 场景下,同一消息会被所有队列的消费者接收)
|
||
async def on_message_received(message: aio_pika.IncomingMessage):
|
||
# async with 上下文:自动确认消息(处理完成后告知 RabbitMQ 删除,避免重复消费)
|
||
# 若处理崩溃,消息会重新入队,等待消费者重启后重试
|
||
async with message.process():
|
||
# 解码消息体(生产者用 utf-8 编码,此处对应解码)
|
||
message_content = message.body.decode("utf-8")
|
||
# Print key information (clearly showing Fanout broadcast feature: same message received by multiple queues)
|
||
print(f"[Fanout Consumer {consumer_id}] Received broadcast message:")
|
||
print(f" Listening queue: {queue_name}")
|
||
print(f" Message content: {message_content}")
|
||
print(f" Message persistence: {'Yes' if message.delivery_mode == 2 else 'No'}") # Verify persistence
|
||
# print(f" 处理时间:{asyncio.get_running_loop().time():.2f}s\n")
|
||
|
||
# 模拟业务处理耗时(根据实际场景替换,如日志存储、通知推送等)
|
||
await asyncio.sleep(1)
|
||
|
||
# 6. 启动队列监听:将处理逻辑绑定到队列,持续接收广播消息
|
||
consumer_tag = f"fanout_consumer_{consumer_id}_{queue_name}"
|
||
await queue.consume(on_message_received, consumer_tag=consumer_tag)
|
||
# Print startup log, indicating consumer is ready
|
||
print(f"[Fanout Consumer {consumer_id}] Started, listening to queue: {queue_name} (tag: {consumer_tag})\n")
|
||
|
||
# 7. 保持消费者运行(无限期阻塞,直到手动停止程序)
|
||
# 若不阻塞,协程会立即结束,消费者连接会断开
|
||
await asyncio.Future()
|
||
|
||
|
||
async def start_all_fanout_consumers(queue_prefix="demo.fanout.queue-", queue_count=3):
|
||
"""
|
||
启动所有 Fanout 消费者:与生产者 setup 中创建的 3 个队列一一对应
|
||
:param queue_prefix: 队列名称前缀(与生产者 queue_name_prefix 一致)
|
||
:param queue_count: 队列数量(与生产者中 range(3) 对应,默认 3 个)
|
||
"""
|
||
# 构建消费者任务列表:为每个队列创建一个独立消费者
|
||
consumer_tasks = [
|
||
fanout_consumer(
|
||
queue_name=f"{queue_prefix}{i}", # 队列名:demo.fanout.queue-0/1/2(与生产者一致)
|
||
consumer_id=i + 1 # 消费者标识:1/2/3
|
||
)
|
||
for i in range(queue_count)
|
||
]
|
||
|
||
# 并发启动所有消费者(3 个消费者同时运行,互不阻塞)
|
||
await asyncio.gather(*consumer_tasks)
|
||
|
||
|
||
if __name__ == "__main__":
|
||
# 启动所有 Fanout 消费者(需先执行 setup_fanout_exchange 初始化交换器和队列)
|
||
asyncio.run(start_all_fanout_consumers())
|