实现mq延时队列(订单延时取消)

本文介绍如何使用RabbitMQ实现延时队列,通过死信队列和TTL消息实现订单超时自动取消,减轻应用服务器负担,同时指出可能的消息堆积和运维预警问题。

简单实现mq延时队列
1.RabbitMqConfiguration (mq配置类)

@Configuration
@Slf4j
public class RabbitMqConfiguration{

    @Bean
    public ConnectionFactory connectionFactory(@Value("${rabbitmq.host}") String host,
        @Value("${rabbitmq.port}") int port, @Value("${rabbitmq.username}") String username,
        @Value("${rabbitmq.password}") String password,
        @Value("${rabbitmq.publisher-confirms}") Boolean publisherConfirms) {
        CachingConnectionFactory connectionFactory = new CachingConnectionFactory();
        connectionFactory.setHost(host);
        connectionFactory.setPort(port);
        connectionFactory.setUsername(username);
        connectionFactory.setPassword(password);
        connectionFactory.setPublisherConfirms(publisherConfirms);
        log.info("RabbitMq connectionFactory is finash start!");
        return connectionFactory;
    }

    @SuppressWarnings("AlibabaRemoveCommentedCode")
    @Bean(name = MqProperties.CONTAINER_FACTORY)
    public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(ConnectionFactory connectionFactory) {
        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        factory.setConnectionFactory(connectionFactory);
        factory.setAcknowledgeMode(AcknowledgeMode.NONE);
        // 每个队列设置3个消费者避免单个消费者阻塞而失败
        factory.setConcurrentConsumers(3);
        factory.setMaxConcurrentConsumers(5);
        return factory;
    }

    @Bean
    public AmqpTemplate amqpTemplate(@Autowired ConnectionFactory connectionFactory) {
        Logger log = LoggerFactory.getLogger(RabbitTemplate.class);
        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        rabbitTemplate.setEncoding("UTF-8");
        // 消息发送失败返回到队列中,yml需要配置 publisher-returns: true
        rabbitTemplate.setMandatory(true);
        rabbitTemplate.setReturnCallback((message, replyCode, replyText, exchange, routingKey) -> {
            String correlationId = message.getMessageProperties().getCorrelationId();
            if (log.isDebugEnabled()) {
                log.debug("amqp send fail message<{}> replyCode<{}> reason<{}> exchange<{}>  routeKey<{}>",
                    correlationId, replyCode, replyText, exchange, routingKey);
            }
        });
        // 消息确认,yml需要配置 publisher-confirms: true
        rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> {
            if (ack) {
                if (log.isDebugEnabled()) {
                    log.debug("amqp send success id<{}>", correlationData.getId());
                }
            } else {
                if (log.isDebugEnabled()) {
                    log.debug("amqp send fail reason<{}>", cause);
                }
            }
        });
        return rabbitTemplate;
    }

    /**
     * 构建管理类
     *
     * @param connectionFactory
     * @param mqProperties
     * @return
     */
    @Bean
    public AmqpAdmin amqpAdmin(@Autowired ConnectionFactory connectionFactory, @Autowired MqProperties mqProperties) {
        AmqpAdmin amqpAdmin = new RabbitAdmin(connectionFactory);
        
        // 预提现死信队列
        // 死信交换机
        DirectExchange exchange =
            (DirectExchange)ExchangeBuilder.directExchange(mqProperties.getExchangeOrderPre()).build();
        amqpAdmin.declareExchange(exchange);

        // TTL消息队列
        Map<String, Object> arguments = new HashMap<>();
        arguments.put("x-dead-letter-exchange", mqProperties.getExchangeOrderPre());
        arguments.put("x-dead-letter-routing-key", mqProperties.getQueueOrderDRT());
        Queue orderPreDLX = QueueBuilder.durable(mqProperties.getSendOrderPreDLX()).withArguments(arguments).build();
        amqpAdmin.declareQueue(orderPreDLX);

        // 消息队列的交换机绑定
        amqpAdmin.declareBinding(BindingBuilder.bind(orderPreDLX).to(exchange).with(mqProperties.getSendOrderPreDLX()));

        // 死信转发队列
        Queue orderPreDRT = QueueBuilder.durable(mqProperties.getQueueOrderDRT()).withArguments(arguments).build();
        amqpAdmin.declareQueue(orderPreDRT);

        // 转发队列的交换机绑定
        amqpAdmin.declareBinding(BindingBuilder.bind(orderPreDRT).to(exchange).with(mqProperties.getQueueOrderDRT()));

        return amqpAdmin;
    }

}

