本文共 5542 字,大约阅读时间需要 18 分钟。
在五一假期中,我原本计划系统地学习一本技术书籍并完成两篇文章,但由于自律性不足,没有按计划进行。回顾这段经历,我深刻认识到与优秀开发者相比,我在学习效率和专注度上还有很大差距。这种自我反省促使我重新振作,重新开始学习和实践。
本文将围绕如何实现延时队列(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; }} 使用示例:
DelayQueuedelayQueue = 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);}
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("我是定时任务!"); }}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); }} 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();}时间轮是一种基于精度时间的任务队列,广泛应用于 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/