实现原理请移步至:小生不才-俏如来
资源部署
1. 交换机
- 交换机可以接收死信消息,部署一个即可
2. 队列
- 异步下单队列
- 异步支付队列
- 死信消息队列
- 延时消息队列(延时不是死信,延时队列不需要监听,而死信队列需要监听)
3. 路由键
都与同一个交换机进行绑定
- 异步下单路由键
- 异步支付路由键
- 死信消息路由键
- 延时消息路由键
业务流程
- 调用下单接口,使用rabbitmq进行异步下单,将订单信息封装,转发到订单队列中
- 消费者监听订单队列,将订单信息保存到数据库中,同时再次向延时队列发送消息
- 消费者不会监听延时队列,到达预定时间后,会将信息自动转发到死信队列中
若未完成支付
- 消费者监听到死信队列中的信息,从数据库中查询到订单状态为未支付,将订单状态改为取消
若完成支付
- 调用支付接口,将支付订单号等信息封装为对象,转发到支付队列中
- 消费者监听支付队列,将支付信息保存到数据库中,同时将订单状态改为已完成
- 消费者监听死信队列,从数据库中查询订单状态为已完成,不进行任何操作
延迟队列实战
1. 项目依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.5.RELEASE</version>
</parent>
<dependencies>
<!--引入web包,调用接口进行测试-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--springboot集成rabbitmq-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
2. yml配置
spring:
rabbitmq:
host: 127.0.0.1
port: 5672
username: guest
password: guest
virtualHost: /
server:
port: 80
3. 配置交换机和队列
package com.qiuming.rabbitmq.config;
import org.springframework.amqp.core.*;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
//rabbitMQ的配置
@Configuration
public class MQConfig {
//定义交换机
public static final String EXCHNAGE_DELAY = "EXCHNAGE_DELAY";
//定义订单队列
public static final String QUEUE_ORDER = "QUEUE_ORDER";
//定义死信队列
public static final String QUEUE_DELAY = "QUEUE_DELAY";
//定义支付队列
public static final String QUEUE_PAY = "QUEUE_PAY";
//定义非监听队列,即用于延时消息的发送
public static final String QUEUE_UNKNOW = "QUEUE_UNKNOW";
//定义订单队列路由键
public static final String ROUTINGKEY_QUEUE_ORDER = "ROUTINGKEY_QUEUE_ORDER";
//定义死信队列路由键
public static final String ROUTINGKEY_QUEUE_DELAY = "ROUTINGKEY_QUEUE_DELAY";
//定义支付队列路由键
public static final String ROUTINGKEY_QUEUE_PAY = "ROUTINGKEY_QUEUE_PAY";
//定义非监听队列路由键,即用户绑定延时消息
public static final String ROUTINGKEY_QUEUE_UNKNOW = "ROUTINGKEY_QUEUE_UNKNOW";
//定义交换机
@Bean
public Exchange exchangeDelay(){
return ExchangeBuilder.topicExchange(EXCHNAGE_DELAY).durable(true).build();
}
//该队列为订单队列
@Bean(QUEUE_ORDER)
public Queue queueOrder(){
return new Queue(QUEUE_ORDER,true);
}
//该队列接收死信交换机转发过来的消息
@Bean(QUEUE_DELAY)
public Queue queueDelay(){
return new Queue(QUEUE_DELAY,true);
}
//该队列为支付队列
@Bean(QUEUE_PAY)
public Queue queuePay(){
return new Queue(QUEUE_PAY,true);
}
//该队列为延时队列
@Bean(QUEUE_UNKNOW)
public Queue queueUnKnow(){
Map<String,Object> map = new HashMap<>();
//过期的消息给哪个交换机的名字
map.put("x-dead-letter-exchange", EXCHNAGE_DELAY);
//死信交换机把消息个哪个个routingkey
map.put("x-dead-letter-routing-key", ROUTINGKEY_QUEUE_DELAY);
//队列过期时间10s
map.put("x-message-ttl", 10000);
return new Queue(QUEUE_UNKNOW,true,false,false,map);
}
//用路由键绑定交换机和队列
@Bean
public Binding queueOrderBinding(){
return BindingBuilder.bind(queueOrder()).to(exchangeDelay()).with(ROUTINGKEY_QUEUE_ORDER).noargs();
}
@Bean
public Binding queueDelayBinding(){
return BindingBuilder.bind(queueDelay()).to(exchangeDelay()).with(ROUTINGKEY_QUEUE_DELAY).noargs();
}
@Bean
public Binding queuePayBinding(){
return BindingBuilder.bind(queuePay()).to(exchangeDelay()).with(ROUTINGKEY_QUEUE_PAY).noargs();
}
@Bean
public Binding queueUnKnowBinding(){
return BindingBuilder.bind(queueUnKnow()).to(exchangeDelay()).with(ROUTINGKEY_QUEUE_UNKNOW).noargs();
}
//定义json消息转换器
@Bean
public MessageConverter messageConverter(){
return new Jackson2JsonMessageConverter();
}
}
4. 定义接口
定义两个接口用于测试,分别是下单接口和支付接口
package com.qiuming.rabbitmq.controller;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
import static com.qiuming.rabbitmq.config.MQConfig.*;
/**
* rabbitmq控制层
* @author qiuming
* @date 2021/03/22 14:03
**/
@RestController
public class RabbitMQController {
@Autowired
private RabbitTemplate rabbitTemplate;
@GetMapping("/rabbit-order")
public void order(){
rabbitTemplate.convertAndSend(
EXCHNAGE_DELAY,
ROUTINGKEY_QUEUE_ORDER,
"已生成订单,请在10s内完成支付");
}
@GetMapping("/rabbit-pay")
public void pay(){
Map<String, Object> map = new HashMap<>(4);
map.put("orderSn", 1L);
map.put("message", "已完成支付");
map.put("success", true);
rabbitTemplate.convertAndSend(
EXCHNAGE_DELAY,
ROUTINGKEY_QUEUE_PAY,
map);
}
}
5. 创建消费者
package com.qiuming.rabbitmq.consumer;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
import static com.qiuming.rabbitmq.config.MQConfig.*;
/**
* 消费者
* @author qiuming
* @date 2021/03/22 14:10
**/
@Component
public class Consumer {
@Autowired
private RabbitTemplate rabbitTemplate;
//未连接数据库,使用静态变量进行测试
private static Boolean success = null;
/**
* 死信队列,延时时间到达进入
* @param message
*/
@RabbitListener(queues = QUEUE_DELAY)
public void listener1(@Payload Map<String,Object> map,Message message){
//从数据库中查询数据,看订单是否支付完成
System.out.println("10s到了,死信队列触发");
if (success!=null && success) {
System.out.println("支付已成功,死信队列不执行任何操作");
success = null;
return ;
}
//订单在数据库中处于未完成
System.out.println(map.get("message"));
success = null;
}
/**
* 生成订单队列
* @param message
*/
@RabbitListener(queues = QUEUE_ORDER)
public void listener2(@Payload String str,Message message) {
System.out.println(str);
//设置10s的支付时间,失败支付则计入死信队列
Map<String, Object> map = new HashMap<>(4);
success = false;
map.put("orderSn", 1L);
map.put("message", "未在规定时间内完成支付,订单取消");
rabbitTemplate.convertAndSend(EXCHNAGE_DELAY,ROUTINGKEY_QUEUE_UNKNOW,map);
}
/**
* 支付订单队列
*/
@RabbitListener(queues = QUEUE_PAY)
public void listener3(@Payload Map<String,Object> map, Message message) throws InterruptedException {
success = (boolean)map.get("success");
if (success) {
System.out.println("order sn = "+map.get("orderSn")+",message = "+"支付成功");
}else {
System.out.println("支付失败");
}
}
}
6. 启动类
package com.qiuming.rabbitmq;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
/**
* rabbitmq启动类
* @author qiuming
* @date 2021/03/22 14:03
**/
@SpringBootApplication
public class RabbitMQApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(RabbitMQApplication.class);
}
}
打完收工(测试)
下单接口:http://localhost/rabbit-order
支付接口:http://localhost/rabbit-pay
1. 调用下单接口,不调用支付接口 |
---|
![]() |
2. 先调用下单接口,再调用支付接口 |
---|
![]() |
此外:可以根据服务器配置,适当减少50ms-100ms的延时时间,使得业务更加精确