Implementing Delayed Tasks in Java: Quartz, DelayQueue, Netty Timer, Redis and RabbitMQ
This article compares several Java-based approaches for implementing delayed tasks, including database polling with Quartz, JDK DelayQueue, Netty's HashedWheelTimer, Redis sorted sets and keyspace notifications, and RabbitMQ dead‑letter queues, analyzing their advantages, disadvantages, and code implementations.
In many applications, delayed tasks such as order cancellation or reminder notifications are required. This article reviews six common implementations in Java, evaluates their trade‑offs, and provides concrete code examples.
1. Database polling with Quartz
Using a scheduled Quartz job to periodically scan a database for overdue orders. The job is defined in MyJob and scheduled with a Trigger that runs every three seconds.
public class MyJob implements Job {
public void execute(JobExecutionContext context) {
System.out.println("要去数据库扫描啦。。。");
}
}
Scheduler scheduler = new StdSchedulerFactory().getScheduler();
JobDetail job = JobBuilder.newJob(MyJob.class).withIdentity("job1", "group1").build();
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("trigger1", "group3")
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(3)
.repeatForever())
.build();
scheduler.scheduleJob(job, trigger);
scheduler.start();Pros: Simple, supports clustering. Cons: High memory consumption, latency up to the scan interval, heavy DB load for large tables.
2. JDK DelayQueue
DelayQueue stores elements that implement Delayed. Each element holds the target execution time, and take() blocks until the delay expires.
public class OrderDelay implements Delayed {
private String orderId;
private long timeout; // absolute timestamp in nanoseconds
public OrderDelay(String orderId, long delayMillis) {
this.orderId = orderId;
this.timeout = System.nanoTime() + TimeUnit.NANOSECONDS.convert(delayMillis, TimeUnit.MILLISECONDS);
}
@Override
public long getDelay(TimeUnit unit) {
return unit.convert(timeout - System.nanoTime(), TimeUnit.NANOSECONDS);
}
@Override
public int compareTo(Delayed o) {
return Long.compare(this.timeout, ((OrderDelay) o).timeout);
}
public void print() { System.out.println(orderId + "编号的订单要删除啦。。。。"); }
}
DelayQueue<OrderDelay> queue = new DelayQueue<>();
queue.put(new OrderDelay("OID000001", 3000)); // 3 seconds
OrderDelay expired = queue.take();
expired.print();Pros: High efficiency, low latency. Cons: Data lost on restart, difficult to scale across nodes, possible OOM for massive queues.
3. Netty HashedWheelTimer
Netty provides a time‑wheel implementation that approximates a clock. It is lightweight and offers millisecond precision.
HashedWheelTimer timer = new HashedWheelTimer();
TimerTask task = timeout -> System.out.println("要去数据库删除订单了。。。。");
// Execute after 5 seconds
timer.newTimeout(task, 5, TimeUnit.SECONDS);
// Keep the JVM alive for demonstration
Thread.sleep(6000);Pros: Efficient, low latency. Cons: Same durability and clustering limitations as DelayQueue.
4. Redis based solutions
4.1 Sorted Set (ZSET) approach – Store order IDs as members and the expiration timestamp as the score. A consumer repeatedly reads the smallest score and processes orders whose score is less than the current time.
// Producer
Jedis jedis = new Jedis("127.0.0.1", 6379);
long expireAt = System.currentTimeMillis() / 1000 + 3; // 3‑second TTL
jedis.zadd("OrderId", expireAt, "OID0000001");
// Consumer loop
while (true) {
Set<Tuple> items = jedis.zrangeWithScores("OrderId", 0, 0);
if (items.isEmpty()) { Thread.sleep(500); continue; }
Tuple t = items.iterator().next();
long score = (long) t.getScore();
long now = System.currentTimeMillis() / 1000;
if (now >= score) {
String orderId = t.getElement();
Long removed = jedis.zrem("OrderId", orderId);
if (removed != null && removed > 0) {
System.out.println("消费了订单:" + orderId);
}
} else {
Thread.sleep(500);
}
}To avoid multiple consumers processing the same order, the removal result is checked; only a positive return value indicates successful claim.
4.2 Keyspace notifications – Enable notify-keyspace-events Ex in redis.conf. When a key expires, Redis publishes a message on the __keyevent@0__:expired channel.
JedisPubSub sub = new JedisPubSub() {
@Override
public void onMessage(String channel, String message) {
System.out.println(System.currentTimeMillis() + "ms:" + message + "订单取消");
}
};
new Thread(() -> new Jedis("127.0.0.1", 6379).subscribe(sub, "__keyevent@0__:expired")).start();
// Producer creates a key with TTL 3 seconds
Jedis jedis = new Jedis("127.0.0.1", 6379);
jedis.setex("OID000001", 3, "placeholder");
System.out.println(System.currentTimeMillis() + "ms:" + "OID000001" + "订单生成");Pros: Persistent storage, easy horizontal scaling, precise timing. Cons: Requires Redis maintenance; Pub/Sub is fire‑and‑forget, so messages may be lost if the subscriber disconnects.
5. RabbitMQ dead‑letter queue
RabbitMQ can emulate delayed delivery by setting x-message-ttl on a queue and configuring x-dead-letter-exchange to forward expired messages to the final processing queue.
# Declare a delay queue
channel.queueDeclare("delay_queue", true, false, false, Map.of(
"x-message-ttl", 3000,
"x-dead-letter-exchange", "",
"x-dead-letter-routing-key", "process_queue"
));
# Publish a message
channel.basicPublish("", "delay_queue", null, "order:123".getBytes());
# Consumer on the processing queue
channel.basicConsume("process_queue", true, (consumerTag, delivery) -> {
System.out.println("处理延时订单: " + new String(delivery.getBody()));
}, consumerTag -> {});Pros: High reliability, built‑in persistence, easy clustering. Cons: Adds operational complexity and requires a running RabbitMQ broker.
6. Summary
The article presents six practical ways to implement delayed tasks in Java, ranging from simple database polling to sophisticated message‑broker solutions. Choosing the right approach depends on factors such as latency requirements, durability, scalability, and operational overhead.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Code Ape Tech Column
Former Ant Group P8 engineer, pure technologist, sharing full‑stack Java, job interview and career advice through a column. Site: java-family.cn
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
