订单到期关闭如何实现(延迟场景、延时队列、定时推送、限时抢购)

概述

延时队列:是一种消息队列,可以用于在指定时间或经过一定时间后执行某种操作。
1,订单超时自动取消:用户下单后,如果在指定时间内未完成支付,系统会自动取消订单,释放库存。
2,定时推送:比如消息通知,用户预约某个服务,系统会在服务开始前一定时间发送提醒短信。
3,定时任务:将需要定时执行的任务放入延时队列中,等到指定的时间到达时再进行执行,例如生成报表、统计数据等操作。
4,限时抢购:将限时抢购的结束时间放入延时队列中,当时间到达时自动下架商品。

订单30分钟未支付自动取消怎么实现(技术方案)

日常开发中,我们经常遇到这种业务场景,如:外卖订单超 30 分钟未支付,则自动取订单;用户注册成功 15 分钟后,发短信息通知用户等等。这就延时任务处理场景。
在电商,支付等系统中,一设都是先创建订单(支付单),再给用户一定的时间进行支付,如果没有按时支付的
话,就需要把之前的订单(支付单)取消掉。这种类以的场景有很多,还有比如到期自动收货,超时自动退款,下
单后自动发送短信等等都是类似的业务问题。

定期轮询(数据库定时任务 Quartz)
JDK DelayQueue
JDK Timer
ScheduledExecutorService 周期性线程池
时间轮(kafka)
时间轮(Netty的HashedWheelTimer)

redis过期监听
redis的zset
redisson
zookeeper之curator
kafka和rocketmq延迟消息
RabbitMQ(延迟队列和死信队列)
Quartz,xxljob等定时任务框架
Koala(考拉)
JCronTab(仿crontab的java调度器)
SchedulerX(阿里)
有赞延迟队列

redis、RabbitMQ、kafaka、 RocketMQ显示延迟队列的区别

redis
1.1 优点:
①Redis的延迟队列是基于Redis的sorted set实现的,性能较高。
②Redis的延迟队列可以通过TTL设置过期时间,灵活性较高。
③简单易用,适用于小型系统。
④性能较高,支持高并发。
1.2 缺点:
①可靠性相对较低,可能会丢失消息,就算redis最高级别的持久化也是有可能丢一条的,每次请求都做aof,但是aof是异步的,所以不保证这一条操作能被持久化。
②而且Redis持久化的特性也导致其在数据量较大时,存储和查询效率逐渐降低,此时会需要对其进行分片和负载均衡。
③Redis的延迟队列需要手动实现消息重试机制,更严谨的消息队列需要数据库兜底。
1.3 应用场景:
①适用于较小规模的系统,实时性要求较高的场景。
②适用于轻量级的任务调度和消息通知场景,适合短期延迟任务,不适合长期任务,例如订单超时未支付等。
Kafka
优点:
①Kafka的优点在于其高并发、高吞吐量和可扩展性强,同时支持分片。
②可靠性高,支持分布式和消息持久化。
③消费者可以随时回溯消费。
④支持多个消费者并行消费、消费者组等机制。
缺点:
①没有原生的延迟队列功能,需要使用topic和消费者组来实现,实现延迟队列需要额外的开发工作。
②消费者需要主动拉取数据,可能会导致延迟,精度不是特别高。
在此案例中代码已经实现了,直接拿来使用就可以了。
2.3 应用场景:
适用于大规模的数据处理,实时性要求较高的,高吞吐量的消息处理场景。
RabbitMQ
优点:
①RabbitMQ的延迟队列是通过RabbitMQ的插件实现的,易于部署和使用。
②RabbitMQ的延迟队列支持消息重试和消息顺序处理,可靠性较高。
③支持消息持久化和分布式。
④支持优先级队列和死信队列。
⑤提供了丰富的插件和工具。
缺点:
①RabbitMQ的延迟队列性能较低,不适用于高吞吐量的场景。
②性能较低,不适合高并发场景。
③实现延迟队列需要额外的配置,但是配置就很简单了。
应用场景:
适用于中小型的任务调度和消息通知,对可靠性要求高的场景。
RocketMQ
优点:
①RocketMQ的延迟队列是RocketMQ原生支持的,易于使用和部署。
②RocketMQ的延迟队列支持消息重试和消息顺序处理,可靠性较高。
③高性能和高吞吐量,支持分布式和消息持久化。
④RocketMQ使用简单,性能好,并且支持延迟队列功能。
缺点:
①RocketMQ的延迟队列不支持动态添加或删除队列。
②RocketMQ的延迟队列需要保证消息的顺序,可能会导致消息延迟。
③在节点崩溃后,RocketMQ有可能发生消息丢失。
应用场景:
①适用于大规模的数据处理,对性能和吞吐量要求较高的场景。
②适合于任务量较大、需要延迟消息和定时消息的场景。例如电商平台、社交软件等。
③适用于分布式任务调度和高可靠性消息通知场景。

