RabbitMQ要实现延迟消息
有两种方式:
- 通过死信队列的方式实现延迟消息。(需要两个交换机和两个队列)
- 通过延迟插件的方式实现延迟消息。延迟插件可以让我们直接定义一个延迟交换机,让交换机拥有延迟发送消息的能力,从而实现延迟消息。(只需要一个交换机和一个队列)
死信队列实现延迟消息
实现原理
首先要了解什么是死信队列。当消息在一个队列中变成死信
之后,他能被重新发送到另一个交换器中,这个交换器成为死信交换器(DLX)
,与该交换器绑定的队列称为死信队列
。
消息变成死信有下面几种情况:
- 消息被拒绝。
- 消息过期
- 队列达到最大长度
所以我们可以通过设置消息的过期时间来达到延迟消息的目的。我们需要两个交换机和两个队列,具体的实现原理图如下:
示例代码
以订单30分钟未支付则取消订单的场景作为示例背景。
消息队列枚举配置
@Getter
public enum QueueEnum {
/**
* 消息通知队列
*/
QUEUE_ORDER_CANCEL("mall.order.direct", "mall.order.cancel", "mall.order.cancel"),
/**
* 消息通知ttl队列
*/
QUEUE_TTL_ORDER_CANCEL("mall.order.direct.ttl", "mall.order.cancel.ttl", "mall.order.cancel.ttl");
/**
* 交换名称
*/
private String exchange;
/**
* 队列名称
*/
private String name;
/**
* 路由键
*/
private String routeKey;
QueueEnum(String exchange, String name, String routeKey) {
this.exchange = exchange;
this.name = name;
this.routeKey = routeKey;
}
}
定义交换机与队列
@Configuration
public class RabbitMqConfig {
/**
* 订单消息实际消费队列所绑定的交换机(死信交换机)
*/
@Bean
DirectExchange orderDirect() {
return (DirectExchange) ExchangeBuilder
.directExchange(QueueEnum.QUEUE_ORDER_CANCEL.getExchange())
.durable(true)
.build();
}
/**
* 订单延迟队列队列所绑定的交换机(延迟交换机)
*/
@Bean
DirectExchange orderTtlDirect() {
return (DirectExchange) ExchangeBuilder
.directExchange(QueueEnum.QUEUE_TTL_ORDER_CANCEL.getExchange())
.durable(true)
.build();
}
/**
* 订单实际消费队列(死信队列)
*/
@Bean
public Queue orderQueue() {
return new Queue(QueueEnum.QUEUE_ORDER_CANCEL.getName());
}
/**
* 订单延迟队列(延迟队列)
*/
@Bean
public Queue orderTtlQueue() {
return QueueBuilder
.durable(QueueEnum.QUEUE_TTL_ORDER_CANCEL.getName())
.withArgument("x-dead-letter-exchange", QueueEnum.QUEUE_ORDER_CANCEL.getExchange())//到期后转发的交换机(死信交换机)
.withArgument("x-dead-letter-routing-key", QueueEnum.QUEUE_ORDER_CANCEL.getRouteKey())//到期后转发的路由键
.build();
}
/**
* 将订单队列绑定到交换机
*/
@Bean
Binding orderBinding(DirectExchange orderDirect,Queue orderQueue){
return BindingBuilder
.bind(orderQueue)
.to(orderDirect)
.with(QueueEnum.QUEUE_ORDER_CANCEL.getRouteKey());
}
/**
* 将订单延迟队列绑定到交换机
*/
@Bean
Binding orderTtlBinding(DirectExchange orderTtlDirect,Queue orderTtlQueue){
return BindingBuilder
.bind(orderTtlQueue)
.to(orderTtlDirect)
.with(QueueEnum.QUEUE_TTL_ORDER_CANCEL.getRouteKey());
}
}
取消订单消息的发出者
@Component
public class CancelOrderSender {
private static Logger LOGGER =LoggerFactory.getLogger(CancelOrderSender.class);
@Autowired
private AmqpTemplate amqpTemplate;
public void sendMessage(Long orderId,final long delayTimes){
//给延迟队列发送消息
amqpTemplate.convertAndSend(QueueEnum.QUEUE_TTL_ORDER_CANCEL.getExchange(), QueueEnum.QUEUE_TTL_ORDER_CANCEL.getRouteKey(), orderId, new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
//给消息设置延迟毫秒值
message.getMessageProperties().setExpiration(String.valueOf(delayTimes));
return message;
}
});
LOGGER.info("send delay message orderId:{}",orderId);
}
}
取消订单消息的处理者
@Component
@RabbitListener(queues = "mall.order.cancel")
public class CancelOrderReceiver {
private static Logger LOGGER =LoggerFactory.getLogger(CancelOrderReceiver.class);
@Autowired
private OmsPortalOrderService portalOrderService;
@RabbitHandler
public void handle(Long orderId){
LOGGER.info("receive delay message orderId:{}",orderId);
portalOrderService.cancelOrder(orderId);
}
}
参考资料:
延迟插件实现延迟消息
实现原理
RabbitMQ可以通过安装延迟插件(安装方法阅读参考资料),让我们可以直接定义一个延迟交换机,让交换机拥有延迟发送消息的能力,从而实现延迟消息。(只需要一个交换机和一个队列)
示例代码
以订单30分钟未支付则取消订单的场景作为示例背景。
消息队列枚举配置
@Getter
public enum QueueEnum {
/**
* 订单超时取消队列
*/
QUEUE_ORDER_CANCEL("mall.delay.exchange","mall.order.cancel","mall.order.cancel");
/**
* 交换名称
*/
private final String exchange;
/**
* 队列名称
*/
private final String name;
/**
* 路由键
*/
private final String routeKey;
QueueEnum(String exchange, String name, String routeKey) {
this.exchange = exchange;
this.name = name;
this.routeKey = routeKey;
}
}
定义交换机与队列
@Configuration
public class RabbitMqConfig {
/**
* 定义插件延迟交换机
*/
@Bean
CustomExchange pluginDelayDirectExchange() {
//创建一个自定义交换机,可以发送延迟消息
Map<String, Object> args = new HashMap<>();
args.put("x-delayed-type", "direct");
return new CustomExchange(QueueEnum.QUEUE_ORDER_CANCEL.getExchange(), "x-delayed-message", true, false, args);
}
/**
* 订单超时取消队列
*/
@Bean
public Queue orderCancelQueue() {
return new Queue(QueueEnum.QUEUE_ORDER_CANCEL.getName());
}
/**
* 将订单超时取消队列绑定到插件延迟交换机
*/
@Bean
Binding orderBinding(CustomExchange pluginDelayDirectExchange, Queue orderCancelQueue) {
return BindingBuilder
.bind(orderCancelQueue)
.to(pluginDelayDirectExchange)
.with(QueueEnum.QUEUE_ORDER_CANCEL.getRouteKey())
.noargs();
}
}
取消订单消息的发出者
@Component
public class CancelOrderSender {
private static Logger LOGGER = LoggerFactory.getLogger(CancelOrderSender.class);
@Autowired
private AmqpTemplate amqpTemplate;
public void sendMessage(Long orderId, final long delayTimes) {
//发送延迟消息
amqpTemplate.convertAndSend(QueueEnum.QUEUE_ORDER_CANCEL.getExchange(), QueueEnum.QUEUE_ORDER_CANCEL.getRouteKey(), orderId, new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
//给消息设置延迟毫秒值
message.getMessageProperties().setHeader("x-delay", delayTimes);
return message;
}
});
LOGGER.info("send delay message orderId:{}", orderId);
}
}
取消订单消息的处理者
@Component
@RabbitListener(queues = "mall.order.cancel")
public class CancelOrderReceiver {
private static Logger LOGGER =LoggerFactory.getLogger(CancelOrderReceiver.class);
@Autowired
private OmsPortalOrderService portalOrderService;
@RabbitHandler
public void handle(Long orderId){
LOGGER.info("receive delay message orderId:{}",orderId);
portalOrderService.cancelOrder(orderId);
}
}
参考资料: