rabbitmq 延时队列,死信队列

版权声明:本文为CSDN博主「你跑快点丶Py」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/qq_41572228/article/details/105252066

作者:渃汐湲     链接:https://www.jianshu.com/p/986ee5eb78bc

 

死信队列介绍
  • 死信队列:DLX,dead-letter-exchange
  • 利用DLX,当消息在一个队列中变成死信 (dead message) 之后,它能被重新publish到另一个Exchange,这个Exchange就是DLX
消息变成死信有以下几种情况
  • 消息被拒绝(basic.reject / basic.nack),并且requeue = false
  • 消息TTL过期
  • 队列达到最大长度
死信处理过程
  • DLX也是一个正常的Exchange,和一般的Exchange没有区别,它能在任何的队列上被指定,实际上就是设置某个队列的属性。
  • 当这个队列中有死信时,RabbitMQ就会自动的将这个消息重新发布到设置的Exchange上去,进而被路由到另一个队列。
  • 可以监听这个队列中的消息做相应的处理。

 

#!/usr/bin/env python
 
import pika
import logging
 
 
class RabbitMQClient:
    def __init__(self, conn_str='amqp://guest:guest@0.0.0.0:5672/Authorization/'):
        self.exchange_type = "topic"  # 特定的路由键完全匹配,会推送到指定的队列上
        self.connection = pika.BlockingConnection(pika.URLParameters(conn_str))
        self.channel = self.connection.channel()
        self._declare_retry_queue()
        logging.debug("Rabbitmq队列连接成功")
 
    def _declare_retry_queue(self):
        """
        创建异常交换器和队列,用于存放没有正常处理的消息。
        :return:
        """
        self.channel.exchange_declare(exchange='RetryExchange',
                                      exchange_type='fanout',
                                      durable=True)
        self.channel.queue_declare(queue='RetryQueue',
                                   durable=True)
        # 绑定队列到指定的交换机
        self.channel.queue_bind('RetryQueue', 'RetryExchange', 'RetryQueue')
 
    def declare_exchange(
            self,
            exchange=None,  # 交换机的名字,为空则自动创建一个名字
            exchange_type='',  # 默认交换机类型为direct
            passive=False,  # 检查交换机是否存在,存在返回状态信息,不存在返回404错误
            durable=False,  # 设置是否持久化)
            auto_delete=False,  # 最后一个队列解绑则删除
            internal=False,  # 是否设置为值接收从其他交换机发送过来的消息,不接收生产者的消息
            arguments=None):  # 一个字典,用于传递额外的参数
 
        self.channel.exchange_declare(exchange=exchange,
                                      exchange_type=exchange_type if exchange_type else self.exchange_type,
                                      durable=True)
 
    def declare_queue(
            self,
            queue='',  # 队列的名字,默认为空,此时将自动创建一个名字,
            passive=False,  # 检查一下队列是否存在,如果该参数为True,该方法判断队列存在否,不会声明队列;存在返回queue的状态,不存在报错
            durable=False,  # 队列持久化参数,默认不持久化
            exclusive=False,  # 设置独享队列,该队列只被当前的connection使用,如果该tcp关闭了,队列会被删除
            auto_delete=False,  # 当最后一个消费者退订后自动删除,默认不开启
            arguments=None):  # 一个字典,用于队列传递额外的参数
        self.channel.queue_declare(queue=queue,
                                   durable=True)
 
    def declare_delay_queue(self, queue, durable=True, DLX='RetryExchange', TTL=""):
        """
        创建延迟队列
        :param TTL: ttl的单位是us,ttl=60000 表示 60s
        :param queue:
        :param DLX:死信转发的exchange
        :return:
        """
        arguments = {}
        if DLX:
            # 设置死信转发的exchange,延迟结束后指向的交换机(死信收容交换机)
            arguments['x-dead-letter-exchange'] = DLX
        if TTL:
            # 消息的存活时间,消息过期后会被指向(死信收容交换机)收入死信队列
            arguments['x-message-ttl'] = TTL
        print(arguments)
        self.channel.queue_declare(queue=queue,  # 声明队列
                                   durable=durable,  # 持久化
                                   arguments=arguments)
 
    def bind_queue(
            self,
            queue,  # 队列的名字
            exchange,  # 交换机的名字
            routing_key=None,  # 路由键规则,当为None时,默认使用queue的名字作为路由键规则
            arguments=None):  # 一个字典,传递额外的参数
        self.channel.queue_bind(queue=queue,
                                exchange=exchange,
                                routing_key=routing_key,
                                )
        # 返回绑定的状态信息
 
    def bind_exchange(
            self,
            destination=None,  # 目的交换机的名字
            source=None,  # 源交换机的名字
            routing_key='',  # 路由键规则,当为None时,默认使用queue的名字作为路由键规则
            arguments=None):  # 一个字典,传递额外的参数
        self.channel.exchange_bind(destination=destination,
                                   source=source)
        # 返回绑定的状态信息
 
    def basic_publish(
            self,
            exchange,  # 交换机的名字
            routing_key,  # 路由键,topic交换机是扇形交换机,可以随便写
            body,  # 消息主体
            properties=None,  # 消息的属性
            mandatory=False,  # 是否设置消息托管
            immediate=False):  # 是否消息实时同步确认,一般和confirm模式配合使用
        self.channel.basic_publish(exchange=exchange,
                                   routing_key=routing_key,
                                   body=body,
                                   properties=pika.BasicProperties(
                                       delivery_mode=2,
                                       type=exchange
                                   ))
        print("生产者推送消息成功")
 
    def __publish_init__(
            self, content_type=None, content_encoding=None, headers=None, delivery_mode=None,
            priority=None,
            correlation_id=None, reply_to=None, expiration=None, message_id=None, timestamp=None,
            type=None,
            user_id=None, app_id=None, cluster_id=None):
        self.content_type = content_type  # 消息的类型,如text/html,json等
        self.content_encoding = content_encoding  # 消息的编码,如gbk,utf-8等
        self.headers = headers  # 消息头,可以和头交换机约定规则
        self.delivery_mode = delivery_mode  # 消息持久化,2表示持久化,
        self.priority = priority  # 消息的优先权
        self.correlation_id = correlation_id
        self.reply_to = reply_to
        self.expiration = expiration  # 消息的有效期
        self.message_id = message_id  # 消息iD,自动管理
        self.timestamp = timestamp  # 消息的时间戳
        self.type = type
        self.user_id = user_id
        self.app_id = app_id  # 发布应用的ID
        self.cluster_id = cluster_id
 
    def basic_consume(
            self,  # 启动队列消费者,告诉服务端开启一个消费者
            consumer_callback,  # 消费者回调函数
            queue,  # 队列名称
            auto_ack=False,  # 发送确认,默认开启消息确认模式,为True是关闭消息确认;如果回调函数中不发送消息确认,消息会一直存在队列中,等待推送给新连接的消费者
            exclusive=False,  # 设置独享消费者,不允许其他消费者订阅该队列
            consumer_tag=None,  # 消费者标签,如果不指定,系统自动生成
            arguments=None):  # 字典,额外的参数
        print("消费者开始消费消息")
        self.channel.basic_qos(prefetch_count=1)
        self.channel.basic_consume(
            on_message_callback=consumer_callback,
            queue=queue,
            auto_ack=auto_ack
        )
 
    # 接收消息处理函数
    def callback_success(
            self,
            channel,  # 信道
            method,  # 一个交付的deliver对象,用来通知客户端消息
            properties,  # 消息的属性,就是消息在发送时定义的属性
            body):  # 消息的主题,二进制格式
        print(str(body.decode('utf-8')))
        print('接收成功!')
        # 发送确认
        channel.basic_ack(delivery_tag=method.delivery_tag)
        print('回复确认消息')
 
    # 失败函数,将消息重新放回队列中
    def callback_failed(
            self,
            channel,  # 信道
            method,  # 一个交付的deliver对象,用来通知客户端消息
            properties,  # 消息的属性,就是消息在发送时定义的属性
            body):  # 消息的主题,二进制格式
        print(str(body.decode('utf-8')))
        print('接收失败!')
        channel.basic_nack(delivery_tag=method.delivery_tag, requeue=True)
        print('将当前消息重新放入队列中')
 
    # 失败函数,将消息重新放回队列中
    def callback_reject(
            self,
            channel,  # 信道
            method,  # 一个交付的deliver对象,用来通知客户端消息
            properties,  # 消息的属性,就是消息在发送时定义的属性
            body):  # 消息的主题,二进制格式
        print(str(body.decode('utf-8')))
        print('接收失败!')
        channel.basic_reject(delivery_tag=method.delivery_tag, requeue=False)
 
    def close_channel(self):
        self.connection.close()
 
    def start_consuming(self):
        self.channel.start_consuming()

  

 

