Design and Implementation of Delayed Task Processing in Java: Database Polling, DelayQueue, Time Wheel, Redis, and RabbitMQ
The article explains the concept of delayed tasks versus scheduled tasks and presents five practical Java implementations—database polling with Quartz, JDK DelayQueue, Netty's HashedWheelTimer, Redis sorted sets and keyspace notifications, and RabbitMQ delayed queues—detailing their code, advantages, and drawbacks.
In development, delayed tasks such as automatically canceling unpaid orders after 30 minutes or sending an SMS after 60 seconds are common. Unlike scheduled tasks, delayed tasks have no fixed trigger time or period and usually handle a single operation.
Solution Analysis
(1) Database Polling
Suitable for small projects; a thread periodically scans the database for expired orders and updates or deletes them. Implementation example using Quartz with Maven dependency and a demo job class.
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.2.2</version>
</dependency> package com.rjzheng.delay1;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
public class MyJob implements Job {
public void execute(JobExecutionContext context) throws JobExecutionException {
System.out.println("Scanning database for delayed tasks…");
}
public static void main(String[] args) throws Exception {
JobDetail jobDetail = JobBuilder.newJob(MyJob.class)
.withIdentity("job1", "group1")
.build();
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("trigger1", "group3")
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(3)
.repeatForever())
.build();
Scheduler scheduler = new StdSchedulerFactory().getScheduler();
scheduler.scheduleJob(jobDetail, trigger);
scheduler.start();
}
}Advantages: simple, cluster‑friendly. Disadvantages: high memory consumption, latency up to the scan interval, heavy DB load under large order volumes.
(2) JDK DelayQueue
Uses the built‑in DelayQueue which releases elements only after their delay expires. The element must implement 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 + " order will be deleted…");
}
} package com.rjzheng.delay2;
import java.util.*;
import java.util.concurrent.*;
public class DelayQueueDemo {
public static void main(String[] args) {
List
list = new ArrayList<>();
list.add("00000001");
list.add("00000002");
list.add("00000003");
list.add("00000004");
list.add("00000005");
DelayQueue
queue = new DelayQueue<>();
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) {
e.printStackTrace();
}
}
}
}Advantages: high efficiency, low trigger latency. Disadvantages: data lost on server restart, difficult cluster scaling, possible OOM with massive pending orders, higher code complexity.
(3) Time Wheel (HashedWheelTimer)
Imitates a clock; each tick moves a pointer. Netty’s HashedWheelTimer is used.
package com.rjzheng.delay3;
import io.netty.util.*;
import java.util.concurrent.TimeUnit;
public class HashedWheelTimerTest {
static class MyTimerTask implements TimerTask {
boolean flag = true;
public void run(Timeout timeout) throws Exception {
System.out.println("Deleting order from DB…");
flag = false;
}
}
public static void main(String[] argv) {
MyTimerTask timerTask = new MyTimerTask();
Timer timer = new HashedWheelTimer();
timer.newTimeout(timerTask, 5, TimeUnit.SECONDS);
int i = 1;
while (timerTask.flag) {
try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println(i + " seconds passed");
i++;
}
}
}Advantages: efficient, lower latency than DelayQueue, simpler code. Disadvantages: same restart‑loss issue and scaling difficulty.
(4) Redis Zset
Orders are stored in a sorted set with the expiration timestamp as the score. A consumer scans the set, compares the current time, and removes expired orders.
package com.rjzheng.delay4;
import java.util.*;
import redis.clients.jedis.*;
public class AppTest {
private static final String ADDR = "127.0.0.1";
private static final int PORT = 6379;
private static JedisPool jedisPool = new JedisPool(ADDR, PORT);
public static Jedis getJedis() { return jedisPool.getResource(); }
public void productionDelayMessage() {
for (int i = 0; i < 5; i++) {
Calendar cal = Calendar.getInstance();
cal.add(Calendar.SECOND, 3);
int score = (int) (cal.getTimeInMillis() / 1000);
getJedis().zadd("OrderId", score, "OID0000001" + i);
System.out.println(System.currentTimeMillis() + "ms: redis generated order OID0000001" + i);
}
}
public void consumerDelayMessage() {
while (true) {
Set
items = getJedis().zrangeWithScores("OrderId", 0, 1);
if (items == null || items.isEmpty()) {
System.out.println("No pending tasks");
try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); }
continue;
}
int score = (int) ((Tuple) items.toArray()[0]).getScore();
int now = (int) (Calendar.getInstance().getTimeInMillis() / 1000);
if (now >= score) {
String orderId = ((Tuple) items.toArray()[0]).getElement();
Long removed = getJedis().zrem("OrderId", orderId);
if (removed != null && removed > 0) {
System.out.println(System.currentTimeMillis() + "ms: redis consumed order " + orderId);
}
}
}
}
public static void main(String[] args) {
AppTest app = new AppTest();
app.productionDelayMessage();
app.consumerDelayMessage();
}
}To avoid multiple consumers processing the same order, the removal result is checked; only the consumer that successfully removes the element proceeds.
(5) Redis Keyspace Notifications
Enables callbacks when a key expires. After configuring notify-keyspace-events Ex , a subscriber receives expiration events and can handle order cancellation.
package com.rjzheng.delay5;
import redis.clients.jedis.*;
public class RedisTest {
private static final String ADDR = "127.0.0.1";
private static final int PORT = 6379;
private static JedisPool jedis = new JedisPool(ADDR, PORT);
private static RedisSub sub = new RedisSub();
public static void init() {
new Thread(() -> jedis.getResource().subscribe(sub, "__keyevent@0__:expired")).start();
}
public static void main(String[] args) throws InterruptedException {
init();
for (int i = 0; i < 10; i++) {
String orderId = "OID000000" + i;
jedis.getResource().setex(orderId, 3, orderId);
System.out.println(System.currentTimeMillis() + "ms:" + orderId + " order created");
}
}
static class RedisSub extends JedisPubSub {
@Override
public void onMessage(String channel, String message) {
System.out.println(System.currentTimeMillis() + "ms:" + message + " order cancelled");
}
}
}Pros: persistence in Redis, easy cluster expansion, accurate timing. Cons: requires Redis maintenance and Pub/Sub is fire‑and‑forget, so events may be lost if the client disconnects.
(6) RabbitMQ Delayed Queue
Leverages message TTL and dead‑letter exchange to implement delayed messages. Provides high efficiency, distributed capabilities, and message durability, but adds operational complexity and cost.
Overall, each solution balances trade‑offs between simplicity, performance, reliability, and scalability; the choice depends on the specific requirements of the system.
Architecture Digest
Focusing on Java backend development, covering application architecture from top-tier internet companies (high availability, high performance, high stability), big data, machine learning, Java architecture, and other popular fields.
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.