任务调度实现(定时任务)

使用 Java 定时任务框架(如 Quartz、Spring Task 等)创建一个定时任务,定时检查订单的到期时间。
在订单创建时,将订单的到期时间保存在数据库中或缓存中。
在定时任务中,获取当前时间,查询数据库或缓存,找出已到期但尚未关闭的订单。
针对每个到期订单,执行关闭订单的操作,例如更新订单状态为关闭或发送关闭订单的消息。
定时任务可以根据需求设置执行频率,以保证及时关闭到期订单。
缺点:效率非常低,消耗服务器性能,对数据库造成压力,

基于redis 如何实现

redis过期监听
redis的zset
redisson

redis过期key实现(键通知机制)

1)用户下单的时候,生成一个令牌(有效期)30分钟,存放到我们redis;
2)redis.set(orderToken ,orderID) 下单时候存放到redis,并存储id入库,30分钟过期,
3)redis客户端监听,过期获取到orderId,拿orderId去查订单,没有支付则,订单关闭,库存增加
缺点: 1) 非常冗余 ,会在表中存放一个冗余字段 2) 键通知机制是一种并不可靠的消息机制,如果系统需要需要很好的可靠性,那么它并不是一种很好的选择。

基于redis延迟队列

优点:可以满足吞吐量
缺点:存在任务丢失的风险(当 Redis 实例挂了的时候)。因此,如果对性能要求比较高,同时又能容忍少数情况下任务的丢失,那么可以使用这种方式来实现。
使用 sortedset,拿时间戳作为score,消息内容作为 key 调用 zadd 来生
产消息,消费者用 zrangebyscore 指令获取 N 秒之前的数据轮询进行处理。
Redis延迟队列是一种常用的消息队列模式,用于延迟处理任务或消息。其原理通常基于两个主要组件:有序集合(Sorted Set)和定时任务轮询。

  1. 有序集合(Sorted Set):
    ○ Redis中的有序集合是一种数据结构,其中的每个成员都关联了一个分数(score)。有序集合的特性之一是按照分数从小到大排序。在延迟队列中,可以将消息或任务的执行时间作为分数,将消息内容作为成员存储在有序集合中。
  2. 定时任务轮询:
    ○ 定时任务轮询是一种机制,用于定期检查有序集合中是否有到期的任务或消息。通过定期轮询有序集合,可以检查是否有任务的执行时间已到达,从而将其取出并执行。

Redisson实现一个延迟队列

