rabbitmq-test/reliable_mq/dead_letter_consumer.py
2025-09-07 10:35:24 +08:00

129 lines
4.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
RabbitMQ 死信队列处理模块
"""
import asyncio
import aio_pika
import json
import logging
from datetime import datetime
from typing import Dict, Any
from .config import config
logger = logging.getLogger(__name__)
class DeadLetterConsumer:
"""死信队列消费者"""
def __init__(self):
"""初始化死信队列消费者"""
self.connection = None
self.channel = None
self.dead_letter_queue = None
async def connect(self):
"""建立连接"""
try:
connection_config = config.get_connection_config()
self.connection = await aio_pika.connect_robust(connection_config['uri'])
self.channel = await self.connection.channel()
await self.channel.set_qos(prefetch_count=connection_config['prefetch_count'])
# 声明死信队列
dead_letter_config = config.get_dead_letter_config()
self.dead_letter_queue = await self.channel.declare_queue(
dead_letter_config['dead_letter_queue'],
durable=True,
auto_delete=False
)
logger.info("[死信消费者] 已连接")
except Exception as e:
logger.error(f"[死信消费者] 连接失败: {e}")
raise
async def process_dead_letter_message(self, message: aio_pika.IncomingMessage):
"""处理死信消息"""
try:
# 解析死信消息
dead_letter_data = json.loads(message.body.decode('utf-8'))
original_message = dead_letter_data.get('original_message', {})
error_info = dead_letter_data.get('error_info', 'Unknown')
message_id = dead_letter_data.get('message_id', 'Unknown')
# 打印死信消息信息
logger.error("=" * 50)
logger.error("[死信消费者] 收到死信消息:")
logger.error(f"[死信消费者] 消息ID: {message_id}")
logger.error(f"[死信消费者] 消息内容: {json.dumps(original_message, ensure_ascii=False, indent=2)}")
logger.error(f"[死信消费者] 错误原因: {error_info}")
logger.error("=" * 50)
# 保存到数据库
await self.save_to_database(original_message, error_info, message_id)
# 确认死信消息
await message.ack()
logger.info(f"[死信消费者] 死信消息 {message_id} 处理完成")
except Exception as e:
logger.error(f"[死信消费者] 处理死信消息失败: {e}")
await message.nack(requeue=False) # 拒绝重新入队,避免一致失败出现的死循环
async def save_to_database(self, original_message: Dict[str, Any], error_info: str, message_id: str):
"""保存死信消息到数据库"""
# 模拟数据库保存操作
await asyncio.sleep(0.5)
# 构建数据库记录
db_record = {
'id': message_id,
'original_message': original_message,
'error_info': error_info,
'created_at': datetime.now().isoformat(),
'status': 'failed'
}
logger.info(f"[死信消费者] 💾 死信消息已保存到数据库: {message_id}")
logger.info(f"[死信消费者] 数据库记录: {json.dumps(db_record, ensure_ascii=False, indent=2)}")
# 这里可以添加实际的数据库操作
# 例如await database.insert('dead_letter_messages', db_record)
async def start_consuming(self):
"""开始消费死信消息"""
self.consumer_tag = await self.dead_letter_queue.consume(self.process_dead_letter_message)
logger.info("[死信消费者] 开始消费死信消息...")
# 保持消费者运行
await asyncio.Future()
async def stop_consuming(self):
"""停止消费死信消息"""
if self.dead_letter_queue and self.consumer_tag:
await self.dead_letter_queue.cancel(self.consumer_tag)
logger.info("[死信消费者] 已停止消费死信消息")
async def close(self):
"""关闭连接"""
try:
await self.stop_consuming()
if self.connection:
await self.connection.close()
logger.info("[死信消费者] 连接已关闭")
except Exception as e:
logger.error(f"[死信消费者] 关闭连接时出错: {e}")
async def __aenter__(self):
"""异步上下文管理器入口"""
await self.connect()
return self
async def __aexit__(self, exc_type, exc_val, exc_tb):
"""异步上下文管理器出口"""
await self.close()