一、延迟队列
1-1 简单介绍
延迟队列:存储延迟消息的队列
延迟消息:消息存入队列,等待一定时间后才交于消费者进行消费
1-2 应用场景
场景一 :订单系统
用户下单的30分钟确认支付,若超过30分钟,则该订单被列为超时订单处理。可以使用延时队列,在30分的时候处理这个订单的状态。关闭订单,并且退还库存。
场景二:定时推送命令
手机远程遥控家里的智能设备在指定的时间进行工作。可以使用延迟队列,将指令定时推送到智能设备进行处理。
二、RabbitMQ 实现延迟队列
RabbitMQ 本身不支持延迟队列,但是可以通过 死信队列(DLX)和 消息有效时间 (TTL)来实现延迟队列。
- TTL - Time To Live
- 设置消息有效时间,若超时,则消息变为 dead letter(死信)
- Queue 的 x-expires 参数,Message 的 x-message-ttl 参数,都可以配置消息的有效时间
- x-expires, x-message-ttl 同时存在,取最小的为准
- 设置 Queue 的 x-expires,队列中所有消息都有相同的过期时间
- 设置 Msg 的 x-message-ttl,则该过期时间仅对该消息有效。
- DLX - Dead Letter Exchanges
- 配置 Queue 的 x-dead-letter-exchange 和 x-dead-letter-routing-key(可选)两个参数
- 如果队列内出现了dead letter,则按照配置的两个参数重新路由转发到指定的队列
- x-dead-letter-exchange:出现 dead letter 之后将 dead letter 重新发送到指定 exchange
- x-dead-letter-routing-key:出现 dead letter 之后将 dead letter 重新按照指定的 routing-key发送
- 出现 DL 的情况
- 消息或者队列的TTL过期
- 队列达到最大长度
- 消息被消费端拒绝(basic.reject or basic.nack)并且 requeue=false
三、Python + Pika 实现
import datetime
import pika
def conn():
credentials = pika.PlainCredentials('test', '123')
connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost', credentials=credentials))
if not connection.is_open:
return
channel = connection.channel()
if channel.is_open is False:
return
return channel
def declare(channel):
# 延迟交换机
delay_exchange = 'delay.15min.ttl.exchange'
# 延迟队列
delay_queue = 'delay.15min.ttl.queue'
# 延迟交换机死信收容交换机
exchange_dlx = 'dlx.exchange'
# 延迟队列死信收容队列
queue_dlx = 'dlx.queue'
# 声明收容交换机
channel.exchange_declare(exchange=exchange_dlx, exchange_type='fanout', durable=True, auto_delete=True, )
# 声明收容队列
channel.queue_declare(queue=queue_dlx, durable=True, auto_delete=True, )
# 收容队列和收容交换机绑定
channel.queue_bind(exchange=exchange_dlx, queue=queue_dlx)
# 设置延迟队列参数
arguments = {
# 'x-message-ttl': 1000 * 60 * 15, # 延迟时间 (毫秒),15min
'x-message-ttl': 1000 * 60 * 3,
'x-dead-letter-exchange': exchange_dlx, # 延迟结束后指向交换机(死信收容交换机)
'x-dead-letter-routing-key': queue_dlx, # 延迟结束后指向队列(死信收容队列)
}
# 声明延迟交换机、队列,并相互绑定;设置延迟队列的 ttl 和 xdl 跳转
channel.exchange_declare(exchange=delay_exchange, exchange_type='fanout', durable=True, auto_delete=True, )
channel.queue_declare(queue=delay_queue, exclusive=True, arguments=arguments, auto_delete=True, )
channel.queue_bind(exchange=delay_exchange, queue=delay_queue)
return queue_dlx, delay_exchange
def publish(channel, delay_exchange, msg, routing_key=''):
channel.basic_publish(exchange=delay_exchange, body=msg, routing_key=routing_key)
def subscribe(channel, callback, queue):
channel.basic_consume(callback, queue=queue, no_ack=True)
def sub_func(channel, body, envelope, properties):
print(datetime.datetime.now())
print(body)
if __name__ == '__main__':
channel = conn()
queue_dlx, exchange_dlx = declare(channel)
subscribe(channel, sub_func, queue_dlx)
# publish(channel, delay_exchange, 'hello')
channel.start_consuming()