原文地址:https://blog.csdn.net/u014308482/article/details/53036770

ps: 文章里面延迟队列=延时队列

什么是延迟队列

延迟队列存储的对象肯定是对应的延时消息,所谓”延时消息”是指当消息被发送以后,并不想让消费者立即拿到消息,而是等待指定时间后,消费者才拿到这个消息进行消费。

场景一:在订单系统中,一个用户下单之后通常有30分钟的时间进行支付,如果30分钟之内没有支付成功,那么这个订单将进行一场处理。这是就可以使用延时队列将订单信息发送到延时队列。

场景二:用户希望通过手机远程遥控家里的智能设备在指定的时间进行工作。这时候就可以将用户指令发送到延时队列,当指令设定的时间到了再将指令推送到只能设备。


RabbitMQ如何实现迟队列

方法一

AMQP协议和RabbitMQ队列本身没有直接支持延迟队列功能,但是可以通过以下特性模拟出延迟队列的功能。 
但是我们可以通过RabbitMQ的两个特性来曲线实现延迟队列:

RabbitMQ可以针对Queue设置x-expires 或者 针对Message设置 x-message-ttl,来控制消息的生存时间,如果超时(两者同时设置以最先到期的时间为准),则消息变为dead letter(死信)

RabbitMQ针对队列中的消息过期时间有两种方法可以设置。

  • A: 通过队列属性设置,队列中所有消息都有相同的过期时间。
  • B: 对消息进行单独设置,每条消息TTL可以不同。

如果同时使用,则消息的过期时间以两者之间TTL较小的那个数值为准。消息在队列的生存时间一旦超过设置的TTL值,就成为dead letter

RabbitMQ的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发送

队列出现dead letter的情况有:

  • 消息或者队列的TTL过期

  • 队列达到最大长度

  • 消息被消费端拒绝(basic.reject or basic.nack)并且requeue=false

综合上述两个特性,设置了TTL规则之后当消息在一个队列中变成死信时,利用DLX特性它能被重新转发到另一个Exchange或者Routing Key,这时候消息就可以重新被消费了。

设置方法:

第一步:设置TTL产生死信,有两种方式Per-Message TTL和 Queue TTL,第一种可以针对每一条消息设置一个过期时间使用于大多数场景,第二种针对队列设置过期时间、适用于一次性延时任务的场景

