Comparison of Delayed Task Implementation Strategies in Java: Database Polling, JDK DelayQueue, Time Wheel, Redis, and RabbitMQ
The article examines various delayed‑task solutions for order timeout handling in Java, including database polling with Quartz, JDK DelayQueue, Netty's HashedWheelTimer, Redis sorted‑set and key‑space notifications, and RabbitMQ delayed queues, analyzing their implementation steps, advantages, and drawbacks.
1. Introduction
In many applications a delayed task is required, such as automatically canceling an order after 30 minutes of non‑payment or sending an SMS 60 seconds after order creation. A delayed task differs from a scheduled task in that it has no fixed trigger time, no execution cycle, and usually handles a single job.
2. Solution Analysis
1. Database Polling
Idea
Periodically scan the database with a dedicated thread, check order timestamps, and update or delete expired orders. Suitable for small projects.
Implementation
Uses Quartz. Add the Maven dependency:
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.2.2</version>
</dependency>Demo job class:
public class MyJob implements Job {
public void execute(JobExecutionContext context) throws JobExecutionException {
System.out.println("Scanning database…");
}
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();
}
}Output every 3 seconds: Scanning database…
Pros & Cons
Simple, easy to use, supports clustering.
High memory consumption, latency depends on scan interval, heavy load on DB when order volume is large.
2. JDK DelayQueue
Idea
Leverages the JDK's unbounded blocking DelayQueue , which only releases elements after their delay expires. Elements must implement Delayed .
Implementation
Define OrderDelay implementing Delayed :
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) { /* omitted for brevity */ }
public long getDelay(TimeUnit unit) {
return unit.convert(timeout - System.nanoTime(), TimeUnit.NANOSECONDS);
}
void print() { System.out.println(orderId + " order will be deleted…"); }
}Test demo with 3‑second delay:
public class DelayQueueDemo {
public static void main(String[] args) {
List
list = new ArrayList<>();
list.add("00000001"); // … up to 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(); }
}
}
}Output shows each order being processed after ~3 seconds.
Pros & Cons
High efficiency, low trigger latency.
Data lost on server restart, difficult to scale in a cluster, possible OOM under massive order volume, higher code complexity.
3. Time Wheel Algorithm
Idea
Imitates a clock: a circular buffer with ticksPerWheel , tickDuration , and timeUnit . Tasks are placed into slots based on their expiration relative to the current pointer.
Implementation
Uses Netty's HashedWheelTimer . Add Maven dependency:
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.24.Final</version>
</dependency>Demo:
public class HashedWheelTimerTest {
static class MyTimerTask implements TimerTask {
boolean flag;
MyTimerTask(boolean flag) { this.flag = flag; }
public void run(Timeout timeout) {
System.out.println("Deleting order from DB…");
this.flag = false;
}
}
public static void main(String[] args) throws InterruptedException {
MyTimerTask task = new MyTimerTask(true);
Timer timer = new HashedWheelTimer();
timer.newTimeout(task, 5, TimeUnit.SECONDS);
int i = 1;
while (task.flag) {
Thread.sleep(1000);
System.out.println(i + " seconds passed");
i++;
}
}
}Output shows a 5‑second wait then the task runs.
Pros & Cons
Efficient, lower latency than DelayQueue, simpler code.
Data lost on restart, scaling difficulty, OOM risk under heavy load.
4. Redis Cache
Idea 1 – Sorted Set (ZSET)
Store order ID as member and expiration timestamp as score . Periodically query the smallest score to determine if an order has timed out.
Key commands:
ZADD key score member
ZRANGE key 0 -1 WITHSCORES
ZSCORE key member
ZREM key member
Demo code (production and consumer) creates orders with a 3‑second future score and repeatedly checks/removes expired orders.
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 ts = (int) (cal.getTimeInMillis() / 1000);
getJedis().zadd("OrderId", ts, "OID000000" + i);
System.out.println(System.currentTimeMillis() + "ms: redis created order OID000000" + i);
}
}
public void consumerDelayMessage() {
Jedis jedis = getJedis();
while (true) {
Set
items = jedis.zrangeWithScores("OrderId", 0, 1);
if (items == null || items.isEmpty()) {
System.out.println("No pending tasks");
Thread.sleep(500);
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 = jedis.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();
}
}When many consumers run concurrently, duplicate consumption can occur. The fix is to check the return value of ZREM (must be >0) before processing.
Idea 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.
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 + " 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 scaling, high timing accuracy. Cons: requires Redis maintenance and the Pub/Sub model is fire‑and‑forget, so events can be lost if the subscriber disconnects.
5. Message Queue (RabbitMQ)
RabbitMQ supports delayed queues via message TTL ( x-message-ttl ) and dead‑letter exchange/routing‑key ( x-dead-letter-exchange , x-dead-letter-routing-key ). When a message expires it is routed to a dead‑letter queue, effectively achieving a delay.
Pros & Cons
High efficiency, leverages RabbitMQ's distributed nature, supports persistence for reliability.
Operational complexity and cost increase due to RabbitMQ management.
Overall, the article provides a comprehensive comparison of five delayed‑task solutions, discussing their implementation details, strengths, and weaknesses, helping developers choose the most suitable approach for order‑timeout scenarios.
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.