2.MqSendService(mq服务类,发送mq消息)

/**
 * 有有效期的的队列
 * 
 * @param exchange 交换机名称
 * @param queueName 队列名称
 * @param message消息内容
 * @param times 延迟时间 单位毫秒
 */
@Async
public void send(String exchange, String queueName, String message, long times) {
    // 消息发送到死信队列上,当消息超时时,会发生到转发队列上,转发队列根据下面封装的queueName,把消息转发的指定队列上
    // 发送前,把消息进行封装,转发时应转发到指定 queueName 队列上
    MessagePostProcessor processor = new MessagePostProcessor() {
        @Override
        public Message postProcessMessage(Message message) {
            message.getMessageProperties().setExpiration(times + "");
            return message;
        }
    };
    amqpTemplate.convertAndSend(exchange, queueName, message, processor);
}

3.OrderService(创建订单时发送死信消息)

 // 启动延时队列 (为了准时取消,须保证mq服务时间与api服务时间一致)
        mqSendService.send(mqProperties.getExchangeOrderPre(), mqProperties.getSendOrderPreDLX(), orderId.toString(),
            loanPreStatusDuration);

4.LoanMqListener 接受死信转发队列消息,取消超时订单

@Component
@Slf4j
public class LoanMqListener extends BaseMqListener {
    @Autowired
    private LoanHandleService loanHandleService;

    /**
     * 订单到期处理
     * 
     * @param message
     */
    @RabbitListener(queues = "${mq.response.order.pre.repeat.trade}", containerFactory = MqProperties.CONTAINER_FACTORY)
    public void preLoanOverTime(String content) {
        log.info("订单:{}延时取消", content);
        loanHandleService.cancelLoanByPreTimeOut(Long.parseLong(content));
    }
}

5.补充.MqProperties

/**
 * Mq 配置
 */
@Component
@Data
@ToString
public class MqProperties {

    /**
     * Mq 容器 factory
     */
    public static final String CONTAINER_FACTORY = "containerFactory";
    /**
     * 订单死信队列
     */
    @Value("${mq.request.order.pre.dead.letter}")
    private String sendOrderPreDLX;

    /**
     * 订单死信转发队列
     */
    @Value("${mq.response.order.pre.repeat.trade}")
    private String queueOrderDRT;

    /**
     * 订单死信转发队列交换机
     */
    @Value("${mq.exchange.order.pre}")
    private String exchangeOrderPre;

}

6.总结:
优点:

  1. 可以实现实时取消订单,及时恢复订单占用资源(如订单中的商品),不占用应用服务器资源

缺点:

  1. 可能会导致消息大量堆积
  2. 死信消息可能会导致运维预警,需要沟通

欢迎补充

实现一个基于消息队列(MQ)的延时队列时,通常需要使用到消息队列提供的延时消息功能。不同的消息队列产品提供了不同的实现方式。以下是使用RabbitMQ实现延时队列的一个简单示例,RabbitMQ通过死信交换机(Dead Letter Exchanges, DLX)和消息的存活时间(time-to-live, TTL)来实现延时队列。 1. 定义一个死信交换机DLX(Dead Letter Exchange)和一个队列DQL Queue,并将队列绑定到DLX上。 2. 发送消息时,设置消息的TTL(存活时间)。如果消息在TTL时间内没有被消费,它会变成死信(Dead Letter)。 3. 死信会被发送到DLX,然后可以根据需要将死信转发到其他队列或者进行处理。 具体步骤如下: 1. 声明一个死信交换机: ```java String dlxName = "dlx"; channel.exchangeDeclare(dlxName, BuiltinExchangeType.DIRECT); ``` 2. 声明一个队列,并设置参数使它成为一个延时队列。队列绑定到死信交换机,并设置消息的TTL为10秒: ```java String queueName = "delayQueue"; Map<String, Object> args = new HashMap<>(); // 设置队列的死信交换机 args.put("x-dead-letter-exchange", dlxName); // 设置消息的存活时间,这里为10秒 args.put("x-message-ttl", 10000); channel.queueDeclare(queueName, true, false, false, args); ``` 3. 发送一条消息,设置消息的TTL为10秒: ```java String message = "延时消息"; AMQP.BasicProperties props = new AMQP.BasicProperties().builder() .expiration("10000") // TTL设置为10秒 .build(); channel.basicPublish("", queueName, props, message.getBytes()); ``` 4. 死信交换机会在消息过期后将消息发送到绑定的队列,此时可以对这个死信进行消费。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值