还有其他产生死信的方式比如消费者拒绝消费 basic.reject 或者 basic.nack ( 前提要设置消费者的属性requeue=false) 
- Per-Message TTL (对每一条消息设置一个过期时间)(官方文档

java client发送一条只能驻留60秒的消息到队列:

byte[] messageBodyBytes = "Hello, world!".getBytes();
AMQP.BasicProperties properties = new AMQP.BasicProperties();
properties.setExpiration("60000");//设置消息的过期时间为60秒
channel.basicPublish("my-exchange", "routing-key", properties, messageBodyBytes);
//这条消息发送到相应的队列之后,如果60秒内没有被消费,则变为死信
  • 1
  • 2
  • 3
  • 4
  • 5
  • Queue TTL (对整个队列设置一个过期时间)

创建一个队列,队列的消息过期时间为30分钟(这个队列30分钟内没有消费者消费消息则删除,删除后队列内的消息变为死信)

java client方式:

Map<String, Object> args = new HashMap<String, Object>();
args.put("x-expires", 1800000);
channel.queueDeclare("myqueue", false, false, false, args); rabbitmqctl命令方式(.* 为所有队列, 可以替换为指定队列):
rabbitmqctl set_policy expiry ".*" '{"expires":1800000}' --apply-to queues rabbitmqctl (Windows):
rabbitmqctl set_policy expiry ".*" "{""expires"":1800000}" --apply-to queues
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

第二步:设置死信的转发规则(如果没有任何规则,则直接丢弃死信) 
- Dead Letter Exchanges设置方法(官方文档

Java Client方式:
//声明一个直连模式的exchange
channel.exchangeDeclare("some.exchange.name", "direct");
//声明一个队列,当myqueue队列中有死信产生时,会转发到交换器some.exchange.name
Map<String, Object> args = new HashMap<String, Object>();
args.put("x-dead-letter-exchange", "some.exchange.name"); //如果设置死信会以路由键some-routing-key转发到some.exchange.name,如果没设默认为消息发送到本队列时用的routing key
//args.put("x-dead-letter-routing-key", "some-routing-key");
channel.queueDeclare("myqueue", false, false, false, args); 命令行方式(.* 为所有队列, 可以替换为指定队列):
设置 "dead-letter-exchange"
rabbitmqctl:
rabbitmqctl set_policy DLX ".*" '{"dead-letter-exchange":"my-dlx"}' --apply-to queues
rabbitmqctl (Windows):
rabbitmqctl set_policy DLX ".*" "{""dead-letter-exchange"":""my-dlx""}" --apply-to queues
设置 "dead-letter-routing-key"
rabbitmqctl:
rabbitmqctl set_policy DLX ".*" '{ "dead-letter-routing-key":"my-routing-key"}' --apply-to queues
rabbitmqctl (Windows):
rabbitmqctl set_policy DLX ".*" "{""dead-letter-routing-key"":""my-routing-key""}" --apply-to queues
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

方法二

在rabbitmq 3.5.7及以上的版本提供了一个插件(rabbitmq-delayed-message-exchange)来实现延迟队列功能。同时插件依赖Erlang/OPT 18.0及以上。

插件源码地址: 
https://github.com/rabbitmq/rabbitmq-delayed-message-exchange

插件下载地址: 
https://bintray.com/rabbitmq/community-plugins/rabbitmq_delayed_message_exchange

安装:

进入插件安装目录 
{rabbitmq-server}/plugins/(可以查看一下当前已存在的插件) 
下载插件 
rabbitmq_delayed_message_exchange

wget https://bintray.com/rabbitmq/community-plugins/download_file?file_path=rabbitmq_delayed_message_exchange-0.0.1.ez
  • 1

(如果下载的文件名称不规则就手动重命名一下如: 
rabbitmq_delayed_message_exchange-0.0.1.ez)

启用插件

rabbitmq-plugins enable rabbitmq_delayed_message_exchange

(关闭插件)
rabbitmq-plugins disable rabbitmq_delayed_message_exchange
  • 1
  • 2
  • 3
  • 4

插件使用

通过声明一个x-delayed-message类型的exchange来使用delayed-messaging特性 
x-delayed-message是插件提供的类型,并不是rabbitmq本身的

// ... elided code ...
Map<String, Object> args = new HashMap<String, Object>();
args.put("x-delayed-type", "direct");
channel.exchangeDeclare("my-exchange", "x-delayed-message", true, false, args);
// ... more code ...
  • 1
  • 2
  • 3
  • 4
  • 5

发送消息的时候通过在header添加”x-delay”参数来控制消息的延时时间

// ... elided code ...
byte[] messageBodyBytes = "delayed payload".getBytes("UTF-8");
Map<String, Object> headers = new HashMap<String, Object>();
headers.put("x-delay", 5000);
AMQP.BasicProperties.Builder props = new AMQP.BasicProperties.Builder().headers(headers);
channel.basicPublish("my-exchange", "", props.build(), messageBodyBytes);
// ... more code ...
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

使用示例:

消息发送端:

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map; import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory; public class Send {
// 队列名称
private final static String EXCHANGE_NAME="delay_exchange";
private final static String ROUTING_KEY="key_delay"; @SuppressWarnings("deprecation")
public static void main(String[] argv) throws Exception {
/**
* 创建连接连接到MabbitMQ
*/
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("192.168.12.190");
factory.setUsername("admin");
factory.setPassword("admin");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel(); SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); // 声明x-delayed-type类型的exchange
Map<String, Object> args = new HashMap<String, Object>();
args.put("x-delayed-type", "direct");
channel.exchangeDeclare(EXCHANGE_NAME, "x-delayed-message", true,
false, args); Map<String, Object> headers = new HashMap<String, Object>();
//设置在2016/11/04,16:45:12向消费端推送本条消息
Date now = new Date();
Date timeToPublish = new Date("2016/11/04,16:45:12"); String readyToPushContent = "publish at " + sf.format(now)
+ " \t deliver at " + sf.format(timeToPublish); headers.put("x-delay", timeToPublish.getTime() - now.getTime()); AMQP.BasicProperties.Builder props = new AMQP.BasicProperties.Builder()
.headers(headers);
channel.basicPublish(EXCHANGE_NAME, ROUTING_KEY, props.build(),
readyToPushContent.getBytes()); // 关闭频道和连接
channel.close();
connection.close();
}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56

消息接收端:

import java.text.SimpleDateFormat;
import java.util.Date; import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.QueueingConsumer; public class Recv { // 队列名称
private final static String QUEUE_NAME = "delay_queue";
private final static String EXCHANGE_NAME="delay_exchange"; public static void main(String[] argv) throws Exception,
java.lang.InterruptedException {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("192.168.12.190");
factory.setUsername("admin");
factory.setPassword("admin");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel(); QueueingConsumer queueingConsumer = new QueueingConsumer(channel); channel.queueDeclare(QUEUE_NAME, true,false,false,null);
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "");
channel.basicConsume(QUEUE_NAME, true, queueingConsumer);
SimpleDateFormat sf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
try {
System.out.println("****************WAIT***************");
while(true){
QueueingConsumer.Delivery delivery = queueingConsumer
.nextDelivery(); // String message = (new String(delivery.getBody()));
System.out.println("message:"+message);
System.out.println("now:\t"+sf.format(new Date()));
} } catch (Exception exception) {
exception.printStackTrace(); } }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47

启动接收端,启动发送端 
运行结果:

****************WAIT***************
message:publish at 2016-11-04 16:44:16.887 deliver at 2016-11-04 16:45:12.000
now: 2016-11-04 16:45:12.023
  • 1
  • 2
  • 3

结果显示在我们2016-11-04 16:45:12.023接收到了消息,距离我们设定的时间2016-11-04 16:45:12.023有23毫秒的延迟

Note:使用rabbitmq-delayed-message-exchange插件时发送到队列的消息数量在web管理界面可能不可见,不影响正常功能使用

Note :使用过程中发现,当一台启用了rabbitmq-delayed-message-exchange插件的RAM节点在重启的时候会无法启动,查看日志发现了一个Timeout异常,开发者解释说这是节点在启动过程会同步集群相关数据造成启动超时,并建议不要使用Ram节点

插件开发者: 
RAM nodes start blank and need a disk node to sync tables from. In this case it times out.

More importantly, you don’t need RAM nodes. If you’re not sure if you do, you certainly don’t, as don’t 99% of users.

rabbitmq 实现延迟队列的两种方式的更多相关文章

  1. RabbitMQ Consumer获取消息的两种方式(poll,subscribe)解析

    以下转自:http://blog.csdn.net/yangbutao/article/details/10395599 rabbitMQ中consumer通过建立到queue的连接,创建channe ...

  2. java实现消息队列的两种方式

    https://blog.csdn.net/fenfenguai/article/details/79257928

  3. 如何用RabbitMQ实现延迟队列

    前言 在 jdk 的 juc 工具包中,提供了一种延迟队列 DelayQueue.延迟队列用处非常广泛,比如我们最常见的场景就是在网购或者外卖平台中发起一个订单,如果不付款,一般 15 分钟后就会被关 ...

  4. 子进程回收资源两种方式,僵尸进程与孤儿进程,守护进程,进程间数据隔离,进程互斥锁,队列,IPC机制,线程,守护线程,线程池,回调函数add_done_callback,TCP服务端实现并发

    子进程回收资源两种方式 - 1) join让主进程等待子进程结束,并回收子进程资源,主进程再结束并回收资源. - 2) 主进程 “正常结束” ,子进程与主进程一并被回收资源. from multipr ...

  5. ActiveMQ消息传递的两种方式

    1.什么是ActiveMQ? ActiveMQ是apache提供的开源的,实现消息传递的一个中间插件,可以和spring整合,是目前最流行的开源消息总线,ActiveMQ是一个完全支持JMS1.1和J ...

  6. sparkStreaming读取kafka的两种方式

    概述 Spark Streaming 支持多种实时输入源数据的读取,其中包括Kafka.flume.socket流等等.除了Kafka以外的实时输入源,由于我们的业务场景没有涉及,在此将不会讨论.本篇 ...

  7. RabbitMQ实现延时消息的两种方法

    目录 RabbitMQ实现延时消息的两种方法 1.死信队列 1.1消息什么时候变为死信(dead-letter) 1.2死信队列的原理 1.3 代码实现 1.4死信队列的一个小坑 2 .延时插件 2. ...

  8. [操作系统知识储备,进程相关概念,开启进程的两种方式、 进程Queue介绍]

    [操作系统知识储备,进程相关概念,开启进程的两种方式.进程Queue介绍] 操作系统知识回顾 为什么要有操作系统. 程序员无法把所有的硬件操作细节都了解到,管理这些硬件并且加以优化使用是非常繁琐的工作 ...

  9. Redis的持久化的两种方式drbd以及aof日志方式

    redis的持久化配置: 主要包括两种方式:1.快照  2 日志 来看一下redis的rdb的配置选项和它的工作原理: save 900 1 // 表示的是900s内,有1条写入,则产生快照 save ...

