电商项目订单取消(Redis 延迟队列)--1

现功能时的选择很重要,如果你的系统所处理的数据量不是很大,我觉得队列和缓存很适合你,这样你可以对消息的传递更加了解,但你使用MQ,kafka的中间件时,你会发现使用起来更加轻松,但对于数据量大的系统来说,中间件是最好的选择,在这个大数据的时代,高并发,多线程,分布式会越来越重要

数据量小推荐使用:DelayQueue+redis

数据量大推荐使用:RabbitMQ

以下介绍常见的几种

1、JDK的延迟队列
 

思路

该方案是利用JDK自带的DelayQueue来实现,这是一个无界阻塞队列,该队列只有在延迟期满的时候才能从中获取元素,放入DelayQueue中的对象,是必须实现Delayed接口的。

 

其中

poll():获取并移除队列的超时元素,没有则返回空

take():获取并移除队列的超时元素,如果没有则wait当前线程,直到有元素满足超时条件,返回结果。

实现

定义一个类OrderDelay实现Delayed,代码如下

  

  package com.rjzheng.delay2;
    import java.util.concurrent.Delayed;
    import java.util.concurrent.TimeUnit;
    public class OrderDelay implements Delayed {
        private String orderId;
        private long timeout;
        OrderDelay(String orderId, long timeout) {
            this.orderId = orderId;
            this.timeout = timeout + System.nanoTime();
        }
        public int compareTo(Delayed other) {
            if (other == this)
                return 0;
            OrderDelay t = (OrderDelay) other;
            long d = (getDelay(TimeUnit.NANOSECONDS) - t
                    .getDelay(TimeUnit.NANOSECONDS));
            return (d == 0) ? 0 : ((d < 0) ? -1 : 1);
        }
        // 返回距离你自定义的超时时间还有多少
        public long getDelay(TimeUnit unit) {
            return unit.convert(timeout - System.nanoTime(), TimeUnit.NANOSECONDS);
        }
        void print() {
            System.out.println(orderId+"编号的订单要删除啦。。。。");
        }
    }

运行的测试Demo为,我们设定延迟时间为3秒

   

package com.rjzheng.delay2;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.concurrent.DelayQueue;
    import java.util.concurrent.TimeUnit;
    public class DelayQueueDemo {
         public static void main(String[] args) {  
                // TODO Auto-generated method stub  
                List<String> list = new ArrayList<String>();  
                list.add("00000001");  
                list.add("00000002");  
                list.add("00000003");  
                list.add("00000004");  
                list.add("00000005");  
                DelayQueue<OrderDelay> queue = new DelayQueue<OrderDelay>();  
                long start = System.currentTimeMillis();  
                for(int i = 0;i<5;i++){  
                    //延迟三秒取出
                    queue.put(new OrderDelay(list.get(i),  
                            TimeUnit.NANOSECONDS.convert(3, TimeUnit.SECONDS)));  
                        try {  
                             queue.take().print();  
                             System.out.println("After " +   
                                     (System.currentTimeMillis()-start) + " MilliSeconds");  
                    } catch (InterruptedException e) {  
                        // TODO Auto-generated catch block  
                        e.printStackTrace();  
                    }  
                }  
            }  
    }

输出如下

    00000001编号的订单要删除啦。。。。
    After 3003 MilliSeconds
    00000002编号的订单要删除啦。。。。
    After 6006 MilliSeconds
    00000003编号的订单要删除啦。。。。
    After 9006 MilliSeconds
    00000004编号的订单要删除啦。。。。
    After 12008 MilliSeconds
    00000005编号的订单要删除啦。。。。
    After 15009 MilliSeconds

可以看到都是延迟3秒,订单被删除

优缺点

优点:效率高,任务触发时间延迟低。

缺点:

(1)服务器重启后,数据全部消失,怕宕机

(2)集群扩展相当麻烦

(3)因为内存条件限制的原因,比如下单未付款的订单数太多,那么很容易就出现OOM异常

(4)代码复杂度较高
 

2、时间轮算法

思路

先上一张时间轮的图(这图到处都是啦)

