今天遇到 支付超时,30min后取消订单,  技术栈中有redis

实现方式 :

最简单的方式,定时扫表;例如每分钟扫表一次十分钟之后未支付的订单进行主动支付 ;
优点: 简单
缺点: 每分钟全局扫表,浪费资源,有一分钟延迟

使用RabbitMq 实现 RabbitMq实现延迟队列
优点: 开源,现成的稳定的实现方案;
缺点: RabbitMq是一个消息中间件;延迟队列只是其中一个小功能,如果团队技术栈中本来就是使用RabbitMq那还好,如果不是,那为了使用延迟队列而去部署一套RabbitMq成本有点大;

使用Java中的延迟队列,DelayQueue
优点: java.util.concurrent包下一个延迟队列,简单易用;拿来即用
缺点: 单机、不能持久化、宕机任务丢失等等;  (可以通过redis 保存,添加队列时,同时添加到redis, 删除时,同时删除redis)

  

问题: 目前没有用RabbitMq,但是有redis

我的方案:使用Java中的延迟队列,DelayQueue

1. 添加订单到队列,同时添加到redis

2.弹出队列,同时删除redis 中的订单

1. 简单示例

2.集成springboot

DelayQueue:简单示例

public class OrderPayTimeOut {
    /**
     * 3个线程:
     * 线程1:向队列中添加数据
     * 线程2:从队列中取出数据
     * 线程3:向队列中添加数据
     * @throws Exception
     */
    @Test
    public void testDelayQueue() throws Exception {
            ExecutorService executor = Executors.newFixedThreadPool(3);        // 创建线程池并返回ExecutorService实例  
            DelayQueue<PaylogX> queue = new DelayQueue<>();
            FutureTask<String> add = add(queue,"before",3);
            FutureTask<String> take = take(queue);
            Thread.sleep(100l);
            FutureTask<String> add1 = add(queue,"later",1);
            FutureTask<String> add2 = add(queue,"then",1);
            executor.execute(add);  // 执行任务  
            executor.execute(add1);  // 执行任务  
            executor.execute(add2);  // 执行任务  
            executor.execute(take); 
            while(true) {}
    }
    
    
    