随机推荐

  1. linux 注册一个 PCI 驱动

    为了被正确注册到内核, 所有的 PCI 驱动必须创建的主结构是 struct pci_driver 结构. 这个结构包含许多函数回调和变量, 来描述 PCI 驱动给 PCI 核心. 这里是这个结构的一 ...

  2. koa2实现简单的验证码

    //首先引入svg-captcha,https://www.npmjs.com/package/svg-captcha const svgCaptcha = require('svg-captcha) ...

  3. 【2016常州一中夏令营Day5】

    小 W 拼图[问题描述]小 W 和小 M 一起玩拼图游戏啦~小 M 给小 M 一张 N 个点的图,有 M 条可选无向边,每条边有一个甜蜜值,小 W 要选K 条边,使得任意两点间最多有一条路径,并且选择 ...

  4. Qt4与Qt3的主要不同

    Qt4与Qt3的主要不同 1)QT4 中提供了大量新控件,虽然它也保持了旧的控件,并命名为Qt3XXX,但是这样的控件没准在今后的哪个QT版本中就不被支持了,所以还是换吧,控件替换的 工作是巨大的,这 ...

  5. Your development team, "", does not support the Push Notifications capability.

    问题: Your development team, "", does not support the Push Notifications capability. 解决方法: 1 ...

  6. jsp页面中如何让一个输入框内的提示文字是灰色而输入的文字是黑色

    <input name="name" type="text"  value="提示的文字" size="30" o ...

  7. no supported authentication methods avaiable

    在git(小乌龟)向github远程推送(push)文件是会报一个异常 no supported authentication methods avaiable 解决方法:因为git(小乌龟)和Git ...

  8. Ubuntu 18.04安装搜狗拼音

    首先安装fcitx 一.检测是否安装fcitx 首先检测是否有fcitx,因为搜狗拼音依赖fcitx > fcitx 提示: 程序“fcitx”尚未安装. 您可以使用以下命令安装: > s ...

  9. Windows服务器管理--批量管理工具

    iis7批量远程控制: 一款电脑远程监控的工具,IIS7远程桌面管理是一款专业的远程桌面连接软件,无需安装.操作简单方便.完美的界面设计.强大的监控功能.稳定的系统平台,满足了用户实现远程桌面连接的需 ...

  10. SpringBoot拦截器没有生效的原因

    最近的一个项目中,需要实现一个记录系统请求日志的功能,我通过拦截器实现了这个功能,但是,测试的时候发现拦截器没有发挥作用,检查了很久,没有发现错的地方,系统也没有报错,最终在网上找打了答案. 原来之前 ...