订单一个时间段没有支付,需要对订单进行操作

场景:例如在美团点外卖,当长时间没有支付,订单会自动关闭;医院挂号,达到一个时间段没有支付,订单需要关闭,同时还需要取消锁号(取消号源,防止占坑不拉屎);抢火车票,没有支付,订单取消,座位释放等

方式1:延迟队列(DelayQueue)

      我们的第一反应是用 数据库轮序+任务调度 来实现此功能。但这种高效率的延迟任务用任务调度(定时器)实现就得不偿失。而且对系统也是一种压力且数据库消耗极大。因此我们使用 Java 延迟队列 DelayQueue 来实现,DelayQueue 是一个无界的延时阻塞队列(BlockingQueue),用于存放实现了 Delayed 接口的对象,队列中的对象只能在其到期时才能从队列中取走。这种队列是有序的,既队头对象的延迟到期时间最长

//加入delayQueue的对象,必须实现Delayed接口,同时实现如下:compareTo和GetDelay方法 
static class DelayItem implements Delayed{
     //过期时间(单位:分钟)
     private long expTime;
     private String orderCode;
        
     public DelayItem(String orderCode,long expTime,Date createTime) {
          super();
          this.orderCode=orderCode;
          this.expTime=TimeUnit.MILLISECONDS.convert(expTime, TimeUnit.MINUTES)+createTime.getTime();
     }
     /**
     * 用于延迟队列内部比较排序,当前时间的延迟时间  -  比较对象的延迟时间
     */
     @Override
     public int compareTo(Delayed o) {
          return Long.valueOf(this.expTime).compareTo(Long.valueOf(((DelayItem)o).expTime));
     }
        
     /**
     * 获得延迟时间,过期时间-当前时间(单位ms)
     */
     @Override
     public long getDelay(TimeUnit unit) {
          return this.expTime-System.currentTimeMillis();
     }

}

 将未付款的订单都 add 到延迟队列中,并通过线程池启动多个线程不断获取延迟队列的内容,获取到后进行状态的修改,进行业务逻辑处理。具体代码如下:


public class DelayQueueTest implements Runnable{
    //创建一个延迟队列
    private    DelayQueue<Delayed> item = new DelayQueue<>();
 
    @Override
    public void run() {
         while(true) {
          try {
            //只有当到期了才会获取到此对象
            DelayItem delayed = (DelayItem) item.take();
                //获取到之后修改状态
           } catch (InterruptedException e) {
                    e.printStackTrace();
           }
         }
    }
 
        //添加数据调用的方法
    public void orderTimer(DelayItem delayItem) {
        //向队列汇总添加数据
        item.add(delayItem);
    }
    
    public static void main(String[] args) {
          //创建一个线程池
          ExecutorService executor = Executors.newCachedThreadPool();
          //多线程执行程序
          executor.execute(new DelayQueueTest());
    }

缺点大量消息堆积,性能不能保证,且很容易触发OOM;需要考虑分布式的实现、存在单点故障;服务器故障,数据丢失;

方式二:环形队列

 

å¨è¿éæå¥å¾çæè¿°

可以看下:https://blog.csdn.net/qq_44149554/article/details/90666352 

实现:

   1)、环形队列,例如可以创建一个包含3600个 slot 的环形队列(本质是个数组)
    2)、任务集合,环上每一个 slot 是一个 Set<Task>
同时,启动一个 timer ,这个 timer 每隔一秒,在上述环形队列中移动一格,有一个 Current Index 指针来标识正在检测的 slot。环形队列分为 3600 个长度,每秒移动一格,移动 3600 秒正好一个小时。比如一个任务需要在60秒后执行,那这个任务应该放在那个槽位的集合里呢?假设当前指针移动到 slot 的位置为2,那么60秒后的槽位就是62,所以数据应该放在索引为 62 的那个槽位圈数为0。如果这个任务要70分钟,70*60+2=4202,4202-3600=602,减了一次3600,所以应该放在第二圈的602槽位,既放在队列索引为602槽位的集合,且圈数为1,代表运行一圈后才执行这个任务。

 缺点和延迟队列差不多

 

方式三:使用 Redis 实现

使用Zset

      通过 Redis ZSet 类型及操作命令实现一个延迟队列,用时间戳(当前时间+延迟的分钟数)作为元素的 score 存入ZSet。只需获取zset中的第一条记录,即最早时间下单数据,如果该记录未超时支付,剩下的订单必然未超时

监听redis的过期

    缺点:当服务器挂了,而这个时候消息过期就GG了

      å¨è¿éæå¥å¾çæè¿°

redis本身需要开启事件监听,配置事件监听规则,有如下两种方式

  1. 打开redis配置文件,打开下面这个配置,但是后续还需要重启redis服务
    11
  2. 直接在redis-cli中用命令打开,这种方式不需要重启redis服务
    在这里插入图片描述

 

实现代码,集成redis的代码此处就不提供了,只提供监听需要用到的代码

/**
 * Redis缓存配置类
 */
@Configuration
public class RedisConfigurer extends CachingConfigurerSupport {

    @Bean
    RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory) {

        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(connectionFactory);
        return container;
    }

}
@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) {
        // message.toString()可以获取失效的key
        String expiredKey = message.toString();
        System.out.println(expiredKey);
    }
}


目前存在缺陷:只能获取到失效的key,但是此时不能根据key获取value值,因为该事件是在数据失效后才触发
有个简单的解决办法,就是一份数据存两份
比如你现在需要set一份数据:key:value
可以额外再set一份value相同但key有指定规则的数据:key_copy:value
第二份数据过期时间相对第一份数据稍微长一点
这样过期事件执行时可以拿着key根据指定的规则拼装出第二份数据的key,从而得到想要的value
缺点就是当你需要set的地方比较多时维护起来就非常恶心

方式4:RabbitMQ 实现

 

利用 RabbitMQ 的死信队列(Dead-Letter-Exchage)机制实现,在 queueDeclare 方法中加入 “x-dead-letter-exchage”实现:

x-dead-letter-exchage:过期消息路由转发(转发器类型)
x-dead-letter-routing-key:当消息达到过期时间由该 exchange 安装配置的 x-dead-letter-routing-key 转发到指定队列,最后被消费者消费
 


 

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值