    /**
     * 单线程测试
     * @description: 延时队列测试
     * @author: hh
     */
    @Test
    public  void testRun() throws InterruptedException {
        PaylogX item1 = new PaylogX("item1", 1, TimeUnit.MINUTES);
        PaylogX item2 = new PaylogX("item2",3, TimeUnit.MINUTES);
        PaylogX item3 = new PaylogX("item3",5, TimeUnit.MINUTES);
        DelayQueue<PaylogX> queue = new DelayQueue<>();
        queue.offer(item1);
        queue.offer(item2);
        queue.offer(item3);
        System.out.println("begin time:" + LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
        
        for (int i = 0; i < 3; i++) 
        {
            PaylogX paylogX = queue.take();
            System.out.format("name:{%s},time:{%s} date:{%s}\n",paylogX.oTitle,paylogX.timeStamp, LocalDateTime.now().format(DateTimeFormatter.ISO_DATE_TIME));
        }
    }
    
    
    /**
     * 添加数据到队列
     * @param queue
     * @return
     */
    private  FutureTask<String> add(DelayQueue<PaylogX> queue,String name,long expireTime) {
           Callable<String> callable1=new Callable<String>() {
                @Override
                public String call() throws Exception {
                    System.err.println(name+"开始添加数据!!");
                    IntStream.range(0, 1000).forEach(i->{
                        PaylogX paylogX = new PaylogX(name+i, expireTime+new Random().nextInt(10), TimeUnit.SECONDS);
                         try {
                            Thread.sleep(100l);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                         queue.put(paylogX);
                         
                    });
                    System.err.println(name+"添加数据结束!!");
                    return null;
                }
             };
             FutureTask<String> futureTask1 = new FutureTask<String>(callable1);
             return futureTask1;
    }
    /**
     * 从队列中取出数据
     * @param queue
     * @return
     */
    private  FutureTask<String> take(DelayQueue<PaylogX> queue) {
        
           Callable<String> callable1=new Callable<String>() {
                @Override
                public String call() throws Exception {
                    while (true) {
                        PaylogX paylogX = queue.take();
//                        queue.remove(take);
                        System.out.format("name:{%s},time:{%s} date:{%s}\n",paylogX.oTitle,paylogX.timeStamp, LocalDateTime.now().format(DateTimeFormatter.ISO_DATE_TIME));
                    }
                }
             };
             FutureTask<String> futureTask1 = new FutureTask<String>(callable1);
             return futureTask1;
    }
    
    
    
    @NoArgsConstructor
    @Data
    @ToString
    class PaylogX implements Delayed{
        private String     _id=NumberUtils.genNO() ; //uuid
        private String     oTitle              ; 
        private long     timeStamp=new Date().getTime();
        private transient long     delayTime;  //// 延迟时长,这个是必须的属性因为要按照这个判断延时时长。
        
        public PaylogX(String oTitle ,long delayTime,TimeUnit unit) {
            this.oTitle=oTitle;
            this.delayTime = System.currentTimeMillis() + (delayTime > 0? unit.toMillis(delayTime): 0);
        }
        @Override
        public int compareTo(Delayed o) {
            PaylogX delayed=(PaylogX)o;
            long diff = this.delayTime-delayed.delayTime;
            if (diff <= 0) {// 改成>=会造成问题
                return -1;
            }else {
                return 1;
            }
        }
        
        @Override
        public long getDelay(TimeUnit unit) {
             return this.delayTime - System.currentTimeMillis();
        }
        public void setDelayTime(long delayTime) {
            this.delayTime = delayTime;
        }
    }

}

 

 

集成springboot 

 

2.1 添加配置 DelayQueue

@Configuration
public class PayTimeoutConfig {
    @Qualifier("payLogTimeoutQueue")
    @Bean(name = "payLogTimeoutQueue")
    public DelayQueue<OrderDelayed<Paylog>> payLogTimeoutQueue(){
        DelayQueue<OrderDelayed<Paylog>> payLogQueue= new DelayQueue<OrderDelayed<Paylog>>();
        return payLogQueue;
    }
}

2.2 自定义通用队列属性 OrderDelayed

@Data
@NoArgsConstructor
@ToString
public class OrderDelayed<T> implements Delayed{
    private String     id=NumberUtils.genNO();
    private transient long     delayTime;  //// 延迟时长,这个是必须的属性因为要按照这个判断延时时长。
    private T  data;
    public OrderDelayed(T data,long delayTime,TimeUnit unit) {
        this.delayTime = System.currentTimeMillis() + (delayTime > 0? unit.toMillis(delayTime): 0);
        this.data=data;
    }
    @Override
    public int compareTo(Delayed o) {
        OrderDelayed<?> delayed=(OrderDelayed<?>)o;
        long diff = this.delayTime-delayed.delayTime;
        if (diff <= 0) {
            return -1;
        }else {
            return 1;
        }
    }
    @Override
    public long getDelay(TimeUnit unit) {
         return this.delayTime - System.currentTimeMillis();
    }
}

2.3  添加PayTimeoutHander  添加删除时同时删除redis

@Component
@Slf4j
public class PayTimeoutHander {
    @Value("${deposit.cancel.order.paytimeout}")
    private long DEPOSIT_PAY_TIMEOUT;
    @Qualifier("payLogTimeoutQueue")
    @Autowired
    private DelayQueue<OrderDelayed<Paylog>> payLogTimeoutQueue;/**
     * 添加paylog 到队列,当用户创建order时
     */
     public void addQueue(Paylog paylog,Long expireTime) {
         try
         {
//             this.delayTime = System.currentTimeMillis() + (delayTime > 0? unit.toMillis(delayTime): 0);
             //feeType   //Deposit(押金0)  
             if(paylog!=null&&"0".equalsIgnoreCase(paylog.getFeeType()))
             { //押金
                 expireTime= (expireTime==null)?DEPOSIT_PAY_TIMEOUT:expireTime;
                 //服务重启的情况,可能已经超时
                 if(System.currentTimeMillis()-paylog.getTimeStamp()-TimeUnit.MINUTES.toMillis(DEPOSIT_PAY_TIMEOUT)>0)
                 {
                     expireTime=0l;
                 }
             }
             OrderDelayed<Paylog> orderDelayed = new OrderDelayed<Paylog>(paylog,expireTime, TimeUnit.MINUTES);
             payLogTimeoutQueue.put(orderDelayed);
        addToRedis(orderDelayed) }
catch (Exception e) { log.error("add paylog to PayTimeoutQueue exception and excption : {}",e); } } /*** * 从队列中去取出paylog */ public void takeDataFromQueue() { try { if (payLogTimeoutQueue.size() > 0) { OrderDelayed<Paylog> orderDelayed = payLogTimeoutQueue.take(); checkIsPaidAndDelRedis(orderDelayed); } } catch (InterruptedException e) { log.error("get paylog from PayTimeoutQueue exception :{}", e); } } }

 

2.4 添加队列监听,(服务启动后自动监听,并且加载redis 中的数据)

@Slf4j
@Component
public class PayTimeoutQueueListener implements CommandLineRunner{
    @Autowired
    private PayTimeoutHander payTimeoutHander;
    @Autowired
    private    PaylogService paylogService;
    @Override
    public void run(String... args) throws Exception {
         log.info("^^^^^^^^^^^^^^^^ start  init PayTimeoutQueue ^^^^^^^^^^^^^^^^^^^^^");
         initPayTimeoutQueue();
         log.info("^^^^^^^^^^^^^^^^ end  init PayTimeoutQueue ^^^^^^^^^^^^^^^^^^^^^");
         log.info("^^^^^^^^^^^^^^^^ start  PayTimeoutQueueListener ^^^^^^^^^^^^^^^^^^^^^");
        CompletableFuture.runAsync(() -> {listenQueue();});
    }

    private void initPayTimeoutQueue() {
        List<?> allPaylog = paylogService.getAllPaylogFromRedis();
        allPaylog.forEach(paylogobj->{
            Paylog paylog=(Paylog)paylogobj;
            payTimeoutHander.addQueue(paylog, null);
        });
    }
    private void  listenQueue()
    {
        while (true) {
            try
            {
                payTimeoutHander.takeDataFromQueue();
                Thread.sleep(1000l);
            } catch (Exception e)
            {
                log.error("listenQueue exception :{}",e);
            }
        }
    }
}

 

posted on 2021-02-02 18:50  lshan  阅读(478)  评论(0编辑  收藏  举报