rabbitmq,pika二次简易封装

Rabbitmq

基本信息

  • 实现了高级消息队列协议(AMQP)的开源消息代理中间件
  • 启动停止:rabbitmqctl start_app ; rabbitmqctl stop
    • 后台启动: rabbitmq-server -detached
    • 如果不启动RabbitMQ 管理插件,是无法通过Web访问的:rabbitmq-plugins enable rabbitmq_management
    • http://127.0.0.1:15672/ 查看消息队列 (account:guest;pwd:guest)
  • RabbitMQ中所有的消息都要先通过交换机,空字符串表示使用默认的交换机
  • rabbitmq服务挂了,那么消息队列里的消息将会全部丢失:声明队列为可持久化存储队列,并且在生产者将消息插入到消息队列时,设置消息持久化存储
    • channel.queue_declare(queue=self.qname,durable=True)
    • channel.basic_publish(exchange='', routing_key=qname, body=body, properties=pika.BasicProperties(delivery_mode=2))
  • 如果在消费者获取到队列里的消息后,在回调函数的处理过程中,消费者突然出错或程序崩溃等异常,那么就会造成这条消息并未被实际正常的处理掉。
    • 为了解决这个问题,我们只需在消费者basic_consume(auto_ack=False);
    • 并在回调函数中设置手动应答即可channel.basic_ack(delivery_tag=method.delivery_tag)
  • 一个生产者和多个消费者,即多个消费者同时监听一个消息队列,这时候队列里的消息就是轮询分发(即如果消息队列里有100条信息,如果有2个消费者,那么每个就会收到50条信息),但是在某些情况下,不同的消费者处理任务的能力是不同的,这时还按照轮询的方式分发消息并不是很合理,那么只需要再配合手动应答的方式,设置消费者接收的消息没有处理完,队列就不要给我放送新的消息即可
    • channel.basic_qos(prefetch_count=1) 限制每次只发送不超过1条消息到同一个消费者,消费者必须手动反馈告知队列,才会发送下一个
    • 必须在 channel.basic_consume 之上,否则不生效
  • 一些RabbitMQ客户端(Bunny,Java,.NET,Objective-C,Swift)提供了一种在网络故障后自动恢复连接的机制,而pika只能通过检测连接异常后再重新创建连接的方式

模板

import random
import time
import pika
from loguru import logger
from functools import wraps
from pika.exceptions import AMQPConnectionError, ChannelClosedByBroker, ConnectionClosedByBroker, ChannelWrongStateError
from apscheduler.schedulers.blocking import BlockingScheduler
from apscheduler.triggers.cron import CronTrigger


