【重磅干货】基于RabbitMQ的TTL(延迟队列+死信队列),实现支付订单超时自动取消,一般大厂的落地方案

原创 不码不疯魔 不码不疯魔 2023-08-28 22:48 发表于四川

收录于合集

#支付1个

#TTL1个

#延迟队列3个

#高频面试题9个

#取消订单3个

20

23

不疯魔不成活,大家好呀,我是科哥,江湖ID 不码不疯魔

图片

大家好!今天我们将一起探讨一个高级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'队列,由消费者进行正常的处理。

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

7. 处理超时订单:在'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,Object> arguments = 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();
    }

}

图片

图片

点击下方公众号进入关注,后台回复【TTL】即可获取如上图所有源码,大量的商用级方案已经更新,记得关注,免费领取,标记星号哟!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值