Redisson是一个基于Redis的分布式Java对象和服务框架,它提供了一系列的高级特性,如分布式对象、分布式锁、分布式消息队列等。其中,分布式消息队列可以非常方便地实现延迟队列。
基于Redisson实现延迟队列的一种常见方式是使用Redis的sorted set来存储任务,并使用Redis的定时器功能触发任务的执行。具体实现步骤如下:

  1. 定义一个Java类来表示任务,该类需要包含任务的执行时间、任务内容等属性。
  2. 使用Redisson获取一个RedissonClient对象,该对象用于与Redis进行交互。
  3. 创建一个RedissonDelayedQueue类,该类包含以下几个方法:
    addTask:将一个任务添加到delayed queue中,使用Redis的zadd命令将任务的执行时间作为score,任务内容作为value添加到sorted set中。
    removeTask:从delayed queue中删除指定的任务,使用Redis的zrem命令从sorted set中删除指定的value。
    pollTask:从delayed queue中取出最早可执行的任务,使用Redis的zrangeByScore命令获取score小于当前时间的最小value,并使用Redis的zrem命令删除该value。
  4. 使用Redis的定时器功能,每隔一段时间(例如1秒钟),调用RedissonDelayedQueue的pollTask方法,将需要执行的任务取出并执行。
    这样,就可以基于Redisson实现一个简单的延迟队列了。需要注意的是,这种方式只适用于任务数量较少的情况,如果任务数量较大,可以考虑使用更高级的方案,比如使用Redis的Lua脚本等。

基于MQ的延迟队列实现

延迟队列方案:
使用消息队列(如 RabbitMQ、Kafka 等)创建一个延迟队列,并设置过期时间为订单的到期时间。
在订单创建时,将订单信息发送到延迟队列。
消费者监听延迟队列,当订单到期时,消费者从队列中接收到该订单并执行关闭操作。
可以使用单个消费者或多个消费者来处理到期订单,实现并行处理和提高吞吐量。

RabbitMQ的延迟队列,使用过期时间+死信队列来实现

实现原理:
1)下单投放消息到 A交换机(过期时间30分钟),将 aa队列绑定到该死信交换机A, 不设置aa队列的消费者(故此消息一直未消费)
2)30分钟后,过期,消息投递到死信交换机,死信队列的消费者消费死信消息, 判断订单id是否支付,执行业务逻辑,支付->return 。未支付->关闭订单,返还库存。

RocketMQ 实现定时延迟消息

RocketMQ 实现定时延迟消息的方式是通过消息的延迟级别(Delay Level)来实现。具体而言,RocketMQ 的消息生产者可以设置消息的延迟级别,消息服务器会根据这个级别将消息存储在不同的延迟队列中,然后在指定的时间后再将消息投递给消费者进行消费。
实现步骤如下:

  1. 设置延迟级别:消息生产者在发送消息时,可以通过设置消息的延迟级别来指定消息的延迟时间。RocketMQ 支持多个预定义的延迟级别,如 1s、5s、10s 等,开发者可以根据实际需求选择合适的延迟级别。
  2. 存储到延迟队列:消息服务器会根据消息的延迟级别将消息存储到对应的延迟队列中,而不是立即投递给消费者。
  3. 定时投递:当消息达到设定的延迟时间后,RocketMQ 会将消息从延迟队列中取出,并投递给对应的消费者进行消费。
    通过以上方式,RocketMQ 实现了定时延迟消息的功能,开发者可以方便地使用延迟级别来控制消息的投递时间,满足各种业务场景下的延迟消息需求,比如订单超时提醒、定时任务等。

要在 Java 代码中实现 RocketMQ 的定时延迟消息,您可以按照以下步骤进行操作:

1.  首先,确保您已经正确配置了 RocketMQ 的依赖项和相关配置文件。 
2.  创建一个 RocketMQ 生产者实例,并设置为支持延迟消息的模式。例如: 
DefaultMQProducer producer = new DefaultMQProducer("your_producer_group");
// 设置为支持延迟消息的模式
producer.setDelayTimeLevel(3); // 设置延迟级别,这里使用预定义的级别3
producer.start();

3. 构建并发送带有延迟属性的消息。例如:
Message message = new Message("your_topic", "your_tags", "your_body".getBytes());
// 设置消息的延迟级别,这里使用预定义的级别3
message.setDelayTimeLevel(3);
SendResult result = producer.send(message);
System.out.println("消息发送结果:" + result.getSendStatus());
4. 创建一个 RocketMQ 消费者实例,并注册消息监听器来处理延迟消息。例如:
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("your_consumer_group");
consumer.subscribe("your_topic", "*");
consumer.registerMessageListener(new MessageListenerConcurrently() {
    @Override
    public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> messages, ConsumeConcurrentlyContext context) {
        for (MessageExt message : messages) {
            // 处理接收到的延迟消息
            System.out.println("接收到延迟消息:" + new String(message.getBody()));
        }
        return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
    }
});
consumer.start();

通过上述代码,您可以在 RocketMQ 中实现定时延迟消息的功能。您可以根据具体需求自定义延迟级别,并在消费者端处理接收到的延迟消息。请注意,延迟消息需要使用推模式(Push Consumer)来接收,因为拉模式(Pull Consumer)不支持延迟消息的消费。

Kafka延时队列实现思路

解决一个问题前首先要明确问题,如何让Kafka有延时队列的功能呢?
就是在Kafka消费者消费的时候延时消费,不久搞定了嘛
那如何延时消费呢,网上有些文章使用Thread.sleep进行延时消费这是不靠谱的(亲身实践),sleep的时间超过了Kafka配置的max.poll.records时间,消费者无法及时提交offset,kafka就会认为这个消费者已经挂了,会进行rebalance也就是重新分配分区给消费者,以保证每个分区只被一个消费者消费
也有同学说了,为了不发生rebalance,那可以增加max.poll.records时间啊,但是这样的话,如果要sleep几天的时间,难道max.poll.records要写几天的时间嘛,有违Kafka的设计原理了,那怎么办呢?
这时候Kafka的pause暂停消费和resume恢复消费就登场了,pause暂停某个分区之后消费者不会再poll拉取该分区的消息,直到resume恢复该分区之后才会重新poll消息。
我已经做好了Kafka延时队列的封装,以后只需要一行代码就可以实现延时队列了,代码核心使用Kafka消费者的pause函数(暂停)和resume函数(恢复)+线程池+定时任务+事件监听机制+工厂模式
在这里插入图片描述

  • 30
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
延时队列定时任务的区别在于,延时队列是按照元素添加的时间来进行排序,而定时任务是按照预定的时间来触发执行的。延时队列可以用于实现某些需要延迟处理的业务场景,例如延迟发布、延迟重试等场景,而定时任务则适用于需要周期性执行任务的场景。 下面是使用Java实现延时队列的示例代码: ``` import java.util.concurrent.DelayQueue; import java.util.concurrent.Delayed; import java.util.concurrent.TimeUnit; public class DelayQueueDemo { public static void main(String[] args) { DelayQueue<DelayedElement> delayQueue = new DelayQueue<>(); delayQueue.offer(new DelayedElement("A", 3000)); delayQueue.offer(new DelayedElement("B", 2000)); delayQueue.offer(new DelayedElement("C", 1000)); while (!delayQueue.isEmpty()) { try { DelayedElement element = delayQueue.take(); System.out.println(element.getData() + "被取出,时间为:" + System.currentTimeMillis()); } catch (InterruptedException e) { e.printStackTrace(); } } } } class DelayedElement implements Delayed { private String data; private long expireTime; public DelayedElement(String data, long delayTime) { this.data = data; this.expireTime = System.currentTimeMillis() + delayTime; } public String getData() { return data; } @Override public long getDelay(TimeUnit unit) { return unit.convert(expireTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS); } @Override public int compareTo(Delayed o) { return Long.compare(this.getDelay(TimeUnit.MILLISECONDS), o.getDelay(TimeUnit.MILLISECONDS)); } } ``` 在这个示例中,我们定义了一个`DelayedElement`类,它实现了`Delayed`接口,并重写了`getDelay`和`compareTo`方法。`getDelay`方法返回该元素离过期时间还有多少时间,`compareTo`方法用于比较两个元素的顺序。 在`main`方法中,我们首先创建了一个`DelayQueue`对象,并向其中添加了三个元素,分别延时1秒、2秒和3秒。然后我们不断从队列中取出元素,直到队列为空。在取出元素的过程中,由于是按照元素到期时间来排序的,所以最先到期的元素会被最先取出。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

思静语

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值