如何实现生成订单30分钟内未支付则自动取消?

如何实现生成订单30分钟内未支付则自动取消?

数据库轮询

不是很推荐的一种方式,需要定时扫描数据库,借助定时任务工具,如果是多服务部署,那么还需要考虑分布式调度,推荐xxL-job。
缺点在于数据库压力大,而且会有延迟性。比如每隔3分钟进行扫描一次,最坏的情况就是那个取消支付要延迟3分钟行。

mysql定时器是系统给提供了event,下面创建表:
create table mytable (
id int auto_increment not null,
name varchar(100) not null default '',
introduce text not null,
createtime timestamp not null,
constraint pk_mytable primary key(id)
)


创建存储过程,这里的存储过程主要提供给mysql的定时器event来调用去执行:
create procedure mypro()
BEGIN
insert into mytable (name,introduce,createtime) values ('1111','inner mongolia',now());
end;
这里只是简单的写了一下,只是为了说明例子。


紧接着创建mysql的定时器event:
create event if not exists eventJob 
on schedule every 1 second
on completion PRESERVE
do call mypro();
这里设置为每一秒执行一次


至此所有的准备工作已经写完了,做完这些,mysql要想利用定时器必须的做准备工作,就是把mysql的定时器给开启了:
SET GLOBAL event_scheduler = 1;  -- 启动定时器
SET GLOBAL event_scheduler = 0;  -- 停止定时器


紧接着还要开启事件:
ALTER EVENT eventJob ON  COMPLETION PRESERVE ENABLE;   -- 开启事件
ALTER EVENT eventJob ON  COMPLETION PRESERVE DISABLE;  -- 关闭事件


SHOW VARIABLES LIKE '%sche%'; -- 查看定时器状态

https://www.cnblogs.com/mr-wuxiansheng/p/5962454.html

xxl-job中心式的调度平台轻量级,开箱即用,操作简易,上手快,与SpringBoot有非常好的集成,而且监控界面就集成在调度中心,界面又简洁,对于企业维护起来成本不高,还有失败的邮件告警等等。

xxl-job是通过一个中心式的调度平台,调度多个执行器执行任务,调度中心通过DB锁保证集群分布式调度的一致性,这样扩展执行器会增大DB的压力,但是如果实际上这里数据库只是负责任务的调度执行。但是如果没有大量的执行器的话和任务的情况,是不会造成数据库压力的。实际上大部分公司任务数,执行器并不多(虽然面试经常会问一些高并发的问题)。

JDK的延迟队列

利用JDK自带的无界阻塞队列DelayQueue来实现,该队列只有在延迟期满的时候才能从中获取元素,放入DelayQueue中的对象,是必须实现Delayed接口的。这种方式优点在于延时低,缺点就是一旦服务重启,之前放入队列的任务全部丢失。如果有大量的任务,可能会造成oom。代码复杂度也比较高。

package com.fchan.mq.jdkDelay;

import com.fasterxml.jackson.annotation.JsonFormat;

import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;


public class MyDelay implements Delayed {

    /**
     * 延迟时间
     */
    @JsonFormat(locale = "zh", timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
    private Long time;
    String name;


    public MyDelay(String name, Long time, TimeUnit unit) {
        this.name = name;
        this.time = System.currentTimeMillis() + (time > 0 ? unit.toMillis(time) : 0);
    }

    public Long getTime() {
        return time;
    }

    public String getName() {
        return name;
    }

    //获取延时时间
    @Override
    public long getDelay(TimeUnit unit) {
        return time - System.currentTimeMillis();
    }

    //对延时队列中的元素进行排序
    @Override
    public int compareTo(Delayed o) {
        MyDelay myDelay = ((MyDelay) o);
        return this.time.compareTo(myDelay.getTime());
    }
}


package com.fchan.mq.jdkDelay;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.TimeUnit;

public class MyDelayDemo {

