基于RabbitMQ的TTL(延迟队列+死信队列),实现支付订单超时自动取消

144 阅读6分钟

大家好!今天我们将一起探讨一个高级Java技术话题——基于RabbitMQ的TTL(延迟队列+死信队列),实现支付订单超时自动取消。这个主题对于许多企业级应用来说都非常重要,因为它可以帮助我们解决一些具有挑战性的业务问题。
什么是延时队列?首先它要具有队列的特性,再给它附加一个延迟消费队列消息的功能,延迟队列相对比普通队列,区别就在延时的特性上,普通队列先进先出,按入队顺序进行处理,而延时队列中的元素在入队时会指定一个延时时间,希望能够在指定时间到了以后处理。
在现实生活中,很多场景都需要用到延迟队列。以下就是使用延迟队列的企业级实战场景:
业务类场景:     1. 支付订单成功后,指定时间以后将支付结果通知给接入方。    2. 淘宝订单业务:下单之后如果三十分钟之内没有付款就自动取消订单。    3. 美团或饿了吧订餐通知:下单成功后60s之后给用户发送短信通知。    4. 会议预定系统,在预定会议开始前半小时通知所有预定该会议的用户。    5. IT问题工单超过24小时未处理,则自动拉企微群提醒相关责任人。    6. 用户下单外卖以后,距离超时时间还有10分钟时提醒外卖小哥即将超时。
技术框架场景:
1. 关闭空闲连接。服务器中,有很多客户端的连接,空闲一段时间之后需要关闭之。    2. 缓存。缓存中的对象,超过了空闲时间,需要从缓存中移出。    3. 任务超时处理。在网络协议滑动窗口请求应答式交互时,处理超时未响应的请求等。 假设我们正在开发一个电商网站,用户可以在该网站上购买商品。为了提供更好的用户体验,我们在用户下单后60s未支付,这时,我们可以通过RabbitMQ的TTL(延迟队列+死信队列)超时未支付自动取消订单

实现步骤如下:

  1. 创建普通队列:命名为'order.release.order.queue',用于处理正常的支付订单。

  2. 创建延迟队列:命名为'order.create.order',并设置延迟时间(例如60秒),用于存放需要超时处理的订单。

  3. 创建死信队列:命名为'order.delay.queue',用于存放处理失败或超时的订单。

4.构建交换机对象:将'order.create.order的死信交换器设置为'

order.event.exchange',以便在订单超时时将订单发送到'order.delay.queue'队列。

5.发布订单消息:将支付订单的消息发布到'order.release.order.queue'队列,由消费者进行正常的处理。

  1. 发布延迟消息:将需要超时处理的订单消息发布到'order.delay.queue'队列,并设置消息的TTL(Time To Live),即消息在队列中的存活时间。当消息存活时间达到预设的超时时间时,消息将被发送到'order.release.order'队列。

  2. 处理超时订单:在'order.delay.queue'队列中,消费者在处理订单时需要判断订单是否已经超时。如果订单已经超时,则将订单消息发布到'order.release.order.queue'队列中,由专门的处理程序进行后续的取消操作。

通过以上步骤,基于RabbitMQ的TTL(延迟队列+死信队列)可以实现支付订单的超时自动取消功能。在实现过程中需要根据具体的业务需求进行调整和优化,以保证系统的稳定性和可靠性。

由于篇幅和格式的限制,我无法在这里提供完整的代码。但我可以为您提供部分代码的示例,以展示如何实现基于RabbitMQ的TTL(延迟队列+死信队列),实现支付订单超时自动取消。

@Configuration
public class OrderMqConfig {

    //死信队列
    private static final String DELAY_QUEUE = "delay_queue";
    //正常队列
    private static final String NORMAL_QUEUE = "normal_queue";
    //死信交换器
    private static final String DELAY_EXCHANGE = "delay_exchange";
    //死信路由键
    private static final String DELAY_KEY = "delay_key";

    // 构建一个死信队列对象
    @Bean
    public Queue orderDelayQueue(){
        //构造参数
        Map<String,Objectarguments = new HashMap<>();
        //为死信队列设置队列交换器:order.event.exchange
        arguments.put("x-dead-letter-exchange","order.event.exchange");
        //为死信队列设置队列路由key:order.release.order
        arguments.put("x-dead-letter-routing-key","order.release.order");
        //为死信队列中的消息设置过期时间:1 分钟
        arguments.put("x-message-ttl",60000);
        /**
         *    String name, 队列名字
         *    boolean durable, 队列是否持久化
         *    boolean exclusive, 队列是否排他的
         *    boolean autoDelete, 队列是否删除
         *    @Nullable Map<String, Object> arguments 自定义属性
         */
        // 构建对象,塞入相关参数
        Queue queue = new Queue("order.delay.queue",
                true,
                false,
                false,
                arguments
                );
        return queue;
    }

    //构建普通的队列
    @Bean
    public Queue orderReleaseOrderQueue(){
        // 构建对象,塞入相关参数
        Queue queue = new Queue("order.release.order.queue",
                true,
                false,
                false,
                null
        );
        return queue;
    }

