延迟队列:用于存放需要在指定时间后(TTL)被执行的消息的队列,延迟队列消息过期后会被存放到死信队列,消费者不断对死信队列进行消费。
TTL:消息存活的最大时间,单位为毫秒,在TTL时间内,若消息未被消费,则会成为死信。
延迟队列使用场景,如:
①订单在指定时间内未支付,则要自动取消订单;
②用户退款,商家在指定时间未回复,则发送消息提醒商家;
③预定会议后,在会议开始前10分钟i提醒参会人员参会;
④外卖订单配送要快超时,提醒配送员。
案例:一个生产者发送一条消息分给一个ttl为10s的延迟队列和一个ttl为40s的延迟队列,如下图:
1、获取连接信道工具类RabbitUntils类,同时需要启动rabbitmq服务,以及关闭linux防火墙
//连接工厂,创建信道工具类
public class RabbitUtils {
// 得到一个连接的 channel
public static Channel getChannel() throws Exception {
// 创建一个连接工厂
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("192.168.23.129");
factory.setUsername("user");
factory.setPassword("123");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
return channel;
}
}
2、创建生产者发送消息
(1)调用工具类获取信道连接,声明普通交换机,类型为direct;
(2)调用basicPublish(交换机,路由,其他参数,消息体)方法发送消息给消费者;
(3)调用System.currentTimeMillis()获取本地时间,记录发送消息时的时间。
public class Producer {
private final static String NORMAL_EXCHANGE="normal_exchange";
public static void main(String[] args) throws Exception {
//获取信道连接
Channel channel = RabbitUtils.getChannel();
//声明交换机
channel.exchangeDeclare(NORMAL_EXCHANGE, BuiltinExchangeType.DIRECT);
//消息体
String messageA="赶快支付订单,队列为queueA";
String messageB="赶快支付订单,队列为queueB";
//发送消息给queueA
channel.basicPublish(NORMAL_EXCHANGE,"NA",null,messageA.getBytes("UTF-8"));
//发消息给queueB
channel.basicPublish(NORMAL_EXCHANGE,"NB",null,messageB.getBytes("UTF-8"));
System.out.println("生产者发送消息:"+messageA+",当前时间为:"+new SimpleDateFormat("yyyy年MM月dd日 HH时:mm分:ss秒").format(System.currentTimeMillis()));
System.out.println("生产者发送消息:"+messageB+",当前时间为:"+new SimpleDateFormat("yyyy年MM月dd日 HH时:mm分:ss秒").format(System.currentTimeMillis()));
}
}
3、创建消费者消费死信队列中的消息
(1)获取信道,声明死信交换机和死信队列;
(2)调用queueDeclare(队列名,是否持久化,是否只供·一个消费者,是否自动删除,其他参数)声明队列queueA,创建map集合,设置ttl过期时间为10s,消息过期后的死信交换机,以及延迟队列与死信交换机的路由;
(3)调用queueDeclare(队列名,是否持久化,是否只供·一个消费者,是否自动删除,其他参数)声明队列queueB,修改map集合参数,设置ttl过期时间为40s,消息过期后的死信交换机,以及延迟队列与死信交换机的路由;
(4)绑定队列A与普通交换机,队列B与普通交换机,死信队列与死信交换机;
(5)调用basicConsumer(队列,是否自动应答,成功回调函数,失败回调函数)接收消息,记录接收消息的本地时间。
public class Consumer {
//队列A,B名字
private final static String QUEUEA="queueA";
private final static String QUEUEB="queueB";
private final static String NORMAL_EXCHANGE="normal_exchange";
//死信队列,交换机名
private final static String QUEUED="queueD";
private final static String DEAD_EXCHANGE="dead_exchange";
//消费死信队列消息消费者
public static void main(String[] args) throws Exception {
//获取连接
Channel channel = RabbitUtils.getChannel();
//声明死信队列和死信交换机
channel.exchangeDeclare(DEAD_EXCHANGE, BuiltinExchangeType.DIRECT);
channel.queueDeclare(QUEUED,false,false,false,null);
//声明队列A
Map argument=new HashMap<>();
//设置ttl为10s
argument.put("x-message-ttl",10000);
//设置死信交换机
argument.put("x-dead-letter-exchange",DEAD_EXCHANGE);
//设置死信交换机与路由
argument.put("x-dead-letter-routing-key","DD");
channel.queueDeclare(QUEUEA,false,false,false,argument);
//声明队列B
//设置ttl为40s
argument.put("x-message-ttl",40000);
channel.queueDeclare(QUEUEB,false,false,false,argument);
//绑定队列A与交换机,队列B与交换机
channel.queueBind(QUEUEA,NORMAL_EXCHANGE,"NA");
channel.queueBind(QUEUEB,NORMAL_EXCHANGE,"NB");
//绑定死信队列与死信交换机
channel.queueBind(QUEUED,DEAD_EXCHANGE,"DD");
//接受消息成功回调
DeliverCallback deliverCallback=(consumerTag,message)->{
System.out.println("接收到消息:"+new String(message.getBody(),"UTF-8")+",当前时间为:"+new SimpleDateFormat("yyyy年MM月dd日 HH时:mm分:ss秒").format(System.currentTimeMillis()));
};
//消费死信队列消息
channel.basicConsume(QUEUED,true,deliverCallback,consumerTag->{});
}
}
4、生产者给队列A,B发送对应消息,测试如下
队列A的消息在10s后被消费,队列B的消息在40s后被消费,测试成功
上面设置ttl在队列中设置,这样每次有需求都需要设置一个ttl延迟队列,需要设置延迟队列过多,我们可以通过添加一个队列C,此处队列C不设置ttl属性,通过生产者发送带有ttl时间的消息,即可优化,如下图: