1)延时队列顾名思义,即放置在该队列里面的消息是不需要立即消费的,而是等待一段时间之后取出消费。
也就是可以控制的一个定时器,多数用在订单,付款等等
2)TTL和DLX
TTL是Time To Live的缩写, 也就是生存时间。RabbitMq支持对消息和队列设置TTL,对消息这设置是在 发送的时候指定,对队列设置是从消息入队列开始计算, 只要超过了队列的超时时间配置, 那么消息会自动 清除。
如果两种方式一起使用消息对TTL和队列的TTL之间较小的为准,也就是消息5s过期,队列是10s,那么5s
的生效。
默认是没有过期时间的,表示消息没有过期时间;如果设置为0,表示消息在投递到消费者的时候直接被
消息,否则丢弃。
设置消息的过期时间用 x-message-ttl 参数实现,单位毫秒。
设置队列的过期时间用 x-expires 参数,单位毫秒,注意,不能设置为0。
DLX是Dead-Letter-Exchange的缩写,全称死信交换器。成为死信队列后,可以被重新发送到另外一个交换器中,这个交换器就是DLX,绑定DLX到队列称为死信队列。注意,死信队列和死信交换器不一样哦。
成为死信一般由以下几种情况:
消息被拒绝 (basic.reject or basic.nack) 且带 requeue=false 参数
消息的TTL-存活时间已经过期
队列长度限制被超越(队列满)
DLX和一般的交换器没有区别,可以声明在任何的队列上,理解为队列的一个属性吧。当这个队列有死信时会根据设置自动的将死信重新发布到设置的DLX上进行消费。这个消费了死信的队列称之为死信队列,并不是绑定了DLX的队列是死信队列,大家要区分清楚。
通过在队列里设置 x-dead-letter-exchange 参数来声明DLX,如果当前DLX是 direct 类型还要 声明 x-dead-letter-routing-key 参数来指定路由键,如果没有指定,则使用原队列的路由键
1)jar
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-amqpartifactId>
dependency>
2)application.yml配置文件
spring:
rabbitmq:
host: 你的RabbitMq配置地址
port: 8080
username: 用户名默认(guest)
password: 密码默认(guest)
3)配置队列
package com.example.rabbitmq.config;
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
/**
* 作者:**
* 日期:2019/12/19 11:01
*/
//配置队列
@Configuration
public class DelayRabbitConfig {
/**
* 延迟队列 TTL 名称
*/
private static final String ORDER_DELAY_QUEUE = "user.order.delay.queue";
/**
* DLX,dead letter发送到的 exchange
* 延时消息就是发送到该交换机的
*/
public static final String ORDER_DELAY_EXCHANGE = "user.order.delay.exchange";
/**
* routing key 名称
* 具体消息发送在该 routingKey 的
*/
public static final String ORDER_DELAY_ROUTING_KEY = "order_delay";
public static final String ORDER_QUEUE_NAME = "user.order.queue";
public static final String ORDER_EXCHANGE_NAME = "user.order.exchange";
public static final String ORDER_ROUTING_KEY = "order";
/**
* 延迟队列配置
*
* 1、params.put("x-message-ttl", 5 * 1000);
* 第一种方式是直接设置 Queue 延迟时间 但如果直接给队列设置过期时间,这种做法不是很灵活,(当然二者是兼容的,默认是时间小的优先)
* 2、rabbitTemplate.convertAndSend(book, message -> {
* message.getMessageProperties().setExpiration(2 * 1000 + "");
* return message;
* });
* 第二种就是每次发送消息动态设置延迟时间,这样我们可以灵活控制
**/
@Bean
public Queue delayOrderQueue() {
Map<String, Object> params = new HashMap<>();
// x-dead-letter-exchange 声明了队列里的死信转发到的DLX名称,
params.put("x-dead-letter-exchange", ORDER_EXCHANGE_NAME);
// x-dead-letter-routing-key 声明了这些死信在转发时携带的 routing-key 名称。
params.put("x-dead-letter-routing-key", ORDER_ROUTING_KEY);
return new Queue(ORDER_DELAY_QUEUE, true, false, false, params);
}
/**
* 需要将一个队列绑定到交换机上,要求该消息与一个特定的路由键完全匹配。
* 这是一个完整的匹配。如果一个队列绑定到该交换机上要求路由键 “dog”,则只有被标记为“dog”的消息才被转发,
* 不会转发dog.puppy,也不会转发dog.guard,只会转发dog。
* @return DirectExchange
*/
@Bean
public DirectExchange orderDelayExchange() {
return new DirectExchange(ORDER_DELAY_EXCHANGE);
}
@Bean
public Binding dlxBinding() {
return BindingBuilder.bind(delayOrderQueue()).to(orderDelayExchange()).with(ORDER_DELAY_ROUTING_KEY);
}
@Bean
public Queue orderQueue() {
return new Queue(ORDER_QUEUE_NAME, true);
}
/**
* 将路由键和某模式进行匹配。此时队列需要绑定要一个模式上。
* 符号“#”匹配一个或多个词,符号“*”匹配不多不少一个词。因此“audit.#”能够匹配到“audit.irs.corporate”,但是“audit.*” 只会匹配到“audit.irs”。
**/
@Bean
public TopicExchange orderTopicExchange() {
return new TopicExchange(ORDER_EXCHANGE_NAME);
}
@Bean
public Binding orderBinding() {
// TODO 如果要让延迟队列之间有关联,这里的 routingKey 和 绑定的交换机很关键
return BindingBuilder.bind(orderQueue()).to(orderTopicExchange()).with(ORDER_ROUTING_KEY);
}
}
4)接收者
import org.springframework.stereotype.Component;
import java.util.Date;
/**
* 作者:**
* 日期:2019/12/19 11:02
*/
@Component
public class DelayReceiver {
//接收者
@RabbitListener(queues = {DelayRabbitConfig.ORDER_QUEUE_NAME})
public void orderDelayQueue(Order order, Message message, Channel channel) {
System.out.println("###########################################");
System.out.println("【orderDelayQueue 监听的消息】 - 【消费时间】 - [{"+new Date()+"}]- 【订单内容】 - [{"+order.toString()+"}]");
if(order.getOrderStatus() == 0) {
order.setOrderStatus(2);
System.out.println("【该订单未支付,取消订单】" + order.toString());
} else if(order.getOrderStatus() == 1) {
System.out.println("【该订单已完成支付】");
} else if(order.getOrderStatus() == 2) {
System.out.println("【该订单已取消】");
}
System.out.println("###########################################");
}
}
5)发送者
package com.example.rabbitmq.config;
import com.example.rabbitmq.pojo.Order;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Date;
/**
* 作者:**
* 日期:2019/12/19 11:07
*/
@Component
public class DelaySender {
@Autowired
private AmqpTemplate amqpTemplate;
//发送者
public void sendDelay(Order order) {
System.out.println("【订单生成时间】" + new Date().toString() +"【1分钟后检查订单是否已经支付】" + order.toString() );
this.amqpTemplate.convertAndSend(DelayRabbitConfig.ORDER_DELAY_EXCHANGE, DelayRabbitConfig.ORDER_DELAY_ROUTING_KEY, order, message -> {
// 如果配置了 params.put("x-message-ttl", 5 * 1000); 那么这一句也可以省略,具体根据业务需要是声明 Queue 的时候就指定好延迟时间还是在发送自己控制时间
message.getMessageProperties().setExpiration(1 * 1000 * 60 + " ");
return message;
});
}
}
6)controller
package com.example.rabbitmq.controller;
import com.example.rabbitmq.config.DelaySender;
import com.example.rabbitmq.pojo.Order;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.DelayQueue;
/**
* 作者:**
* 日期:2019/12/19 11:17
*/
@RestController
public class TestController {
@Autowired
private DelaySender delaySender;
@GetMapping("/sendDelay")
public Object sendDelay() {
Order order1 = new Order();
order1.setOrderStatus(0);
order1.setOrderId("123456");
order1.setOrderName("小米7");
Order order2 = new Order();
order2.setOrderStatus(1);
order2.setOrderId("456789");
order2.setOrderName("小米9");
delaySender.sendDelay(order1);
delaySender.sendDelay(order2);
return "ok";
}
}
7)实体类
package com.example.rabbitmq.pojo;
import lombok.Data;
import java.io.Serializable;
/**
* 作者:**
* 日期:2019/12/19 11:38
*/
@Data
public class Order implements Serializable {
private static final long serialVersionUID = -2221214252163879885L;
private String orderId; // 订单id
private Integer orderStatus; // 订单状态 0:未支付,1:已支付,2:订单已取消
private String orderName; // 订单名字
}