Python连接RabbitMQ三大方案全解析

目录

[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
相关推荐
海梨花1 小时前
又是秒杀又是高并发,你的接口真的扛得住吗?
java·后端·jmeter
代码雕刻家1 小时前
C语言的左对齐符号-
c语言·开发语言
小肖爱笑不爱笑1 小时前
2025/11/19 网络编程
java·运维·服务器·开发语言·计算机网络
Livingbody1 小时前
win11上wsl本地安装版本ubuntu25.10
后端
郑州光合科技余经理2 小时前
开发指南:海外版外卖跑腿系统源码解析与定制
java·开发语言·mysql·spring cloud·uni-app·php·深度优先
用户8356290780512 小时前
如何在 C# 中自动化生成 PDF 表格
后端·c#
星释2 小时前
Rust 练习册 44:Trait 中的同名函数调用
开发语言·后端·rust
京东零售技术2 小时前
并发丢数据深度剖析:JED的锁机制与事务实战踩坑及解决方案
后端
fanruitian2 小时前
Java 静态代码块
java·开发语言