订单过期 自动取消实现方案

本文介绍了电商系统中订单超时自动取消的四种实现方案:定时任务、RocketMQ延迟队列、RabbitMQ死信队列和时间轮算法。详细探讨了每种方法的优缺点和具体实现细节,如RocketMQ的18级延迟和RabbitMQ的死信交换机与TTL设置。此外,还提到了Redis的过期监听作为另一种可能的解决方案。

在电商、支付等领域,往往会有这样的场景,用户下单后放弃支付了,那这笔订单会在指定的时间段后进行关闭操作,细心的你一定发现了像某宝、某东都有这样的逻辑,而且时间很准确,误差在1s内;那他们是怎么实现的呢?

一般的做法有如下几种

定时任务关闭订单

rocketmq延迟队列

rabbitmq死信队列

时间轮算法

redis过期监听

一、定时任务关闭订单(最low)
一般情况下,最不推荐的方式就是关单方式就是定时任务方式,原因我们可以看下面的图来说明

image.png

 

我们假设,关单时间为下单后10分钟,定时任务间隔也是10分钟;通过上图我们看出,如果在第1分钟下单,在第20分钟的时候才能被扫描到执行关单操作,这样误差达到10分钟,这在很多场景下是不可接受的,另外需要频繁扫描主订单号造成网络IO和磁盘IO的消耗,对实时交易造成一定的冲击,所以PASS

二、rocketmq延迟队列方式
延迟消息 生产者把消息发送到消息服务器后,并不希望被立即消费,而是等待指定时间后才可以被消费者消费,这类消息通常被称为延迟消息。 在RocketMQ开源版本中,支持延迟消息,但是不支持任意时间精度的延迟消息,只支持特定级别的延迟消息。 消息延迟级别分别为1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h,共18个级别。

发送延迟消息(生产者)

/**
     * 推送延迟消息
     * @param topic 
     * @param body 
     * @param producerGroup 
     * @return boolean
     */
    public boolean sendMessage(String topic, String body, String producerGroup)
    {
        try
        {
            Message recordMsg = new Message(topic, body.getBytes());
            producer.setProducerGroup(producerGroup);
 
            //设置消息延迟级别,我这里设置14,对应就是延时10分钟
            // "1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h"
            recordMsg.setDelayTimeLevel(14);
            // 发送消息到一个Broker
            SendResult sendResult = producer.send(recordMsg);
            // 通过sendResult返回消息是否成功送达
            log.info("发送延迟消息结果:======sendResult:{}", sendResult);
            DateFormat format =new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            log.info("发送时间:{}", format.format(new Date()));
 
            return true;
        }
        catch (Exception e)
        {
            e.printStackTrace();
            log.error("延迟消息队列推送消息异常:{},推送内容:{}", e.getMessage(), body);
        }
        return false;
    }
消费延迟消息(消费者)

/**
     * 接收延迟消息
     * 
     * @param topic
     * @param consumerGroup
     * @param messageHandler
     */
    public void messageListener(String topic, String consumerGroup, MessageListenerConcurrently messageHandler)
{
        ThreadPoolUtil.execute(() ->
        {
            try
            {
                DefaultMQPushConsumer consumer = new DefaultMQPushConsumer();
                consumer.setConsumerGroup(consumerGroup);
                consumer.setVipChannelEnabled(false);
                consumer.setNamesrvAddr(address);
                //设置消费者拉取消息的策略,*表示消费该topic下的所有消息,也可以指定tag进行消息过滤
                consumer.subscribe(topic, "*");
                //消费者端启动消息监听,一旦生产者发送消息被监听到,就打印消息,和rabbitmq中的handlerDelivery类似
                consumer.registerMessageListener(messageHandler);
                consumer.start();
                log.info("启动延迟消息队列监听成功:" + topic);
          &n

订单超时自动取消并没有绝对的最佳方案,不同方案适用于不同的业务场景,以下为你介绍几种常见方案及其特点: ### 延时消息 订单服务生成订单后,发送一条延时消息到消息队列。消息队列在消息到达支付过期时间时,将消息投递给消费者,消费者收到消息之后,判断订单状态是否为已支付,若未支付,则执行取消订单的逻辑。这种方案的优点是实现相对优雅,对业务代码侵入性小,借助消息队列的可靠性保障消息的处理;缺点是需要依赖消息队列中间件,增加了系统复杂度和运维成本 [^2]。 ### Redis过期事件订阅 设置订单过期时间,同时订阅 Redis 的过期事件。当订单对应的键过期时,会触发相应的事件,在事件处理逻辑中执行取消订单的操作。示例代码如下: ```java // 设置订单过期时间 public void setOrderWithExpiration(String orderId, long expireSeconds) { jedis.setex("order:" + orderId, expireSeconds, "PENDING"); } // 订阅 Redis 的过期事件 public void subscribeToExpirationEvents() { Jedis jedis = new Jedis("localhost"); jedis.psubscribe(new JedisPubSub() { @Override public void onPMessage(String pattern, String channel, String message) { if (channel.equals("__keyevent@0__:expired")) { System.out.println("接收到过期事件,取消订单:" + message); // 执行取消订单的业务逻辑 } } }, "__keyevent@0__:expired"); // 订阅过期事件 } ``` 该方案的优点是利用 Redis 的高性能和过期机制,实现较为简单;缺点是 Redis 的过期事件不是实时触发的,可能存在一定的延迟,并且需要对 Redis 进行相关配置才能开启过期事件通知 [^3]。 ### 定时任务 通过定时任务定期扫描订单表,找出已超时且未支付的订单并进行取消操作。这种方案实现简单,不依赖额外的中间件;但当订单量较大时,扫描任务可能会对数据库造成较大压力,且无法做到实时处理,存在一定的延迟 [^1]。 ### 时间轮 时间轮是一种高效的定时调度算法,将时间划分为多个槽,每个槽代表一个时间间隔。订单按照过期时间被分配到不同的槽中,时间轮不断转动,当到达某个槽的时间时,处理该槽中的订单。时间轮的优点是处理效率高,能在订单过期时及时处理;缺点是实现相对复杂,需要对时间轮算法有深入的理解 [^1]。 在选择方案时,需要综合考虑业务规模、系统复杂度、实时性要求等因素。如果业务规模较小,对实时性要求不高,定时任务可能是一个简单可行的方案;如果业务规模较大,对实时性有一定要求,且系统中已经使用了消息队列,延时消息方案可能更合适;如果对性能和实时性要求极高,时间轮方案可以考虑;而 Redis 过期事件订阅方案则适用于对 Redis 有一定使用基础,且能接受一定延迟的场景。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值