消费者01 用来接收routing_key 为 info01 的消息,消费者01 接收到消息后,通过手动提交ack来确认消息送达

    channel = RabbitMQClient()
    exchange_name = 'exchange_topic'
    channel.declare_exchange(exchange_name, durable=True)
    queue = "info01"
    channel.declare_delay_queue(queue, TTL=10000)
 
    channel.bind_queue(queue, exchange_name, 'info01')
    channel.basic_consume(channel.callback_success, queue, auto_ack=True)
    channel.start_consuming()

消费者02 用来接收routing_key 为 info02 的消息,消费者02接收到消息后,手动提交nack,当前消息将会被自动发送至死信队列中。通过在回调函数中使用basic_nack(requeue=True)可以将消息重新放回队列中重新消费,可以实现循环调用业务。如果当前消息没有被消费,则在TTL时间过期后,被放进死信队列

    channel = RabbitMQClient()
    exchange_name = 'exchange_topic'
    channel.declare_exchange(exchange_name, durable=True)
    queue = "info02"
    channel.declare_delay_queue(queue, TTL=10000)
 
    channel.bind_queue(queue, exchange_name, 'info02')
    channel.basic_consume(channel.callback_reject, queue, auto_ack=False)
    channel.start_consuming()

死信、延迟消息消费者,在收到消息之后,回复ack或者nack,否则会报406错误

    channel = RabbitMQClient()
    queue = "RetryQueue"
    channel.basic_consume(channel.callback_success, queue, auto_ack=False)
    channel.start_consuming()

生产者,发送给info01的消息会被直接消费,发送给info02的消息,如果没有接收到ack回复或者接收到nack,则会被通知死信路由,将消息推入死信队列中,可以被死信队列消费

    channel = RabbitMQClient()
    # 指定交换机
    exchange_name = 'exchange_topic'
    channel.declare_exchange(exchange_name, durable=True)
    body = 'Hello Direct11111'
    # 发送消息的时候,需要指定路由分配规则routing_key,如果是Fanout则不用传递,将会广播,每一个路由下的队列都会收到消息
    channel.basic_publish(
        exchange=exchange_name,
        routing_key='info02',
        body=body,
    )
    channel.close_channel()

 

posted @ 2021-09-01 10:48  逐梦客!  阅读(96)  评论(0)    收藏  举报