延时队列

延时队列,顾名思义是带有延时功能的消息队列,列举几个使用场景:

  1. 定时发公告
  2. 用户下单30分钟后未付款自动关闭订单
  3. 用户下单后延时短信提醒
  4. 延时关闭空闲客户端连接
  5. ......

延时队列实现方案:

  • Java中的DelayQueue

DelayQueue是一个无界阻塞队列,只有消息到期才能从中获取到消息。话不多说,实现一个Demo。

消息实体:

public class Message implements Delayed {

    private String body;

    private long delayTime;

    private long putTime;

    /**
     * 用于返回剩余时间
     * 消息是否到期则是通过此方法判断
     * 返回小于等于0则到期
     */
    @Override
    public long getDelay(TimeUnit unit) {
        return unit.convert(putTime + delayTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
    }

    /**
     * 因为Delayed继承于Comparable
     * 所以需要实现compareTo方法,用于排序
     * 该对象(this)小于、等于或大于指定对象(o),则分别返回负整数、零或正整数。
     */
    @Override
    public int compareTo(Delayed o) {
        long result = this.getDelay(TimeUnit.NANOSECONDS) - o.getDelay(TimeUnit.NANOSECONDS);
        if (result < 0) {
            return -1;
        } else if (result > 0) {
            return 1;
        } else {
            return 0;
        }
    }

    // getter/setter

    public Message(String body, long delayTime) {
        this.body = body;
        this.delayTime = delayTime;
        this.putTime = new Date().getTime();
    }
}

消费者线程:

public class Consumer implements Runnable {

    // 延时队列
    private DelayQueue<Message> queue;

    public Consumer(DelayQueue<Message> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        while (true) {
            try {
                Message message = queue.take();
                System.out.println("接收到消息内容:" + message.getBody());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }

    // getter/setter
}

生产者:

public class Producer {

    public static void main(String[] args) {
        DelayQueue<Message> queue = new DelayQueue<>();

        // 延时5秒
        Message m1 = new Message("兄dei吃饭了吗?", 5000);

        // 延时15秒
        Message m2 = new Message("开黑吗???我玩亚索!", 15000);

        // 将延时消息放到延时队列
        queue.add(m1);
        queue.offer(m2);

        new Thread(new Consumer(queue)).start();

    }
}

成功运行,等待5秒,15秒,输出消息。

 

要点:

1.add方法跟offer方法其实是一样的

2.需要始终保证最快到期的消息在队首(关键在于消息实体的compareTo方法),否则会导致到期消息无法及时消费

查看DelayQueue.take()源码可以发现,它会获取队首消息并判断是否到期,队首消息到期才返回结果;
队首消息没到期:
  • 如果有其他线程在等待(leader != null),则阻塞当前线程
  • 如果当前没有其他线程等待(leader == null),则阻塞当前线程直到延迟时间

 

  • Redis实现的DelayQueue

使用redis提供的有序数据结构zset,把过期时间戳作为score。

当然已经有现成的轮子了------Redisson的RDelayedQueue + RBlockingDeque

原理就是使用redis的zset + list,先来个Demo。

消息实体:

public class Order {

    private String createdTime;

    public Order() {
        this.createdTime = new SimpleDateFormat("hh:mm:ss").format(new Date());
    }

    public String getCreatedTime() {
        return createdTime;
    }
}

生产者:

public class Producer {

    public static void main(String[] args) {
        Config config = new Config();
        config.useSingleServer().setAddress("redis://192.168.3.183:6379");
        RedissonClient redissonClient = Redisson.create(config);
        RBlockingDeque<Order> blockingDeque = redissonClient.getBlockingDeque("delay_queue");
        RDelayedQueue<Order> delayedQueue = redissonClient.getDelayedQueue(blockingDeque);

        for (int i = 0; i < 10; i++) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Order order = new Order();
            delayedQueue.offer(order, 1, TimeUnit.SECONDS);
            System.out.println("成功发送延时队列");
        }

        delayedQueue.destroy();
    }
}

消费者:

public class Consumer {

    public static void main(String[] args) {
        Config config = new Config();
        config.useSingleServer().setAddress("redis://192.168.3.183:6379");
        RedissonClient redissonClient = Redisson.create(config);
        RBlockingDeque<Order> blockingDeque = redissonClient.getBlockingDeque("delay_queue");

        while (true) {
            Order order = null;
            try {
                // 如果没有到期消息,返回null
                order = blockingDeque.take();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            if (order != null)
                System.out.println("订单取消时间:" + new SimpleDateFormat("hh:mm:ss").format(new Date()) + "==订单生成时间" + order.getCreatedTime());
            else
                continue;
        }

    }
}

分别执行生产者/消费者。

Redisson底层其实就是在执行Lua脚本,源码解析参考

https://www.jianshu.com/p/8fa478da3b00

 

以上代码仓库

https://gitee.com/Deep_feel/study_notes/tree/master/test/src/main/java/delayQueue

  • 0
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值