前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >关于实现订单超时的几种方案(详细细节版)

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

作者头像
终有救赎
发布2023-10-22 19:36:35
3000
发布2023-10-22 19:36:35
举报
文章被收录于专栏:多线程多线程

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

代码语言:javascript
复制
    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
image.png

死信Exchange(Dead Letter Exchanges) 死信交换机不是一个消息队列了,而是一个交换机,交换机可以绑定多个消息队列。 当消息满足以下情况时,消息会进入死信队列:

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

死信交换机并不是一个特殊的交换机,它也是一个很普通的交换机,只是因为它的作用不同,所以称呼也不同。 在某一个设置Dead Letter Exchange的队列中有消息过期了,会自动触发消息的转发,发送到Dead Letter Exchange中去。

image.png
image.png

具体实现步骤:

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

image.png
image.png

2.创建队列queue.ordertimeout

image.png
image.png

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

image.png
image.png

4.创建队列queue.ordercreate,Arguments添加: x-message-ttl=10000 x-dead-letter-exchange: exchange.ordertimeout

image.png
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的配置类:

代码语言:javascript
复制
@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方法。

代码语言:javascript
复制
@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:“订单过期时间"

代码语言:javascript
复制
/**
     * 设置订单超时
     *
     * @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的过期事件适合那种灵活设置过期时间的场景。

本文参与?腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2023-10-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客?前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与?腾讯云自媒体分享计划? ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 个人的实现思路
    • 一、轮询数据库
      • 二、mq延迟消息
        • 三、redis的过期事件
          • 四、总结
          相关产品与服务
          消息队列
          腾讯云消息队列 TDMQ 是分布式架构中的重要组件,提供异步通信的基础能力,通过应用解耦降低系统复杂度,提升系统可用性和可扩展性。TDMQ 产品系列提供丰富的产品形态,包含 CKafka、RocketMQ、RabbitMQ、Pulsar、CMQ 五大产品,覆盖在线和离线场景,满足金融、互联网、教育、物流、能源等不同行业和场景的需求。
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
          http://www.vxiaotou.com