一、业务场景
下订单后,30分组内没用付款就要关闭订单等一些需要延时执行的任务。使用定时任务轮询数据库则浪费太多资源
二、环境搭建
1.Rabbit MQ
这里我使用docker安装Rabbit MQ
docker run -d --name rabbitmq -p 5671:5671 -p 5672:5672 -p 4369:4369 -p25672:25672 -p 15671:15671 -p 15672:15672 rabbitmq:management
(PS:Rabbit MQ各个端口的作用)
端口 | 作用 |
---|---|
15672 | 管理界面ui使用的端口 |
15671 | 管理监听端口 |
5672,5671 | AMQP 0-9-1 without and with TLSclient端通信口 |
4369 | (epmd)epmd代表 Erlang端口映射守护进程,erlang发现口 |
25672 | ( Erlang distribution) server间内部通信口 |
2.Spring Boot整合Rabbit MQ
引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
rabbitmq:
addresses: localhost
port: 5672
virtual-host: /
#手动ack消息确认模式
listener:
simple:
acknowledge-mode: manual
三、实现业务
RabbitMQ队列本身是没有直接实现支持延迟队列的功能,但可以通过它的Time-To-Live Extensions(消息存活时间) 与 Dead Letter Exchange 的特性模拟出延迟队列的功能。
普通队列设置相关属性后就成为了死信队列
因此,我们需要1个交换机,一个死信队列和一个普通队列和两个路由Key实现延时队列功能
1.创建需要的交换机,队列,bind
RabbitMQ支持java客户端的操作,我们只需把交换机,队列,bind放到spring容器中,RabbitMQ就为自动为我们创建(不会覆盖原有的)
@Bean
public Queue orderDelayQueue()
{
//死信队列的属性
Map<String,Object> arguments = new HashMap<>();
//指定order-event-exchange为死信交换机,消息过期后将会投到死信交换机
arguments.put("x-dead-letter-exchange","order-event-exchange");
//指定死信的路由key
arguments.put("x-dead-letter-routing-key","order.release.order");
//ttl time to live 消息存活时间,为了测试,设置为1分钟
arguments.put("x-message-ttl",60000);
//队列名,是否持久,是否排他,是否自动删除
return new Queue("order.delay.queue",true,false,false,arguments);
}
@Bean
public Queue orderReleaseOrderQueue()
{
return new Queue("order.release.queue",true,false,false);
}
@Bean
public Exchange orderEventExchange()
{
return new TopicExchange("order-event-exchange",true,false);
}
@Bean
public Binding orderCreateOrderBingding()
{
return new Binding("order.delay.queue",
Binding.DestinationType.QUEUE,
"order-event-exchange",
"order.create.order",null);
}
@Bean
public Binding orderReleaseBingding()
{
return new Binding("order.release.queue",
Binding.DestinationType.QUEUE,
"order-event-exchange",
"order.release.order",null);
}
可以到ui界面看我们刚刚创建的这些玩意
2.业务代码
a) 订单创建后,放入死信队列
rabbitTemplate.convertAndSend("order-event-exchange", "order.create.order", order);
b)监听消息释放队列,判断是否需要关单
因为使用的手动消息确认机制,需要在代码中确认接收消息还是退回消息
@Service
@RabbitListener(queues = "order.release.queue")
public class OrderCloseListener {
@Autowired
OrderService orderService;
//关闭订单
@RabbitHandler
public void closeOrder(OrderEntity orderEntity, Channel channel, Message message) throws IOException {
try {
//只有未付款的订单要关闭
OrderEntity oreder = orderService.getById(orderEntity.getId());
if(oreder.getStatus() == OrderStatusEnum.CREATE_NEW.getCode()) {
orderService.closeOrder(orderEntity.getId());
System.out.println("接收到的死信,即为交易的订单:"+orderEntity);
}
//手动确认接收消息
channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
}
catch (Exception e)
{
//关单失败,退回消息给队列
channel.basicReject(message.getMessageProperties().getDeliveryTag(),true);
}
}
}
四、测试
新建一个订单
死信队列收到了发来的消息
1分钟后,消息死亡,来到普通队列。监听队列的Listenser执行光单操作