本篇这主要是利用rabbitMQ 的高级特性,通过springBoot框架来实践订单超时取消功能的实现:
框架截图如下:
1、rabbitMq通过yml文件配置简单属性:
server:
port: 8021
spring:
#给项目来个名字
application:
name: rabbitmq-provider
rabbitmq:
port: 5672
host: 127.0.0.1
username: guest
password: guest
virtual-host: /
2、SpringBoot启动类Bootstrap:
/**
* 启动类
* @author huoyajing
*/
@SpringBootApplication(scanBasePackages = {"com.huohuo.info"})
public class Bootstrap {
public static void main(String[] args) {
SpringApplication.run(Bootstrap.class, args);
}
}
3、rabbitMQ 配置类,配置queue和exchange之间的关系:
/**
* @description:rabbit延迟消息交换机和队列配置
* @time: 2021/9/12 4:40 下午
*/
@Configuration
public class DelayRabbitConfig {
/**
* 延迟队列 TTL 名称
*/
public static final String USER_ORDER_DELAY_QUEUE = "queue.user.order.delay";
/**
* DLX,dead letter发送到的 exchange
* 延时消息就是发送到该交换机的
*/
public static final String USER_ORDER_DELAY_EXCHANGE = "exchange.user.order.delay";
/**
* routing key 名称
* 具体消息发送在该 routingKey 的
*/
public static final String USER_ORDER_DELAY_ROUTING_KEY = "routingkey.user.order.delay";
/**
* 立即消费的队列名称
*/
public static final String USER_ORDER_QUEUE = "queue.user.order.immediate";
/**
* 立即消费的exchange
*/
public static final String USER_ORDER_EXCHANGE = "exchange.user.order.immediate";
/**
* 立即消费 routing key 名称
*/
public static final String USER_ORDER_ROUTING_KEY = "routingkey.user.order.immediate";
/**
* 订单实际消费-队列
*
* @return
*/
@Bean
public Queue orderImmediateQueue() {
return new Queue(USER_ORDER_QUEUE, true);
}
/**
* 订单延迟消费-队列
*
* @return
*/
@Bean
public Queue orderDelayQueue() {
Map<String, Object> map = new HashMap<>(2);
// x-dead-letter-exchange 声明了队列里的死信转发到的DLX名称(正常exchange)
map.put("x-dead-letter-exchange", USER_ORDER_EXCHANGE);
// x-dead-letter-routing-key 声明了这些死信在转发时携带的 routing-key 名称(正常routing)
map.put("x-dead-letter-routing-key", USER_ORDER_ROUTING_KEY);
return new Queue(USER_ORDER_DELAY_QUEUE, true, false, false, map);
}
/**
* 订单消息直接消费-交换机
*
* @return
*/
@Bean
public DirectExchange orderImmediateExchange() {
return new DirectExchange(USER_ORDER_EXCHANGE,true,false);
}
/**
* 订单延迟队列-交换机
*
* @return
*/
@Bean
public DirectExchange orderDelayExchange() {
return new DirectExchange(USER_ORDER_DELAY_EXCHANGE,true,false);
}
/**
* 延迟-绑定queue和exchange
*
* @return
*/
@Bean
public Binding bindDelayQueueExchange() {
return BindingBuilder
.bind(orderDelayQueue())
.to(orderDelayExchange())
.with(USER_ORDER_DELAY_ROUTING_KEY);
}
/**
* 立即消费-绑定queue和exchange
*
* @return
*/
@Bean
public Binding bindImmediateQueueExchange() {
return BindingBuilder
.bind(orderImmediateQueue())
.to(orderImmediateExchange())
.with(USER_ORDER_ROUTING_KEY);
}
4、订单超时取消-将数据交给消费者
/**
* @description:订单超时取消-将数据交给消费者
* @author: huoyajing
* @time: 2021/9/12 5:42 下午
*/
@Component
public class DelaySender {
private AmqpTemplate amqpTemplate;
@Autowired
public void delayProducer(AmqpTemplate amqpTemplate){
this.amqpTemplate=amqpTemplate;
}
public void delaySend(Order order,String dateString) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// convertAndSend参数说明:1、延迟交换机名 2、延迟routing-key 3、order对象 4、message消息
// 交换机会马上把所有的信息都交给所有的消费者
this.amqpTemplate.convertAndSend(DelayRabbitConfig.USER_ORDER_DELAY_EXCHANGE, DelayRabbitConfig.USER_ORDER_DELAY_ROUTING_KEY, order, message -> {
message.getMessageProperties().setExpiration(dateString+"");
System.out.println("send orderInfo:"+order.getOrderName()+";订单生成时间"+sdf.format(new Date()) + " Delay sent.");
return message;
});
}
}
5、、需要处理的订单信息,当用户下订单的时候,则把订单信息放入rabbitMQ 中
/**
* @description:订单数据,触发场景
* @author: huoyajing
* @time: 2021/9/12 7:52 下午
*/
@RestController
public class DelayController {
private DelaySender delaySender;
@Autowired
public void setDelayProducer(DelaySender delaySender) {
this.delaySender = delaySender;
}
@GetMapping("/sendDelay")
public Object sendDelay() {
Order order1 = new Order();
order1.setOrderId("001");
order1.setOrderStatus(1);
order1.setOrderName("huohuo订单1");
//订单1睡10秒
delaySender.delaySend(order1,"10000");
//让服务一直挂起,不然,接收消息时,服务已经停了
while (true) {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
6、消费消息,符合条件的处理逻辑,不符合条件的订单直接忽略
/**
* @description:订单超时取消-消费者
* @author: huoyajing
* @time: 2021/9/12 5:53 下午
*/
@Component
public class DelayReceiver {
@RabbitListener(queues = DelayRabbitConfig.USER_ORDER_QUEUE)
@RabbitHandler
public void orderDelayQueue(Order order) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println("receive orderInfo:"+order.getOrderName()+";收到延时消息时间:" + sdf.format(new Date()) + " Delay sent.");
if (null != order && null != order.getOrderStatus()) {
//0待支付;1已完成;2已取消
if (0 == order.getOrderStatus()) {
//实现对应的取消功能(核心逻辑处理)
System.out.println("该订单未支付,取消订单:" + order);
} else if (1 == order.getOrderStatus()) {
System.out.println("该订单已完成支付:" + order);
} else if (2 == order.getOrderStatus()) {
System.out.println("该订单已取消:" + order);
}
}
}
}
启动bootstrap,项目启动好之后,利用swagger或者postman即可以调用接口,触发订单接口,然后则实现了生产消费的流程,结果如下:
send orderInfo:huohuo订单1;订单生成时间2021-09-19 17:00:33 Delay sent.
receive orderInfo:huohuo订单1;收到延时消息时间:2021-09-19 17:00:43 Delay sent.
该订单已完成支付:Order[id=001,name=huohuo订单1,state=1]
订单生成的时间为:17点00分33秒,代码设置了10秒延后,也就是实际项目中设置的半小时或者十分钟取消订单的时间,延后消息为17点00分43秒,由于订单1是已完成的,所以直接忽略,若是状态为0,待支付,则走取消订单,释放库存的逻辑。
当多个订单过来,设置不同休眠时间,会怎样呢?
/**
* @description:订单数据,触发场景
* @author: huoyajing
* @time: 2021/9/12 7:52 下午
*/
@RestController
public class DelayController {
private DelaySender delaySender;
@Autowired
public void setDelayProducer(DelaySender delaySender) {
this.delaySender = delaySender;
}
@GetMapping("/sendDelay")
public Object sendDelay() {
Order order1 = new Order();
order1.setOrderId("001");
order1.setOrderStatus(1);
order1.setOrderName("huohuo订单1");
Order order2 = new Order();
order2.setOrderId("002");
order2.setOrderStatus(0);
order2.setOrderName("huohuo订单2");
Order order3 = new Order();
order3.setOrderId("003");
order3.setOrderStatus(0);
order3.setOrderName("huohuo订单3");
//订单1睡10秒
delaySender.delaySend(order1,"10000");
//订单2睡4秒
delaySender.delaySend(order2,"4000");
//订单3睡3秒
delaySender.delaySend(order3,"3000");
//让服务一直挂起,不然,接收消息时,服务已经停了
while (true) {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
send orderInfo:huohuo订单1;订单生成时间2021-09-19 17:04:40 Delay sent.
send orderInfo:huohuo订单2;订单生成时间2021-09-19 17:04:40 Delay sent.
send orderInfo:huohuo订单3;订单生成时间2021-09-19 17:04:40 Delay sent.
receive orderInfo:huohuo订单1;收到延时消息时间:2021-09-19 17:04:50 Delay sent.
该订单已完成支付:Order[id=001,name=huohuo订单1,state=1]
receive orderInfo:huohuo订单2;收到延时消息时间:2021-09-19 17:04:50 Delay sent.
该订单未支付,取消订单:Order[id=002,name=huohuo订单2,state=0]
receive orderInfo:huohuo订单3;收到延时消息时间:2021-09-19 17:04:50 Delay sent.
该订单未支付,取消订单:Order[id=003,name=huohuo订单3,state=0]
如上测试,我们设置订单2,3休眠时间分别为4秒和3秒,感觉应该是先执行订单3,再执行订单2,最后执行订单1,但是测试结果出来我们发现一个很神奇的过程,就是订单2,3不管休眠多少,最终是根据订单1执行后的顺序执行的。也就是订单2,3被阻塞了。
再试一下:
//订单1睡10秒
delaySender.delaySend(order1,"2000");
//订单2睡4秒
delaySender.delaySend(order2,"10000");
//订单3睡3秒
delaySender.delaySend(order3,"3000");
如上结果执行完订单1,执行订单2,订单3等候订单2
send orderInfo:huohuo订单1;订单生成时间2021-09-19 17:14:36 Delay sent.
send orderInfo:huohuo订单2;订单生成时间2021-09-19 17:14:36 Delay sent.
send orderInfo:huohuo订单3;订单生成时间2021-09-19 17:14:36 Delay sent.
receive orderInfo:huohuo订单1;收到延时消息时间:2021-09-19 17:14:38 Delay sent.
该订单已完成支付:Order[id=001,name=huohuo订单1,state=1]
receive orderInfo:huohuo订单2;收到延时消息时间:2021-09-19 17:14:46 Delay sent.
该订单未支付,取消订单:Order[id=002,name=huohuo订单2,state=0]
receive orderInfo:huohuo订单3;收到延时消息时间:2021-09-19 17:14:46 Delay sent.
该订单未支付,取消订单:Order[id=003,name=huohuo订单3,state=0]
为何会出现这种情况,其实很简单,Queue的性质导致的,纵然设置的时间短,但是出不了队列也没办法,就好比买东西似的,谁去的早谁先买,而不是谁花的钱多谁就有优先权。
PS:写代码还是得认真,不能觉得理所当然,本来很简单的一个demo,结果由于自己的粗心大意,然后找了半天问题,却硬是不晓得哪里有问题,也么有报错,就是消费者无反应,就是不消费。其实就是代码写错了,对于静态变量的编写以及引用,由于只有部分字段不一样,如果不仔细,很容易就引用错了,就比如我应该引入queue,但是却引用了routing,自己却硬没发现,也是醉了,哈哈。