时间轮算法可以类比于时钟,如上图箭头(指针)按某一个方向按固定频率轮动,每一次跳动称为一个 tick。这样可以看出定时轮由个3个重要的属性参数,ticksPerWheel(一轮的tick数),tickDuration(一个tick的持续时间)以及 timeUnit(时间单位),例如当ticksPerWheel=60,tickDuration=1,timeUnit=秒,这就和现实中的始终的秒针走动完全类似了。

如果当前指针指在1上面,我有一个任务需要4秒以后执行,那么这个执行的线程回调或者消息将会被放在5上。那如果需要在20秒之后执行怎么办,由于这个环形结构槽数只到8,如果要20秒,指针需要多转2圈。位置是在2圈之后的5上面(20 % 8 + 1)

实现

我们用Netty的HashedWheelTimer来实现 给Pom加上下面的依赖

     <dependency>
                <groupId>io.netty</groupId>
                <artifactId>netty-all</artifactId>
                <version>4.1.24.Final</version>
     </dependency>

测试代码HashedWheelTimerTest如下所示

   

 package com.rjzheng.delay3;
    import io.netty.util.HashedWheelTimer;
    import io.netty.util.Timeout;
    import io.netty.util.Timer;
    import io.netty.util.TimerTask;
    import java.util.concurrent.TimeUnit;
    public class HashedWheelTimerTest {
        static class MyTimerTask implements TimerTask{
            boolean flag;
            public MyTimerTask(boolean flag){
                this.flag = flag;
            }
            public void run(Timeout timeout) throws Exception {
                // TODO Auto-generated method stub
                 System.out.println("要去数据库删除订单了。。。。");
                 this.flag =false;
            }
        }
        public static void main(String[] argv) {
            MyTimerTask timerTask = new MyTimerTask(true);
            Timer timer = new HashedWheelTimer();
            timer.newTimeout(timerTask, 5, TimeUnit.SECONDS);
            int i = 1;
            while(timerTask.flag){
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                System.out.println(i+"秒过去了");
                i++;
            }
        }
    }

输出如下

    1秒过去了
    2秒过去了
    3秒过去了
    4秒过去了
    5秒过去了
    要去数据库删除订单了。。。。
    6秒过去了

优缺点

优点:效率高,任务触发时间延迟时间比delayQueue低,代码复杂度比delayQueue低。

缺点:

(1)服务器重启后,数据全部消失,怕宕机

(2)集群扩展相当麻烦

(3)因为内存条件限制的原因,比如下单未付款的订单数太多,那么很容易就出现OOM异常
 

3、Redisson延迟队列RDelayedQueue

Redisson的集合相关文档

https://github.com/redisson/redisson/wiki/7.-%E5%88%86%E5%B8%83%E5%BC%8F%E9%9B%86%E5%90%88

pom文件加入

  

  <dependency>
       <groupId>org.redisson</groupId>
       <artifactId>redisson</artifactId>
       <version>3.8.0</version>
    </dependency>  

队列中要存放的元素实体
  

  package com.jeiao.redis;
     
    import org.redisson.api.RDelayedQueue;
    import org.redisson.api.RQueue;
     
    import java.text.SimpleDateFormat;
    import java.util.Date;
     
    /**
     * Author:ZhuShangJin
     * Date:2018/9/6
     */
     
    public class CallCdr   {
        private String name;
        private int age;
        private String wife;
        private Double salary;
        private String putTime;
     
        public CallCdr() {
        }
     
        public CallCdr(Double salary) {
            this.salary = salary;
        }
     
        public String getName() {
            return name;
        }
     
        public void setName(String name) {
            this.name = name;
        }
     
        public int getAge() {
            return age;
        }
     
        public void setAge(int age) {
            this.age = age;
        }
     
        public String getWife() {
            return wife;
        }
     
        public void setWife(String wife) {
            this.wife = wife;
        }
     
        public Double getSalary() {
            return salary;
        }
     
        public void setSalary(Double salary) {
            this.salary = salary;
        }
     
        public String getPutTime() {
            return putTime;
        }
     
        public void setPutTime() {
            this.putTime = new SimpleDateFormat("hh:mm:ss").format(new Date());
        }
    }