    // 构建交换机对象
    @Bean
    public Exchange orderEventExchange(){
        /**
         * String name, 交换机名称
         * boolean durable, 队列是否持久化
         * boolean autoDelete, 队列是否删除
         */
        TopicExchange exchange = new TopicExchange(
                "order.event.exchange",
                true,
                false);
        return exchange;
    }

    // 将交换机与死信队列绑定
    @Bean
    public Binding orderCreateOrderBinding(){
        /**
         * String destination, 目的地
         * DestinationType destinationType, 目的地类型
         * String exchange, 交换机
         * String routingKey, 路由key
         * @Nullable Map<String, Object> arguments 参数
         */
        Binding binding = new Binding("order.delay.queue",
                Binding.DestinationType.QUEUE,
                "order.event.exchange",
                "order.create.order",
                null
                );
        return binding;
    }

    // 将交换机与普通队列绑定
    @Bean
    public Binding orderReleaseOrderBinding(){
        /**
         * String destination, 目的地
         * DestinationType destinationType, 目的地类型
         * String exchange, 交换机
         * String routingKey, 路由key
         * @Nullable Map<String, Object> arguments 参数
         */
        Binding binding = new Binding("order.release.order.queue",
                Binding.DestinationType.QUEUE,
                "order.event.exchange",
                "order.release.order",
                null
        );
        return binding;
    }



}
@Service
@Slf4j
public class OrderServiceImpl implements OrderService {
    @Resource
    private OrderMapper orderMapper;

    @Resource
    private RabbitTemplate rabbitTemplate;

    @Override
    public RespResult createOrder(OrderCreateDTO orderCreateDTO, HttpServletResponse response) {
        OrderInfo orderInfo = new OrderInfo();
        orderInfo.setId(String.valueOf(DCGenIdUtil.genIdBySnowflake()));
        orderInfo.setOrderTime(new Date());
        String orderNo = GenerateSequenceUtil.generateSequenceNo();//20230404160605001
        if (!StringUtils.isEmpty(orderNo)){
            orderInfo.setOrderNo(orderNo);
        }
        orderInfo.setStatus(Constant.ORDER_CREATE);
        //拷贝对象(只拷贝不为空的属性)
        DCBeanUtil.copyNotNull(orderInfo, orderCreateDTO);
        //保存订单
        orderMapper.saveOrderInfo(orderInfo);

        orderCreateDTO.setOrderNo(orderInfo.getOrderNo());

        //将订单保存到延迟队列中
        rabbitTemplate.convertAndSend("order.event.exchange","order.create.order",orderInfo);


        return RespResult.success(orderCreateDTO);
    }

    /**
     * 功能:job处理超时的订单
     */
    @Override
    public void cancelOrderForJob() {
        OrderReqDTO reqDTO = new OrderReqDTO();
        reqDTO.setStatus(Constant.ORDER_CREATE);
        reqDTO.setIsCancel(1);
        //查询前刚产生的100条,不要整表查,不然速度太慢
        //reqDTO.setLimitRecords(100);
        List<OrderInfo> orderList =  orderMapper.findList(reqDTO);
        if(CollUtil.isEmpty(orderList)){
            log.warn("没有自动取消的订单!时间:{}", LocalDateTime.now());
            return;
        }
        //并行流
        orderList.parallelStream().forEach(order->{
            OrderCancelDTO orderCancelDTO = new OrderCancelDTO();
            orderCancelDTO.setOrderNo(order.getOrderNo());
            orderCancelDTO.setMobile(order.getMobile());
            this.cancelOrder(orderCancelDTO);
        });
        log.info("自动取消订单完成!共处理{}单,时间:{}", orderList.size(), LocalDateTime.now());
    }

    //修改订单支付状态
    @Override
    public RespResult updataOrderStatusByOrderNo(String out_trade_no) {
        orderMapper.updataOrderStatusByOrderNo(out_trade_no,new Date(),Constant.ORDER_PAY);
        return RespResult.success("支付成功");
    }

    /**
     * 功能:取消订单
     */
    public RespResult<Void> cancelOrder(OrderCancelDTO orderCancelDTO) {
        //再根据手机和订单号再查询一遍
        List<OrderInfo> orderList = orderMapper.findByMobileAndOrderNo(orderCancelDTO.getMobile(),
                orderCancelDTO.getOrderNo());
        if(CollUtil.isEmpty(orderList)){
            return RespResult.error(String.format("取消订单失败,订单%s不存在", orderCancelDTO.getOrderNo()));
        }
        //取第一条
        OrderInfo orderInfo = orderList.get(0);
        if(Constant.ORDER_CREATE != orderInfo.getStatus()){
            return RespResult.error("只能取消已下单状态的订单");
        }
                                    //时间向以后推移1分钟
        orderInfo.setCancelOrderTime(DateUtil.offsetMinute(orderInfo.getOrderTime(), 1));
        orderInfo.setStatus(Constant.ORDER_CANCEL);
        //更新订单信息
        orderMapper.updateById(orderInfo);
        return RespResult.success();
    }

}