博客
关于我
一口气说出 6种 延时队列的实现方法,面试官也得服
阅读量:798 次
发布时间:2023-04-02

本文共 5542 字,大约阅读时间需要 18 分钟。

延时队列的实现与应用

在五一假期中,我原本计划系统地学习一本技术书籍并完成两篇文章,但由于自律性不足,没有按计划进行。回顾这段经历,我深刻认识到与优秀开发者相比,我在学习效率和专注度上还有很大差距。这种自我反省促使我重新振作,重新开始学习和实践。

本文将围绕如何实现延时队列(DelayQueue)进行详细探讨,结合实际项目中的应用场景,并提供多种实现方式的代码示例。


一、延时队列的应用场景

延时队列是一种结合了队列和定时任务特性的数据结构,主要用于处理需要在特定时间后才进行处理的任务。常见的应用场景包括:

  • 订单自动取消:用户在下单后若未支付,超时后自动取消订单。
  • 发送通知:在用户下单后,经过固定时间(如60秒)后发送短信或推送通知。
  • 订单状态处理:当订单处于某个未完成状态时,及时触发关单操作并退还库存。
  • 系统冻结处理:如新商户未上传商品信息,经过一段时间后冻结商铺。
  • 这些场景都可以通过延时队列来高效解决。


    二、延时队列的实现方法

    在实际开发中,延时队列可以通过多种方式实现。以下是一些常见且高效的方法:

    1. JDK中的DelayQueue

    Java标准库中的 DelayQueue 是一个基于优先队列实现的延时队列,支持设置元素的延迟时间。其核心原理是将每个元素包装在一个 Delayed 对象中,并根据延迟时间进行排序。当延迟时间到了,元素才会被取出处理。

    实现代码示例:

    public class Order implements Delayed {    private final String name;    private final long time;    public Order(String name, long delay, TimeUnit unit) {        this.name = name;        this.time = System.currentTimeMillis() + (delay > 0 ? unit.toMillis(delay) : 0);    }    @Override    public long getDelay(TimeUnit unit) {        return time - System.currentTimeMillis();    }    @Override    public int compareTo(Delayed o) {        Order other = (Order) o;        long diff = this.time - other.time;        return diff <= 0 ? -1 : 1;    }}

    使用示例:

    DelayQueue
    delayQueue = new DelayQueue<>();delayQueue.put(new Order("Order1", 5, TimeUnit.SECONDS));delayQueue.put(new Order("Order2", 10, TimeUnit.SECONDS));delayQueue.put(new Order("Order3", 15, TimeUnit.SECONDS));while (!delayQueue.isEmpty()) { Order task = delayQueue.poll(); if (task != null) { System.out.println(task.name + "被取消,取消时间:" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); } Thread.sleep(1000);}

    2. Quartz定时任务

    Quartz 是一款经典的任务调度框架,常用于实现定时任务。通过 Quartz,可以在短时间内处理大量超时订单,避免因超时导致的业务逻辑失败。

    实现步骤:

  • 引入依赖包:

    org.springframework.boot
    spring-boot-starter-quartz
  • 启用定时任务:

    @EnableScheduling@SpringBootApplicationpublic class DelayqueueApplication {    public static void main(String[] args) {        SpringApplication.run(DelayqueueApplication.class, args);    }}
  • 编写定时任务:

    @Componentpublic class QuartzDemo {    @Scheduled(cron = "0/5 * * * * ?")    public void process() {        System.out.println("我是定时任务!");    }}

  • 3. Redis Zset(有序集合)

    Redis 的 Zset 数据结构可以用来实现延时队列。通过设置元素的分数值(score),可以按时间排序。消息在过期时会自动被移除。

    实现步骤:

  • 启用 Redis 过期事件监听:

    notify-keyspace-events Ex
  • 编写 Redis监听器:

    @Componentpublic class RedisKeyExpirationListener extends KeyExpirationEventMessageListener {    public RedisKeyExpirationListener(RedisMessageListenerContainer listenerContainer) {        super(listenerContainer);    }    @Override    public void onMessage(Message message, byte[] pattern) {        String expiredKey = message.toString();        System.out.println("监听到 key:" + expiredKey + " 已过期");    }}
  • 消费端处理:

    public void pollOrderQueue() {    while (true) {        Set
    set = jedis.zrangeWithScores(DELAY_QUEUE, 0, 0); if (set.isEmpty()) { System.out.println("zset empty"); return; } Tuple tuple = set.iterator().next(); String value = tuple.getElement(); long score = tuple.getScore(); Calendar cal = Calendar.getInstance(); int nowSecond = (int) (cal.getTimeInMillis() / 1000); if (nowSecond > score) { jedis.zrem(DELAY_QUEUE, value); System.out.println sdf.format(new Date()) + " removed key: " + value); } Thread.sleep(1000); }}

  • 4. RabbitMQ TTL和DLX

    RabbitMQ 可以通过设置消息的 TTL(时间到生存)和 DLX(死信交换机)来实现延时队列。消息在到期后会转为死信,重新路由到指定队列处理。

    实现步骤:

  • 设置队列和消息的 TTL:

    x-message-ttl 300000
  • 配置 DLX:

    x-dead-letter-exchange my-exchangex-dead-letter-routing-key my-queue
  • 发送消息:

    public void send(String delayTimes) {    amqpTemplate.convertAndSend("order.pay.exchange", "order.pay.queue", "大家好我是延迟数据", message -> {        message.getMessageProperties().setExpiration(String.valueOf(delayTimes));        return message;    });}
  • 处理死信:

    @Bean(name = "order.delay.queue")public Queue getMessageQueue() {    return QueueBuilder.durable(RabbitConstant.DEAD_LETTER_QUEUE)        .withArgument("x-dead-letter-exchange", RabbitConstant.ORDER_CLOSE_EXCHANGE)        .withArgument("x-dead-letter-routing-key", RabbitConstant.ORDER_CLOSE_QUEUE)        .build();}

  • 5. 时间轮算法(Netty实现)

    时间轮是一种基于精度时间的任务队列,广泛应用于 Netty 等高性能网络框架中。其核心原理是将任务按时间分成不同的“轮”(wheel),每个轮代表特定的时间间隔。

    实现代码示例:

    public class NettyDelayQueue {    public static void main(String[] args) {        final Timer timer = new HashedWheelTimer(            Executors.defaultThreadFactory(), 5, TimeUnit.SECONDS, 2        );        TimerTask task1 = new TimerTask() {            @Override            public void run(Timeout timeout) throws Exception {                System.out.println("order1 5s 后执行");            }        };        Timer timeout1 = timer.newTimeout(task1, 5, TimeUnit.SECONDS);        TimerTask task2 = new TimerTask() {            @Override            public void run(Timeout timeout) throws Exception {                System.out.println("order2 10s 后执行");            }        };        Timer timeout2 = timer.newTimeout(task2, 10, TimeUnit.SECONDS);        TimerTask task3 = new TimerTask() {            @Override            public void run(Timeout timeout) throws Exception {                System.out.println("order3 15s 后执行一次");            }        };        Timer timeout3 = timer.newTimeout(task3, 15, TimeUnit.SECONDS);    }}

    三、总结

    本文介绍了几种常见的延时队列实现方式,包括 JDK 的 DelayQueue、Quartz、Redis Zset、RabbitMQ TTL/DLX 以及 Netty 的时间轮算法。每种方法都有其适用场景和优势,选择哪种方式取决于具体的业务需求和性能要求。

    通过实际项目实践,我深刻认识到延时队列在现代应用中的重要性。它不仅能够解决资源有限的处理能力问题,还能优化业务流程,提升用户体验。希望本文能为开发者提供有价值的参考和启发。

    转载地址:http://plefk.baihongyu.com/

    你可能感兴趣的文章
    oracle基础 管理索引
    查看>>
    ORACLE多表关联UPDATE 语句
    查看>>
    Oracle多表查询与数据更新
    查看>>
    oracle如何修改单个用户密码永不过期
    查看>>
    oracle字符集
    查看>>
    oracle存储参数(storage子句)含义及设置技巧
    查看>>
    Oracle学习
    查看>>
    Oracle学习第五课
    查看>>
    Oracle安装、Navicat for Oracle、JDBCl连接、获取表结构
    查看>>
    ORACLE客户端连接
    查看>>
    oracle常用SQL——创建用户、表空间、授权(12C)
    查看>>
    Oracle数据库异常--- oracle_10g_登录em后,提示java.lang.Exception_Exception_in_sending_Request__null或Connection
    查看>>
    oracle数据库异常---SP2-1503: 无法初始化 Oracle 调用界面 SP2-1503: 无法初始化 Oracle 问题的解决办法
    查看>>
    oracle数据库笔记---oracleweb视图使用流程,及plsql安装
    查看>>
    oracle数据库笔记---pl/sql的基础使用方法
    查看>>
    Transformer 架构解释
    查看>>
    Oracle数据库表空间 数据文件 用户 以及表创建的SQL代码
    查看>>
    oracle数据库零碎---Oracle Merge 使用,表中存在数据就修改,没有数据自动添加
    查看>>
    Oracle数据库验证IMP导入元数据是否会覆盖历史表数据
    查看>>
    Oracle未开启审计情况下追踪表变更记录
    查看>>