生成订单并放进延时队列的类


    package com.jeiao.redis;
     
    import org.redisson.Redisson;
    import org.redisson.api.RBlockingQueue;
    import org.redisson.api.RDelayedQueue;
    import org.redisson.api.RQueue;
    import org.redisson.api.RedissonClient;
    import org.redisson.config.Config;
     
    import java.util.concurrent.TimeUnit;
     
    /**
     * Author:ZhuShangJin
     * Date:2018/8/31
     */
    public class RedisPutInQueue {
         public static void main(String args[]) {
             Config config = new Config();
             config.useSingleServer().setAddress("redis://192.168.19.173:6379").setPassword("ps666").setDatabase(2);
             RedissonClient redissonClient = Redisson.create(config);
             RBlockingQueue<CallCdr> blockingFairQueue = redissonClient.getBlockingQueue("delay_queue");
     
             RDelayedQueue<CallCdr> delayedQueue = redissonClient.getDelayedQueue(blockingFairQueue);
             for (int i = 0; i <10 ; i++) {
                 try {
                     Thread.sleep(1*1000);
                 } catch (InterruptedException e) {
                     e.printStackTrace();
                 }
                 // 一分钟以后将消息发送到指定队列
     //相当于1分钟后取消订单
    //             延迟队列包含callCdr 1分钟,然后将其传输到blockingFairQueue中
                 //在1分钟后就可以在blockingFairQueue 中获取callCdr了
                 CallCdr callCdr = new CallCdr(30000.00);
                 callCdr.setPutTime();
                 delayedQueue.offer(callCdr, 1, TimeUnit.MINUTES);
     
             }
    //         在该对象不再需要的情况下,应该主动销毁。
    // 仅在相关的Redisson对象也需要关闭的时候可以不用主动销毁。
             delayedQueue.destroy();
     
    //redissonClient.shutdown();
             }
    }


取消订单的操作类

    package com.jeiao.redis;
     
    import org.redisson.Redisson;
    import org.redisson.api.RBlockingQueue;
    import org.redisson.api.RDelayedQueue;
    import org.redisson.api.RQueue;
    import org.redisson.api.RedissonClient;
    import org.redisson.config.Config;
     
    import java.text.SimpleDateFormat;
    import java.util.Date;
    import java.util.concurrent.TimeUnit;
    import java.util.logging.SimpleFormatter;
     
    /**
     * Author:ZhuShangJin
     * Date:2018/8/31
     */
    public class RedisOutFromQueue {
         public static void main(String args[]) {
             Config config = new Config();
             config.useSingleServer().setAddress("redis://192.168.19.173:6379").setPassword("ps666").setDatabase(2);
             RedissonClient redissonClient = Redisson.create(config);
             RBlockingQueue<CallCdr> blockingFairQueue = redissonClient.getBlockingQueue("delay_queue");
             RDelayedQueue<CallCdr> delayedQueue = redissonClient.getDelayedQueue(blockingFairQueue);
             while (true){
                 CallCdr callCdr = null;
                 try {
                     callCdr = blockingFairQueue.take();
                 } catch (InterruptedException e) {
                     e.printStackTrace();
                 }
                     System.out.println("订单取消时间:"+new SimpleDateFormat("hh:mm:ss").format(new Date())+"==订单生成时间"+callCdr.getPutTime());
     
             }
     
             }
    }

打印结果如下
    订单取消时间:09:30:30==订单生成时间09:29:30
    订单取消时间:09:30:31==订单生成时间09:29:31
    订单取消时间:09:30:32==订单生成时间09:29:32
    订单取消时间:09:30:33==订单生成时间09:29:33
    订单取消时间:09:30:34==订单生成时间09:29:34
    订单取消时间:09:30:35==订单生成时间09:29:35
    订单取消时间:09:30:36==订单生成时间09:29:36
    订单取消时间:09:30:37==订单生成时间09:29:37
    订单取消时间:09:30:38==订单生成时间09:29:38
    订单取消时间:09:30:39==订单生成时间09:29:39

原文:https://blog.csdn.net/zsj777/article/details/82468212
 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值