RabbitMQ实现延迟消息的两种方式

3,502 阅读4分钟

RabbitMQ要实现延迟消息有两种方式:

  1. 通过死信队列的方式实现延迟消息。(需要两个交换机和两个队列)
  2. 通过延迟插件的方式实现延迟消息。延迟插件可以让我们直接定义一个延迟交换机,让交换机拥有延迟发送消息的能力,从而实现延迟消息。(只需要一个交换机和一个队列)

死信队列实现延迟消息

实现原理

首先要了解什么是死信队列。当消息在一个队列中变成死信之后,他能被重新发送到另一个交换器中,这个交换器成为死信交换器(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);
    }
}

参考资料: