关于实现订单超时的几种方案(详细细节版)

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: 关于实现订单超时的几种方案(详细细节版)

说明:关于使用rabbitmq实现订单超时的部分说明有错误,首先mq是可以实现自定义超时时间的,我们可以在创建队列queue.ordercreate时不设置它的x-message-ttl参数,转而在代码里设置消息过期时间。


public void test() {
        // 消息后处理对象,设置一些消息的参数信息
        MessagePostProcessor messagePostProcessor = message -> {
            //1.设置message的信息
            // 第二个方法:消息的过期时间 ,5秒之后过期,这里可以自由设置消息过期时间
           message.getMessageProperties().setExpiration("5000");
            //2.返回该消息
            return message;
        };
        rabbitTemplate.convertAndSend("exchange.ordertimeout", "*", "hello!", messagePostProcessor);
    }

=========================================================

先描述一下业务场景,用户下单后在规定时间内没有完成支付,那么系统需要把订单终结掉。

但是这个规定时间可能不是定死的,它可能是3小时,2小时,30分钟等等

个人的实现思路

一、轮询数据库

这种方式就是在保存订单的时候把订单的超时时间也一起保存进去,然后用定时任务去轮询数据库获取未支付的订单,再去判断是否超时了。

但是!这种方法太捞了呀,而且也不具备实时性,比如我有个订单号为:123的订单是在10:00:00这个时间超时,但是我的定时任务是每5分钟执行一次,它又恰好是在09:58:00执行了一次任务,那么它这个时候拿到订单号:123的订单做判断,结果是未超时。然后下一次执行是在10:03:00,这个时候再拿到123的订单,肯定是超时了。也就是说我们订单应该在10:00:00就超时的,可直到10:03:00才超时。这就是没有实时性。 这咱都不说数据量太多处理起来还贼慢的情况。

二、mq延迟消息

第二种方式就是借助消息队列,这里我只提供rabbitMQ的实现思路。

使用rabbitMQ实现延迟消息首先要了解两个点:消息的TTL和死信Exchange。通过这两个我们就可以实现延迟消息了。

TTL(Time To Live)

消息的ttl意思就是消息的过期时间。rabbitmq中可以对消息和队列分别设置过期时间。

对队列设置就是队列没有消费者连着的保留时间,也可以对每一个单独的消息做单独的设置。超过了这个时间,我们认为这个消息就死了,称之为死信。

image.png

死信Exchange(Dead Letter Exchanges)

死信交换机不是一个消息队列了,而是一个交换机,交换机可以绑定多个消息队列。

当消息满足以下情况时,消息会进入死信队列:

  • 一个消息被Consumer拒收了,并且reject方法的参数里requeue是false。也就是说不会被再次放在队列里,被其他消费者使用。
  • 消息的TTL到期了,消息过期了。
  • 队列的长度限制满了。排在前面的消息会被丢弃或者扔到死信交换机上。

死信交换机并不是一个特殊的交换机,它也是一个很普通的交换机,只是因为它的作用不同,所以称呼也不同。

在某一个设置Dead Letter Exchange的队列中有消息过期了,会自动触发消息的转发,发送到Dead Letter Exchange中去。

image.png

具体实现步骤:

1.创建死信交换器 exchange.ordertimeout (fanout)

image.png

2.创建队列queue.ordertimeout

image.png

3.建立死信交换器 exchange.ordertimeout 与队列queue.ordertimeout 之间的绑定

image.png

4.创建队列queue.ordercreate,Arguments添加:

x-message-ttl=10000

x-dead-letter-exchange: exchange.ordertimeout

image.png

代码层面实现:
我们每次创建订单的时候要往queue.ordercreate这里消息队列里方法消息,可以将订单编号作为消息发送。
在写一个监听类,去监听queue.ordertimeout这个消息队列,当“queue.ordercreate”里的消息过期时会被转发到

“queue.ordertimeout”里
这个时候拿到“queue.ordertimeout”里的消息(订单编号),做一个超时处理就行了。

小结:

用rabbitmq作为中间件可以解决很多后端服务的问题,而且是低耦合。

如果你的业务场景要设置所有订单都是固定好的时间内过期,比如都是在2小时内过期,那么很简单,只需要把对应的“x-message-ttl”的值设置成“7200000”。这种情况下用rabbitmq很合适。

回到开头,我们说可能这个时间也不是固定死的,它可能是3小时,2小时,30分钟等等。如果不多,可以多开几个延迟队列,对应不同的过期时间。

如果设置过期时间的自由度很高,用rabbitmq咋实现,以我目前的道行,我实现不了,哈哈哈哈哈哈!

接下的方案就是针对那种自定义过期时间的!

三、redis的过期事件

redis作为中间件有多好用,咱就不多BB里。直接上代码!

第一步、 首先写一个redis的配置类:


@Configuration
public class RedisListenerConfig {
    @Autowired
    private RedisTemplate redisTemplate;
    /**
     * 处理乱码
     */
    @Bean
    public RedisTemplate redisTemplate() {
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        return redisTemplate;
    }
    @Bean
    RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory) {
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(connectionFactory);
        return container;
    }
}

第二步、再写一个监听类,这个类需要继承KeyExpirationEventMessageListener类,并且重写onMessage方法。


@Component
public class RedisKeyExpirationListener extends KeyExpirationEventMessageListener {
    public RedisKeyExpirationListener(RedisMessageListenerContainer listenerContainer) {
        super(listenerContainer);
    }
    /**
     * 针对redis数据失效事件处理,进行数据处理
     *
     * @param message
     * @param pattern
     */
    @Override
    public void onMessage(Message message, byte[] pattern) {
        //获取失效的key
        String expirationKey = message.toString();
        //对开头是ordertimeout的键进行处理
        if (expirationKey.startsWith("ordertimeout:")) {
            String[] split = expirationKey.split(":");
            String orderId = split[1];
            //处理订单
            this.endOrder(orderId);
        }
    }
}

第三步、创建订单的时候存入redis里,key的格式:“ordertimeout:订单号”,value:“订单过期时间"


/**
     * 设置订单超时
     *
     * @param time
     * @param orderId
     */
    public void setTimeOut(LocalDateTime time, String orderId) {
        //获取截止时间
        LocalDateTime endTime = time;
        Date endDate = Date.from(endTime.atZone(ZoneId.systemDefault()).toInstant());
        //系统当前时间,计算秒值
        Date nowDate = new Date();
        //相减获取秒
        long expirationTime = (endDate.getTime() - nowDate.getTime()) / 1000;
        redisTemplate.boundValueOps("ordertimeout:" + orderId).set(endTime, expirationTime, TimeUnit.SECONDS);
    }

最后我们就可以在监听类里处理过期的订单了。

四、总结

以上三种方案,第一种个人认为应该没有人会用,就连我自己也觉得是废话。后面两种方案可以参考着来,延迟消息适合那种固定死时间的场景,redis的过期事件适合那种灵活设置过期时间的场景。



相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore     ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库 ECS 实例和一台目标数据库 RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
4月前
|
运维 分布式计算 算法
函数计算产品使用问题之如何使用重试机制
函数计算产品作为一种事件驱动的全托管计算服务,让用户能够专注于业务逻辑的编写,而无需关心底层服务器的管理与运维。你可以有效地利用函数计算产品来支撑各类应用场景,从简单的数据处理到复杂的业务逻辑,实现快速、高效、低成本的云上部署与运维。以下是一些关于使用函数计算产品的合集和要点,帮助你更好地理解和应用这一服务。
|
3月前
|
Go
关于处理电商系统订单状态的流转,分享下我的技术方案(附带源码)
关于处理电商系统订单状态的流转,分享下我的技术方案(附带源码)
87 0
|
4月前
|
移动开发
交易链路设计原则&模式问题之在订单管理系统中,doOp接口实现多种按钮操作,如何解决
交易链路设计原则&模式问题之在订单管理系统中,doOp接口实现多种按钮操作,如何解决
支付系统34----支付成功异步通知,处理重复通知,我们在我们程序当中找到处理订单的processOrder方法,我们要在更新订单状态和记录日志之前,先处理重复通知
支付系统34----支付成功异步通知,处理重复通知,我们在我们程序当中找到处理订单的processOrder方法,我们要在更新订单状态和记录日志之前,先处理重复通知
|
6月前
|
消息中间件 架构师 NoSQL
以架构师的视角,深入剖析如何设计订单超时自动取消的功能
我们在美团 APP 下单,假如没有立即支付,进入订单详情会显示倒计时,如果超过支付时间,订单就会被自动取消。 这篇文章,笔者想以架构师的视角,深入剖析如何设计订单超时自动取消的功能。
以架构师的视角,深入剖析如何设计订单超时自动取消的功能
|
消息中间件 存储 资源调度
订单超时怎么处理?我们用这种方案
在电商业务下,许多订单超时场景都在24小时以上,对于超时精度没有那么敏感,并且有海量订单需要批处理,推荐使用基于定时任务的跑批解决方案。
1587 7
订单超时怎么处理?我们用这种方案
|
消息中间件 NoSQL 数据库
订单超时未支付自动取消--实现简述
订单超时未支付自动取消--实现简述
238 0
|
消息中间件 存储 资源调度
订单超时处理的几种方案及分析
描述业务常见的订单超时处理的几种方案及分析
31847 19
订单超时处理的几种方案及分析
|
SQL 负载均衡 NoSQL
【防止重复下单】分布式系统接口幂等性实现方案
【防止重复下单】分布式系统接口幂等性实现方案
1749 0
【防止重复下单】分布式系统接口幂等性实现方案
|
NoSQL 关系型数据库 MySQL
业务让我实现一个排队导出功能
业务诉求:考虑到数据库数据日渐增多,导出会有全量数据的导出,多人同时导出可以会对服务性能造成影响,导出涉及到mysql查询的io操作,还涉及文件输入、输出流的io操作,所以对服务器的性能会影响的比较大;结合以上原因,对导出操作进行排队;