这是使用rabbitmq的延迟队列来实现。不过rabbitmq并没有延迟队列这种模式,所以,需要通过TTL+死信队列来实现。
下面,为了方便理解,首先认识一下ttl和死信队列。
下面代码都是基于springboot2.3.4.RELEASE版本,由于生产者和消费者pom.xml和配置文件相同,所以,在下面先给出pom.xml和配置文件。
pom.xml
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.3.4.RELEASEversion>
parent>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-amqpartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
dependency>
dependencies>
配置文件
spring:
rabbitmq:
host: 192.168.211.131
port: 5672
username: test
password: test
virtual-host: /test
首先,讲一下什么是TTL。全称:Time To Live,用于设置过期时间。
在创建队列时,如果设置了过期时间,那么到时间消息就会自动丢弃或者是发送到死信队列里。
管控台中设置队列TTL
代码实现:只需配置生产者,因为消息会过期,所以消费者也监听不到消息
springboot入口
@SpringBootApplication
public class ProviderApplication {
public static void main(String[] args) {
SpringApplication.run(ProviderApplication.class, args);
}
}
配置rabbibtmq
@Configuration
public class RabbitmqConfig {
/**
* ttl交换机
*/
public static final String TTL_EXCHANGE = "springboot_ttl_exchange";
/**
* ttl队列
*/
private static final String TTL_QUEUE = "springboot_ttl_queue";
/**
* 声明ttl交换机
* @return ttl交换机
*/
@Bean
public Exchange ttlExchange() {
return ExchangeBuilder.topicExchange(TTL_EXCHANGE).build();
}
/**
* 声明ttl队列
* @return ttl队列
*/
@Bean
public Queue ttlQueue() {
return QueueBuilder.durable(TTL_QUEUE)
//该队列过期时间,单位毫秒
.ttl(10000)
.build();
}
/**
* 绑定ttl队列到ttl交换机
* @param queue ttl队列
* @param exchange ttl交换机
*/
@Bean
public Binding bindingTtl(@Qualifier("ttlQueue") Queue queue,
@Qualifier("ttlExchange") Exchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with("ttl.#").noargs();
}
}
测试类
@RunWith(SpringRunner.class)
@SpringBootTest(classes = ProviderApplication.class)
public class RabbitmqTest {
@Autowired
private RabbitTemplate template;
@Test
public void test1() {
template.convertAndSend(RabbitmqConfig.TTL_EXCHANGE,"ttl.test","这是ttl消息");
}
}
测试
10s之后,自动丢弃
下面介绍一下死信队列。
死信队列,英文缩写:DLX 。Dead Letter Exchange(死信交换机),当消息成为Dead message后,可以被重新发送到另一个交换机,这个交换机就是DLX。
消息成为死信的三种情况(面试常问)
代码实现
springboot入口
@SpringBootApplication
public class ProviderApplication {
public static void main(String[] args) {
SpringApplication.run(ProviderApplication.class, args);
}
}
rabbitmq配置类
@Configuration
public class RabbitmqConfig {
/**
* 正常交换机
*/
public static final String TEST_DLX_EXCHANGE = "test_dlx_exchange";
/**
* 正常队列
*/
private static final String TEST_DLX_QUEUE = "test_dlx_queue";
/**
* 死信交换机
*/
public static final String DLX_EXCHANGE = "springboot_dlx_exchange";
/**
* 死信队列
*/
private static final String DLX_QUEUE = "springboot_dlx_queue";
/**
* 声明正常交换机,用于测试死信队列
*/
@Bean
public Exchange testDlxExchange() {
return ExchangeBuilder.topicExchange(TEST_DLX_EXCHANGE).build();
}
/**
* 声明正常队列,用于测试死信队列
*/
@Bean
public Queue testDlxQueue() {
return QueueBuilder.durable(TEST_DLX_QUEUE)
//该队列过期时间,单位毫秒
.ttl(10000)
//绑定死信交换机
.deadLetterExchange(DLX_EXCHANGE)
//设置死信交换机routingKey
.deadLetterRoutingKey("dlx.haha")
//设置队列最大消息数量为10
.maxLength(10)
.build();
}
/**
* 声明死信交换机
* @return 死信交换机
*/
@Bean
public Exchange dlxExchange() {
return ExchangeBuilder.topicExchange(DLX_EXCHANGE).build();
}
/**
* 声明死信队列
* @return 死信队列
*/
@Bean
public Queue dlxQueue() {
return QueueBuilder.durable(DLX_QUEUE).build();
}
/**
* 绑定测试队列到测试交换机
* @param queue 测试队列
* @param exchange 测试交换机
*/
@Bean
public Binding bindingTtl(@Qualifier("testDlxQueue") Queue queue,
@Qualifier("testDlxExchange") Exchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with("test.dlx.#").noargs();
}
/**
* 绑定死信队列到死信交换机
* @param queue 死信队列
* @param exchange 死信交换机
*/
@Bean
public Binding bindingDlx(@Qualifier("dlxQueue") Queue queue,
@Qualifier("dlxExchange") Exchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with("dlx.#").noargs();
}
}
测试类
@RunWith(SpringRunner.class)
@SpringBootTest(classes = ProviderApplication.class)
public class RabbitmqTest {
@Autowired
private RabbitTemplate template;
/**
* 测试过期时间
* 消息一过期,立刻进入死信队列中
*/
@Test
public void testDlx1() {
template.convertAndSend("test_dlx_exchange", "test.dlx.haha", "测试队列的过期时间");
}
/**
* 测试死信队列的长度
*/
@Test
public void testDlx2() {
//有10条会进入正常队列中,有10条会立刻进入死信队列,剩下的10条会在10s后进入死信队列
for (int i = 0; i < 20; i++) {
template.convertAndSend("test_dlx_exchange", "test.dlx.haha", "测试队列的长度");
}
}
/**
* 测试消费者拒收
*/
@Test
public void testDlx3() {
template.convertAndSend("test_dlx_exchange", "test.dlx.haha", "测试队列的的消费者拒收");
}
}
springboot入口
@SpringBootApplication
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
}
配置类【因为要测试拒收,所以消费者必须要手动确认,配置稍微麻烦一点】
@Configuration
public class RabbitmqListenerConfig {
@Autowired
private CachingConnectionFactory cachingConnectionFactory;
@Autowired
private RabbitmqListener listener;
@Bean
public SimpleMessageListenerContainer listener() {
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(cachingConnectionFactory);
//设置最小并发数量(可不配)
container.setConcurrentConsumers(1);
//设置最大并发数量(可不配)
container.setMaxConcurrentConsumers(10);
//设置单位时间内消费多少条记录(可不配)
container.setPrefetchCount(10);
//设置确认方式,默认自动
//自动确认:NONE
//手动确认:MANUAL
//根据异常情况确认:AUTO
container.setAcknowledgeMode(AcknowledgeMode.MANUAL);
//监听队列的名称。监听的是正常队列的名称,不要写成死信队列了
container.setQueueNames("test_dlx_queue");
//设置消息监听类
container.setMessageListener(listener);
return container;
}
}
@Component
public class RabbitmqListener implements ChannelAwareMessageListener {
@Override
public void onMessage(Message message, Channel channel) throws Exception {
//获取消息id
long deliveryTag = message.getMessageProperties().getDeliveryTag();
System.out.println("处理业务。。。");
//拒接确认,将消息发送到死信队列
channel.basicNack(deliveryTag, true, false);
}
}
测试一:测试过期时间【10s后自动进入死信队列】
测试二:测试死信队列的长度。有10条会进入正常队列中,有10条会立刻进入死信队列,剩下的10条会在10s进入死信队列
测试三:测试消费者拒收
三个测试,一共为22条消息进入死信队列。
使用延迟队列,模拟下单后,30分钟未支付,取消订单,回滚库存。【30分钟太长了,这里模拟10s】
配置类
@Configuration
public class RabbitmqConfig {
/**
* ttl交换机
*/
public static final String TTL_EXCHANGE = "springboot_ttl_exchange";
/**
* ttl队列
*/
private static final String TTL_QUEUE = "springboot_ttl_queue";
/**
* 死信交换机
*/
public static final String DLX_EXCHANGE = "springboot_dlx_exchange";
/**
* 死信队列
*/
private static final String DLX_QUEUE = "springboot_dlx_queue";
/**
* 声明ttl交换机
*
* @return ttl交换机
*/
@Bean
public Exchange ttlExchange() {
return ExchangeBuilder.topicExchange(TTL_EXCHANGE).build();
}
/**
* 声明ttl队列,同时绑定死信交换机
*
* @return ttl队列
*/
@Bean
public Queue ttlQueue() {
return QueueBuilder.durable(TTL_QUEUE)
//该队列过期时间,单位毫秒
.ttl(10000)
//绑定死信交换机
.deadLetterExchange(DLX_EXCHANGE)
//设置死信交换机routingKey
.deadLetterRoutingKey("dlx.haha")
//设置队列最大消息数量为10
.maxLength(10)
.build();
}
/**
* 声明死信交换机
*
* @return 死信交换机
*/
@Bean
public Exchange dlxExchange() {
return ExchangeBuilder.topicExchange(DLX_EXCHANGE).build();
}
/**
* 声明死信队列
*
* @return 死信队列
*/
@Bean
public Queue dlxQueue() {
return QueueBuilder.durable(DLX_QUEUE).build();
}
/**
* 绑定ttl队列到ttl交换机
*
* @param queue ttl队列
* @param exchange ttl交换机
*/
@Bean
public Binding bindingTtl(@Qualifier("ttlQueue") Queue queue,
@Qualifier("ttlExchange") Exchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with("ttl.#").noargs();
}
/**
* 绑定死信队列到死信交换机
*
* @param queue 死信队列
* @param exchange 死信交换机
*/
@Bean
public Binding bindingDlx(@Qualifier("dlxQueue") Queue queue,
@Qualifier("dlxExchange") Exchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with("dlx.#").noargs();
}
}
测试类
@RunWith(SpringRunner.class)
@SpringBootTest(classes = ProviderApplication.class)
public class RabbitmqTest {
@Autowired
private RabbitTemplate template;
@Test
public void test1() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
template.convertAndSend(RabbitmqConfig.TTL_EXCHANGE,"ttl.test","这是生成订单时间:" + sdf.format(new Date()));
}
}
springboot入口
@SpringBootApplication
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
}
配置类
@Component
public class RabbitmqListener {
@RabbitListener(queues = "springboot_dlx_queue")
public void listener(Message message) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println(new String(message.getBody(), StandardCharsets.UTF_8));
System.out.println("消费消息时间:" + sdf.format(new Date()));
}
}
测试
队列中的消息10s后进入死信队列中,然后被消费者消费。
获取死信队列中的消息后,去判断订单状态,如果用户支付了,就什么都不做,如果用户没有支付,就取消订单,回滚库存。