class RabbitmqPackage:
    def __init__(self, user, pwd, host):
        self.user, self.pwd = user, pwd
        self.host = host
        self.flow_control = "NORMAL"

    def retry2(self):
        def decorator(func):
            @wraps(func)
            def wrapper(*args, **kwargs):
                while True:
                    try:
                        return func(*args, **kwargs)
                    except (AMQPConnectionError, ChannelClosedByBroker, ConnectionClosedByBroker, ChannelWrongStateError) as e:
                        logger.error(e)
                        if self.flow_control == "NORMAL":
                            self.init_connection()  # 开始重连
                        else:
                            print("主动强制断开链接")
                            return False
                    except Exception as e:
                        logger.error(e)
                        return False
                    time.sleep(random.randint(3, 5))

            return wrapper

        return decorator

    def change_flow_control(self, status="STOP"):
        self.flow_control = status
        logger.info(f"将当前状态改为:{status}")
        if self.flow_control == "STOP" and hasattr(self, "connection"):
            self.channel.close()
            self.connection.close()
            print("链接通道关闭")
            # raise ValueError("程序退出")
        return True

    def init_connection(self):  # 创建消息队列链接
        while True:
            try:
                print(f"开始链接到{self.host}")
                credentials = pika.PlainCredentials(self.user, self.pwd)
                connection = pika.BlockingConnection(
                    pika.ConnectionParameters(socket_timeout=10, host=self.host, port=5672, virtual_host='/',
                                              credentials=credentials, heartbeat=20))  # 心跳包20秒发一次
                channel = connection.channel()
                return connection, channel
            except pika.exceptions.AMQPConnectionError:
                logger.error("链接错误")
            except Exception as e:
                logger.error("其他错误")
            time.sleep(5)

    def send_msg(self, qname, body):  # 发送消息
        if not hasattr(self, "connection"):
            self.connection, self.channel = self.init_connection()
        if self.connection.is_open and self.channel.is_open:
            self.channel.queue_declare(queue=qname,
                                       durable=True)  # 如果指定的queue不存在,则会创建一个queue,如果已经存在 则不会做其他动作,官方推荐,每次使用时都可以加上这句
            self.channel.basic_publish(exchange='', routing_key=qname, body=body,
                                       properties=pika.BasicProperties(delivery_mode=2))
        else:
            self.connection, self.channel = self.init_connection()  # 重新链接

    @staticmethod
    def disconnect(connection: pika.BlockingConnection, channel: pika.adapters.blocking_connection.BlockingChannel):
        channel.close()
        connection.close()

    def init_func(self, qname):  # 初始化获取工具函数和消息队列
        def get_name(func):
            self.func = func
            self.qname = qname

        return get_name

    def callback(self, channel, method, properties, body):
        # channel: 包含channel的一切属性和方法
        # method: 包含 consumer_tag, delivery_tag, exchange, redelivered, routing_key
        # properties: basic_publish 通过 properties 传入的参数
        # body: basic_publish发送的消息
        if hasattr(self, "func"):
            status = self.func(body)  # 执行自己的目标函数
            if status:  # 确定是否消费
                channel.basic_ack(delivery_tag=method.delivery_tag)  # 手动确定消息消费完成
            else:
                channel.basic_nack(delivery_tag=method.delivery_tag, requeue=True)  # 将消息重新放回队列
        else:
            print("执行函数未定义")
        return True

    @property
    def listen(self):
        @self.retry2()
        def inner_method():
            self.connection, self.channel = self.init_connection()  # 初始化链接
            if hasattr(self, "qname"):
                self.channel.queue_declare(
                    queue=self.qname,
                    durable=True)  # 如果指定的queue不存在,则会创建一个queue,如果已经存在 则不会做其他动作,生产者和消费者都做这一步的好处是这样生产者和消费者就没有必要的先后启动顺序了
                logger.info("开始启动监听")
                self.channel.basic_qos(
                    prefetch_count=1)  # 还有就是这个设置必须在basic_consume之上,否则不生效;prefetch_count表示接收的消息数量,当我接收的消息没有处理完(用basic_ack标记消息已处理完毕)之前不会再接收新的消息了
                self.channel.basic_consume(queue=self.qname,  # 接收指定queue的消息
                                      auto_ack=False,  # 指定为True,表示消息接收到后自动给消息发送方回复确认,已收到消息;指定为False,表示取消自动应答,交由回调函数手动应答
                                      on_message_callback=self.callback  # 设置收到消息的回调函数,每次接收到消息后调用
                                      )
                self.channel.start_consuming()  # 一直处于等待接收消息的状态,如果没收到消息就一直处于阻塞状态,收到消息就调用上面的回调函数
            else:
                print("队列名称未定义")

        return inner_method


if __name__ == '__main__':
    mq = RabbitmqPackage("guest", "guest", "127.0.0.1")
    @mq.init_func(qname="hello2")
    def task(body):
        print(f"任务函数{body}")
        time.sleep(5)
        return True  # 返回True正常消费


    # mq.listen()
    def task_2():
        mq.change_flow_control(status="NORMAL")
        mq.listen()


    scheduler = BlockingScheduler(timezone='Asia/Shanghai')
    trigger1 = CronTrigger(second="15")  # 每秒触发
    scheduler.add_job(task_2, trigger=trigger1, coalesce=True)

    trigger2 = CronTrigger(second="45")  # 每秒触发
    scheduler.add_job(mq.change_flow_control, trigger=trigger2, coalesce=True)

    # 启动调度器
    scheduler.start()
    # for i in range(50):
    #     mq.send_msg(qname="hello2", body=str(random.randint(1, 5)))

"""
AMQPConnectionError: 这个异常表示与AMQP(Advanced Message Queuing Protocol)服务器的连接问题。可能的原因包括网络问题、服务器不可用或者认证失败等
ChannelClosedByBroker:这个异常表示由broker(消息代理服务器,例如RabbitMQ)主动关闭了通道。可能的原因包括试图执行一个非法操作或者通道因某种原因被中断。
ConnectionClosedByBroker: 这个异常表示由broker主动关闭了连接。可能的原因包括broker因为某种原因(如配置、负载)决定关闭连接。
"""

  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值