目录
[Python 连接 RabbitMQ 的三种方案](#Python 连接 RabbitMQ 的三种方案)
[一、三大 RabbitMQ 客户端方案概览](#一、三大 RabbitMQ 客户端方案概览)
[1. aio-pika(asyncio 异步客户端)](#1. aio-pika(asyncio 异步客户端))
[2. pika(官方主流客户端)](#2. pika(官方主流客户端))
[3. kombu(Celery 背后的通信库)](#3. kombu(Celery 背后的通信库))
[三、线程 / 进程支持能力对比(重点)](#三、线程 / 进程支持能力对比(重点))
[四、三种 RabbitMQ 客户端的 class 封装示例](#四、三种 RabbitMQ 客户端的 class 封装示例)
[五、pytest 单测示例](#五、pytest 单测示例)
[六、最终选型建议(综合功能 + 线程/进程 + gevent)](#六、最终选型建议(综合功能 + 线程/进程 + gevent))
Python 连接 RabbitMQ 的三种方案
aio-pika、pika、kombu 的完整对比、线程进程模型、客户端封装与单测示例
- aio-pika / pika / kombu 三大方案介绍
- 场景对比 + 性能对比 + gevent 兼容性
- 多线程 / 多进程支持比较
- 三种客户端的 class 封装示例
- pytest 用例
一、三大 RabbitMQ 客户端方案概览
| 客户端 | 编程模型 | 是否支持 gevent | 适用场景 | 典型优势 |
|---|---|---|---|---|
| aio-pika | asyncio 异步 | 一般 | 高并发异步系统(FastAPI) | 高性能、自动重连 |
| pika | 同步 | 差 | 同步脚本、小工具 | 轻量、直接 AMQP |
| kombu | 抽象层(同步/异步) | 好 | 企业级、复杂架构、gevent | 连接池、多 broker、稳定 |
二、三者功能与使用场景对比
1. aio-pika(asyncio 异步客户端)
特点:
- 基于 asyncio,天然适合高并发
- connect_robust 自动重连
- 适合现代 async 应用,如 FastAPI、异步消费者
适用场景:
- 需要异步模型
- 高吞吐、I/O 密集型场景
- 拒绝 gevent,采用 asyncio
2. pika(官方主流客户端)
特点:
- 同步阻塞型库(BlockingConnection)
- 底层、轻量、性能高
- 与 gevent 不兼容(socket 阻塞)
适用场景:
- 简单同步脚本
- 工具类服务
- 单进程小型消费者生产者
3. kombu(Celery 背后的通信库)
特点:
- 高抽象层:支持 RabbitMQ / Redis / SQS
- 支持连接池、自动重连、多序列化格式
- 与 gevent/asyncio 都能共存
- Celery 的官方通信层
适用场景:
- 企业级应用、复杂 MQ 流程
- gevent 生态
- 多线程、多进程混用场景
- 多 broker 需求
三、线程 / 进程支持能力对比(重点)
| 特性 | aio-pika | pika | kombu |
|---|---|---|---|
| 多线程支持 | 差(不建议) | 一般(每线程独立连接) | 好(连接池友好) |
| 线程共享 Connection | 不支持 | 不支持 | 不建议但可行(Producer 线程安全) |
| 多进程支持 | 强(async worker 可扩展) | 强 | 非常强(Celery 级别) |
| 适合 gevent 环境 | 一般 | 差 | 最佳 |
结论:
- 多线程:kombu > pika > aio-pika
- 多进程:三者都支持,但 kombu 最成熟(Celery 模型)
四、三种 RabbitMQ 客户端的 class 封装示例
from typing import Union, Dict, Any
import aio_pika
import pika
from kombu import Connection, Exchange, Queue, Consumer
from utils.log_utils import dj_logger
# 前置:
# uv add pika aio-pika kombu pytest-asyncio 或者 pip install pika aio-pika kombu pytest-asyncio
class PikaRabbitMQClient:
"""
单个 RabbitMQ 客户端,负责建立连接、发送消息、重连等
"""
def __init__(self, host, port, username, password, virtual_host='/'):
"""
初始化连接参数并建立连接
:param host: RabbitMQ 服务器地址
:param port: RabbitMQ 端口号
:param username: 用户名
:param password: 密码
:param virtual_host: 虚拟主机名,默认是 '/'
"""
self.host = host
self.port = port
self.username = username
self.password = password
self.connection = None
self.channel = None
self.virtual_host = virtual_host
self._connect()
def _connect(self):
"""建立 RabbitMQ 连接"""
try:
# 使用 pika 提供的 PlainCredentials 来传入用户名和密码
credentials = pika.PlainCredentials(self.username, self.password)
# 设置连接参数
params = pika.ConnectionParameters(
host=self.host,
port=self.port,
credentials=credentials,
virtual_host=self.virtual_host,
heartbeat=60, # 连接保持活跃的时间(秒)
blocked_connection_timeout=5 # 如果连接被阻塞,超时前的等待时间(秒)
)
# 建立连接
self.connection = pika.BlockingConnection(params)
self.channel = self.connection.channel()
# 开启发布确认模式,确保消息到达 RabbitMQ 服务器
self.channel.confirm_delivery()
except Exception as e:
# 连接失败时抛出异常
raise Exception(f"RabbitMQ 连接失败: {e}")
def reconnect(self):
"""连接断开后自动重连"""
try:
if self.connection and self.connection.is_open:
return
self._connect()
except Exception as e:
raise Exception(f"RabbitMQ 重连失败: {e}")
def publish(self, exchange, queue, routing_key, message: str, exchange_type='direct'):
"""
发送消息
:param exchange: RabbitMQ 交换机
:param queue: 队列名
:param routing_key: 路由键
:param message: 要发送的消息(字符串类型)
:param exchange_type: 交换机类型(默认 direct)
"""
try:
# 如果连接已经关闭,尝试重连
if not self.connection or self.connection.is_closed:
self.reconnect()
# 声明交换机(确保交换机已创建)
self.channel.exchange_declare(exchange=exchange, exchange_type=exchange_type, durable=True)
# 声明队列并绑定到交换机,队列名通常与路由键相同,或者根据业务逻辑进行命名
self.channel.queue_declare(queue=queue, durable=True) # 确保队列是持久化的
self.channel.queue_bind(
exchange=exchange,
queue=queue,
routing_key=routing_key # 使用路由键绑定队列
)
# 发布消息
self.channel.basic_publish(
exchange=exchange,
routing_key=routing_key,
body=message,
mandatory=True # 确保消息能够到达至少一个队列
)
except Exception as e:
# 如果发送消息失败,尝试重连一次
self.reconnect()
dj_logger.error(f"首次消息发送失败: {e}")
try:
# 重连后再次声明交换机并发送消息
self.channel.exchange_declare(exchange=exchange, exchange_type='topic', durable=True)
self.channel.basic_publish(
exchange=exchange,
routing_key=routing_key,
body=message,
mandatory=True
)
except Exception as e:
# 如果重试仍然失败,抛出异常
raise Exception(f"消息发送失败: {e}")
class AioPikaClient:
"""基于 aio-pika 的 RabbitMQ 异步客户端,支持动态交换机与路由键"""
def __init__(self, host: str, port: int, username: str, password: str, vhost: str = "/"):
# 基础连接参数
self.host = host
self.port = port
self.username = username
self.password = password
self.vhost = vhost
self.connection = None
self.channel = None
async def connect(self):
"""建立连接并创建通道"""
self.connection = await aio_pika.connect_robust(
host=self.host,
port=self.port,
login=self.username,
password=self.password,
virtualhost=self.vhost,
)
self.channel = await self.connection.channel()
async def publish(self, exchange: str, routing_key: str, message: str, queue: str,
exchange_type: str = "direct", declare_queue: bool = True):
"""
发布消息到指定交换机
:param exchange: 交换机名称
:param queue: 队列名
:param routing_key: 路由键
:param message: 消息内容(字符串)
:param exchange_type: 交换机类型(默认 direct)
:param declare_queue: 是否自动声明 queue 并绑定(可选)
"""
# 声明交换机(如果不存在则创建)
exchange = await self.channel.declare_exchange(
exchange,
type=exchange_type,
durable=True
)
# 可选:声明队列并绑定(用于测试或确保 routing_key 有绑定队列)
if declare_queue:
queue = await self.channel.declare_queue(queue, durable=True)
await queue.bind(exchange, routing_key)
# 发布消息
await exchange.publish(
aio_pika.Message(body=message.encode("utf-8")),
routing_key=routing_key
)
async def consume(self, queue_name: str, callback):
"""
消费指定队列的消息
:param queue_name: 队列名
:param callback: 处理消息的回调函数,接受 str 入参
"""
queue = await self.channel.declare_queue(queue_name, durable=True)
async with queue.iterator() as queue_iter:
async for msg in queue_iter:
async with msg.process():
await callback(msg.body.decode("utf-8"))
async def close(self):
"""关闭连接"""
if self.connection:
await self.connection.close()
class KombuClient:
"""
Kombu客户端类,支持动态配置连接参数和交换机选择
"""
def __init__(self, host: str = 'localhost', port: int = 5672,
username: str = 'guest', password: str = 'guest',
virtual_host: str = '/', transport: str = None, transport_options=None):
"""
初始化Kombu客户端
Args:
host: RabbitMQ服务器地址
port: 端口号
username: 用户名
password: 密码
virtual_host: 虚拟主机
transport: 传输协议
transport_options: 传输协议选项,格式为字典,可选参数如下:
1. 高可用生产环境
transport_options = {
'socket_timeout': 30.0, # 较长的超时时间
'socket_keepalive': True, # 启用keep-alive
'socket_keepalive_idle': 60, # 1分钟后开始检测
'socket_keepalive_intvl': 10, # 每10秒检测一次
'socket_keepalive_cnt': 3, # 连续3次失败断开
'confirm_publish': True, # 确保消息可靠性
'max_retries': 5, # 更多重试次数
}
2. 实时响应系统
transport_options = {
'socket_timeout': 5.0, # 快速超时
'socket_keepalive': True,
'socket_keepalive_idle': 30, # 快速检测
'socket_keepalive_intvl': 5, # 频繁检测
'confirm_publish': True,
'max_retries': 2, # 快速失败
}
3. 网络不稳定环境
transport_options = {
'socket_timeout': 60.0, # 更长的等待时间
'socket_keepalive': True,
'socket_keepalive_idle': 120, # 较保守的检测
'socket_keepalive_intvl': 30,
'socket_keepalive_cnt': 5, # 更多容错次数
'max_retries': 10, # 更多重试
'interval_max': 2.0, # 更长的重试间隔
}
"""
self.transport_options = transport_options or {
'socket_timeout': 5.0, # 快速超时
'socket_keepalive': True,
'socket_keepalive_idle': 30, # 快速检测
'socket_keepalive_intvl': 5, # 频繁检测
'confirm_publish': True,
'max_retries': 2, # 快速失败
}
self.connection_params = {
'hostname': host,
'port': port,
'userid': username,
'password': password,
'virtual_host': virtual_host,
'transport': transport,
'transport_options': self.transport_options
}
self._connection = None
self._exchanges = {} # 缓存交换机对象
self._queues = {} # 缓存队列对象
@property
def connection(self) -> Connection:
"""获取连接对象"""
if self._connection is None or not self._connection.connected:
self._connection = Connection(**self.connection_params)
return self._connection
def create_exchange(self, name: str, type_: str = 'direct',
durable: bool = True, auto_delete: bool = False,
**kwargs) -> Exchange:
"""
创建或获取交换机
Args:
name: 交换机名称
type_: 交换机类型 (direct, topic, fanout, headers)
durable: 是否持久化
auto_delete: 是否自动删除
**kwargs: 其他交换机参数
Returns:
Exchange对象
"""
if name not in self._exchanges:
self._exchanges[name] = Exchange(
name=name,
type=type_,
durable=durable,
auto_delete=auto_delete,
**kwargs
)
dj_logger.info(f"Created exchange: {name} (type: {type_})")
return self._exchanges[name]
def create_queue(self, name: str, exchange: str,
routing_key: str = '', durable: bool = True,
auto_delete: bool = False, **kwargs) -> Queue:
"""
创建并绑定队列
"""
if name not in self._queues:
# 先确保交换机存在
if exchange not in self._exchanges:
raise ValueError(f"Exchange '{exchange}' does not exist. Please create it first.")
exchange_obj = self._exchanges[exchange]
# 创建队列并绑定交换机
q = Queue(
name=name,
exchange=exchange_obj,
routing_key=routing_key,
durable=durable,
auto_delete=auto_delete,
**kwargs
)
self._queues[name] = q
dj_logger.info(f"Created queue '{name}' bound to exchange '{exchange}'")
return self._queues[name]
def publish(self, message: Union[str, dict], exchange: str, queue: str,
routing_key: str = '', headers: Dict[str, Any] = None, type_: str = 'direct', **kwargs):
"""
发送消息到指定交换机
Args:
message: 要发送的消息
exchange: 交换机名称
queue: 队列名称
routing_key: 路由键
headers: 消息头
type_: 交换机类型
**kwargs: 其他发送参数
"""
if exchange not in self._exchanges:
self.create_exchange(name=exchange, type_=type_)
self.create_queue(name=queue, exchange=exchange, routing_key=routing_key)
self.declare_topology()
exchange = self._exchanges[exchange]
try:
with self.connection:
with self.connection.Producer() as producer:
producer.publish(
message,
exchange=exchange,
routing_key=routing_key,
headers=headers or {},
**kwargs
)
dj_logger.info(f"Message sent to exchange '{exchange}' with routing_key '{routing_key}'")
except Exception as e:
dj_logger.error(f"Failed to send message: {e}")
raise
def consume_messages(self, queue_name: str, callback_func,
auto_ack: bool = True, prefetch_count: int = 1):
"""
消费队列中的消息
Args:
queue_name: 队列名称
callback_func: 回调函数
auto_ack: 是否自动确认
prefetch_count: 预取数量
"""
if queue_name not in self._queues:
raise ValueError(f"Queue '{queue_name}' not found. Please create it first.")
queue = self._queues[queue_name]
def process_message(body, message):
try:
callback_func(body, message)
if not auto_ack:
message.ack()
except Exception as se:
dj_logger.error(f"Error processing message: {se}")
if not auto_ack:
message.reject()
try:
with self.connection:
# 设置预取数量
self.connection.default_channel.basic_qos(prefetch_count=prefetch_count)
with Consumer(self.connection, [queue], callbacks=[process_message],
auto_declare=True, accept=['json', 'pickle']):
dj_logger.info(f"Started consuming from queue: {queue_name}")
while True:
try:
self.connection.drain_events(timeout=1.0)
except KeyboardInterrupt:
dj_logger.info("Consumer stopped by user")
break
except Exception as e:
dj_logger.error(f"Failed to consume messages: {e}")
raise
def declare_topology(self, with_connection=None):
"""
声明所有交换机和队列
Args:
with_connection: 可选的连接对象
"""
conn = with_connection or self.connection
with conn:
# 声明所有交换机
for exchange in self._exchanges.values():
exchange.declare(channel=conn.default_channel)
dj_logger.info(f"Declared exchange: {exchange.name}")
# 声明所有队列
for queue in self._queues.values():
queue.declare(channel=conn.default_channel)
dj_logger.info(f"Declared queue: {queue.name}")
def get_connection_info(self) -> Dict[str, Any]:
"""获取当前连接信息"""
return self.connection_params.copy()
def test_connection(self) -> bool:
"""测试连接是否正常"""
try:
with self.connection:
return self.connection.connected
except Exception as e:
dj_logger.error(f"Connection test failed: {e}")
return False
def close(self):
"""关闭连接"""
if self._connection and self._connection.connected:
self._connection.close()
dj_logger.info("Connection closed")
五、pytest 单测示例

import pytest
from utils.xu_box.rabbitmq_client import PikaRabbitMQClient, AioPikaClient, KombuClient
def test_pika_connect():
PikaRabbitMQClient(host='192.168.252.121', port=5672, username='test', password='admin123')
@pytest.mark.asyncio
async def test_aio_pika_connect():
AioPikaClient(host='192.168.252.121', port=5672, username='test', password='admin123')
def test_kombu_connect():
KombuClient(host='192.168.252.121', port=5672, username='test', password='admin123')
六、最终选型建议(综合功能 + 线程/进程 + gevent)
| 场景 | 最佳选择 |
|---|---|
| 高并发 async(FastAPI、异步微服务) | aio-pika |
| gevent 环境(Locust 压测、gevent worker) | kombu |
| 多线程环境需要连接池 | kombu |
| 多进程 worker(消费者集群) | kombu / pika |
| 简单同步脚本、工具程序 | pika |
| 需要支持多个 broker | kombu |