    public static void main(String[] args) throws InterruptedException {
        MyDelay myDelay1 = new MyDelay("MyDelay1", 5L, TimeUnit.SECONDS);
        MyDelay myDelay2 = new MyDelay("MyDelay2", 10L, TimeUnit.SECONDS);
        MyDelay myDelay3 = new MyDelay("MyDelay3", 15L, TimeUnit.SECONDS);

        DelayQueue<MyDelay> delayQueue = new DelayQueue<MyDelay> ();
        //add 和 put 后面都是调用的offer方法,内部使用了ReentrantLock
        //delayQueue.put();
        delayQueue.add(myDelay1);
        delayQueue.add(myDelay2);
        delayQueue.add(myDelay3);

        System.out.println("订单延迟队列开始时间:" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        while (delayQueue.size() != 0) {
            /**
             * 取队列头部元素是否过期
             */
            //DelayQueue的put/add方法是线程安全的,因为put/add方法内部使用了ReentrantLock锁进行线程同步。
            // DelayQueue还提供了两种出队的方法 poll() 和 take() ,
            // poll() 为非阻塞获取,没有到期的元素直接返回null;
            // take() 阻塞方式获取,没有到期的元素线程将会等待。
            MyDelay task = delayQueue.poll();
            if (task != null) {
                System.out.format("任务:{%s}被取消, 取消时间:{%s}\n", task.name, LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
            }
            Thread.sleep(1000);
        }


    }
}

Quartz

Quartz一款非常经典任务调度框架,在Redis、RabbitMQ还未广泛应用时,超时未支付取消订单功能都是由定时任务实现的。
Quartz缺点没有自带的管理界面;调度逻辑和执行任务耦合在一起;维护需要重启服务

时间轮算法

时间轮算法可以类比于时钟,如下图箭头(指针)按某一个方间按固定频率轮动,每一次跳动称为一个tick。这样可以看出定时轮由个3个重要的属性参数,ticksPerWheel(
轮的tick数),tickDuration(一个tick的持续时间)从及timeUnit(时间单位),例如当ticksPerWheel=60,tickDuration=1,timeUnit=秒,这就和现实中的始终的利针走动完全类似了。可从用Netty的HashedWheelTimer来进行使用,缺点和延识队列一样

使用消息队列

采用rabbitMQ的延时队列。RabbitMQ具有从下两个特性,RabbitMQ可以针对Queue和Message设置x-message-tt,来控制消息的生存时间,如果超时,则消息变为dead letter。
RabbitMQ的Queue可从配置x-dead-letter-exchange和x-
dead-letter-routing-key(可选)两个参数,用来控制队列内出现了deadletter,则按照这两个参数重新路由。结合以上两个特性,就可以模拟出延迟消息的功能。
优点就是高效,可以利用rabbitmq的分布式特性轻易的进行横句扩展,消息支持持久化增加了可靠性

public void send(String delayTimes) {
        amqpTemplate.convertAndSend("order.pay.exchange", "order.pay.queue","大家好我是延迟数据", message -> {
            // 设置延迟毫秒值
            message.getMessageProperties().setExpiration(String.valueOf(delayTimes));
            return message;
        });
    }
}

设置转发规则

/**
     * 延时队列
     */
    @Bean(name = "order.delay.queue")
    public Queue getMessageQueue() {
        return QueueBuilder
                .durable(RabbitConstant.DEAD_LETTER_QUEUE)
                // 配置到期后转发的交换
                .withArgument("x-dead-letter-exchange", "order.close.exchange")
                // 配置到期后转发的路由键
                .withArgument("x-dead-letter-routing-key", "order.close.queue")
                .build();
    }

除此之外 监听RedisKey过期时间进行定时任务并不可信
redis的过期监听并不可靠, 尤其是大量KEY的时候.很可能 会延迟推送.
redis官方链接

In order to obtain a correct behavior without sacrificing consistency, when a key expires, a DEL operation is synthesized in both the AOF file and gains all the attached replicas nodes. This way the expiration process is centralized in the master instance, and there is no chance of consistency errors.
However while the replicas connected to a master will not expire keys independently (but will wait for the DEL coming from the master), they’ll still take the full state of the expires existing in the dataset, so when a replica is elected to master it will be able to expire the keys independently, fully acting as a master.

大致意思是会生成del追加到aof中,副本没有自己的过期key,他会等待删除命令,
数据分析请勿过度依赖Redis的过期监听

Redis缓存可以通过设置键的过期时间来实现订单自动取消功能。当订单生成时,我们将订单信息存储到Redis缓存中,并设置一个过期时间(比如30分钟)。这样,订单信息将在30分钟自动从Redis中移除。如果订单30分钟内完成支付,我们可以根据订单号在Redis中查找订单信息并进行相应处理。如果订单30分钟内未完成支付,则订单信息会在过期时间到达时自动从Redis中移除,并触发订单取消逻辑。 在实现过程中,可以使用Redis的SET命令将订单信息保存到Redis中,并通过EXPIRE命令设置该键的过期时间。例如: ``` SET order:12345 "待支付订单信息" EXPIRE order:12345 1800 ``` 以上代码将"待支付订单信息"保存在键名为"order:12345"的键中,并设置其过期时间为1800秒(30分钟)。当订单完成支付时,可以通过GET命令获取订单信息。如果返回空值,则说明订单已被取消。如果返回订单信息,则可以继续处理。 另外,为了保证系统的可靠性,可以在订单取消时触发一些额外的操作,如发送通知给用户或其他相关系统。可以使用Redis的发布/订阅功能,将订单取消事件发布到指定的频道,然后其他订阅该频道的系统可以接收到该事件并执行相应的处理逻辑。 总之,通过使用Redis的缓存功能,可以轻松实现订单自动取消的功能,并且具备一定的灵活性和可扩展性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值