「消息队列」Python使用pika操作RabbitMQ

转载请注明出处: https://blog.csdn.net/jinixin/article/details/84193745

 

 

前面几篇文章分别介绍了消息队列, AMQP协议以及RabbitMQ, 本篇文章开始尝试用具体语言操作RabbitMQ来搭建"生产者-消费者"模型. 本篇文章将使用Python语言, 而Python操作RabbitMQ库有很多, 比如pika, amqp以及Celery等, 下面就使用pika库来完成这个任务.

 

想要查看pika的官方文档, 点击这里, 下面直接抛出代码:

# coding=utf-8

import time
import pika
from threading import Thread


class MQBase(object):
    """ 消息队列基类 """

    def __init__(self, host, port, exchange, exchange_type, ack=True, persist=True):
        self.conn = None  # 连接
        self.channel = None  # 信道

        self.host = host  # 主机
        self.port = port  # 端口
        self.exchange = exchange  # 交换机名称
        self.exchange_type = exchange_type  # 交换机类型
        self.ack = ack  # 是否开启消息手动确认机制, 默认是自动确认机制
        self.persist = persist  # 消息是否持久化
        self.param = pika.ConnectionParameters(host=self.host, port=self.port)  # 转换为连接参数

    def _open_channel(self):
        """ 建立连接并开辟信道 """

        self.conn = pika.BlockingConnection(self.param)  # 建立连接
        self.channel = self.conn.channel()  # 在连接中开辟信道

        self.channel.exchange_declare(exchange=self.exchange,  # 交换机名称
                                      exchange_type=self.exchange_type,  # 交换机类型
                                      durable=self.persist)  # 交换机是否持久化; 该方法用于声明交换机, 声明多次仅会创建一次
        if self.ack:
            self.channel.confirm_delivery()  # 在该信道中开启消息手动确认机制

    def _close_channel(self):
        """ 关闭信道并断开连接 """

        if self.channel and self.channel.is_open:  # 检测信道是否还存活
            self.channel.close()  # 关闭信道

        if self.conn and self.conn.is_open:  # 检测连接是否还存活
            self.conn.close()  # 断开连接


class MQSender(MQBase):
    """ 消息队列-生产者 """

    def send(self, route, msg):
        self._open_channel()

        properties = pika.BasicProperties(delivery_mode=(2 if self.persist else 0))  # delivery_mode为2时表示消息持久化, 其他值时非持久化
        self.channel.confirm_delivery()  # 开启消息送达确认(注意这里是送达消息队列即可)
        ret = self.channel.basic_publish(exchange=self.exchange,  # 指定发送到的交换机
                                         routing_key=route,  # 消息中的路由键
                                         body=msg,  # 消息中的有效载荷
                                         properties=properties)  # 该方法用于发送消息, 消息成功送达消息队列时返回True, 否则返回False
        self._close_channel()
        return ret


class MQReceiver1(MQBase):
    """ 消息队列-消费者1 """

    def _declare_queue(self, queue_name):
        """ 声明队列 """

        self.channel.queue_declare(queue=queue_name,  # 队列名称
                                   durable=self.persist)  # 队列是都否持久化; 该方法用于声明队列, 声明多次仅会创建一次

        self.channel.queue_bind(queue=queue_name,  # 队列
                                exchange=self.exchange,  # 交换机
                                routing_key=queue_name)  # 绑定键, 该方法用于将队列通过绑定键绑定到交换机, 之后交换机会将对应路由键的消息转发到该队列上

    def _subscribe_queue(self, queue_name):
        """ 订阅队列 """

        self._declare_queue(queue_name)
        self.channel.basic_qos(prefetch_count=1)  # 该方法起到负载均衡的作用, 表明一次只接受prefetch_count条信息, 直到消息确认后再接收新的
        self.channel.basic_consume(consumer_callback=self.handler,  # 收到消息后的回调方法, 即消息处理器
                                   queue=queue_name,  # 订阅的队列名称
                                   no_ack=not self.ack)  # 是否不使用手动消息确认机制; 该方法用于订阅队列, 并分配消息处理器

    def start(self, queue, func):
        """ 开始消费, 以回调方式 """

        self.func = func  # 真正的消息处理器

        self._open_channel()
        self._subscribe_queue(queue)
        self.channel.start_consuming()  # 该方法用于开始消费消息队列中的消息, 会阻塞住的. 该方法虽然是由channel调用的, 一个连接下有多个channel, 但一个连接只能调用一次该方法

    def end(self):
        """ 结束消费 """

        self.channel.stop_consuming()  # 该方法用于停止消费
        self._close_channel()

    def handler(self, channel, method_frame, header_frame, body):
        """ 收到消息后的回调方法, 即消息处理器 """

        # from pprint import pprint
        # pprint(method_frame), pprint(header_frame)  # 可以通过pprint打印出对象的内容

        self.func(body)  # 调用真正的消息处理器

        if self.ack:
            channel.basic_ack(delivery_tag=method_frame.delivery_tag)  # 手动确认消息已被成功处理


class MQReceiver2(MQBase):
    """ 消息队列-消费者2 """

    def start(self, queue, func):
        """ 开始消费, 以迭代方式 """

        self._open_channel()
        for method_frame, properties, msg in self.channel.consume(queue=queue,  # 队列名称
                                                                  no_ack=not self.ack):  # 是否不使用手动消息确认机制; 该方法用于消费消息队列中的消息
            func(msg)  # 调用消息的处理器
            if self.ack:
                self.channel.basic_ack(delivery_tag=method_frame.delivery_tag)  # 手动确认消息已被成功处理

    def end(self):
        """ 结束消费 """

        self.channel.cancel()

 

调用方法和参数的含义大多已通过注释给出. 针对消费者的实现, 我提供了两种方式: MQReceiver1通过回调来处理消息, MQReceiver2通过迭代来处理消息. 当RabbitMQ中没有消息时, 两种方式都会阻塞住, 代码所到达的结果是一致的, 只是迭代看上去比回调节省了很多代码.

通过下面代码, 就可以使得程序运行起来.

def producer_handler():
    for msg in range(18):
        print ('producer send %s' % msg)

        sender = MQSender(host='localhost',
                          port=5680,
                          exchange='test_exchange',
                          exchange_type='direct',
                          ack=True,
                          persist=True)

        sender.send('test_queue', str(msg))


def consumer_handler(num):

    def func(msg):
        """ 真正的消息处理器 """
        print ('consumer_%s handling message: %s, sleep: %s' % (num, msg, int(msg) % 10))
        time.sleep(int(msg) % 10)

    receiver = MQReceiver1(host='localhost',
                           port=5680,
                           exchange='test_exchange',
                           exchange_type='direct',
                           ack=True,
                           persist=True)

    receiver.start('test_queue', func)


def main():
    for i in range(3):
        Thread(target=consumer_handler, args=(i + 1,)).start()  # 消费者

    Thread(target=producer_handler).start()  # 生产者


if __name__ == '__main__':
    main()

 

运行结果如下:

运行结果

 

结合代码和运行结果可以看出, 程序是一个生产者生产消息, 三个消费者消费消息, 消费者是负载均衡的. 我们很方便的就可以把上面的代码扩展到多台计算机上, 但这样做有不妥之处.

虽然RabbitMQ的连接下有多个信道, 支持多个消费者(线程)共享一个连接, 每个消费者(线程)独享一个信道. 但pika是线程不安全的, 所以一个消费者(线程)需要独享一个连接. 当有大量消费者时, 就隐隐有些忧患. 具体的问题与优化, 将在下篇文章中给出.
 

 

文中如有不当之处, 还望包容和指出, 感谢.

 

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值