Various Approaches to Implementing Delayed Tasks in Java Backend Systems
The article compares delayed and scheduled tasks and presents five backend solutions—database polling with Quartz, JDK DelayQueue, Netty's HashedWheelTimer, Redis ZSET, and RabbitMQ delayed queues—detailing their implementations, code examples, outputs, and pros and cons.
In many applications, delayed tasks such as automatically cancelling unpaid orders or sending SMS after order creation are required, and they differ from scheduled tasks in trigger time, execution cycle, and granularity.
Solution Analysis
(1) Database Polling
Idea : A single thread periodically scans the database for orders that have exceeded their timeout and updates or deletes them.
Implementation : The author used Quartz (Maven dependency shown below) to schedule a job that runs every three seconds.
<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) {
System.out.println("要去数据库扫描啦。。。");
}
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();
}
}Running the program prints the message every three seconds.
要去数据库扫描啦。。。Pros : Simple, easy to implement, supports clustering.
Cons : High memory consumption, latency up to the scan interval, heavy DB load for large order volumes.
(2) JDK DelayQueue
Idea : Use the built‑in DelayQueue which only releases elements after their delay expires. Elements 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) { /* omitted for brevity */ }
public long getDelay(TimeUnit unit) {
return unit.convert(timeout - System.nanoTime(), TimeUnit.NANOSECONDS);
}
void print() {
System.out.println(orderId + "编号的订单要删除啦。。。。");
}
} 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");
// ... add more ids ...
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(); }
}
}
}Sample output shows each order being removed after roughly three seconds.
00000001编号的订单要删除啦。。。。
After 3003 MilliSeconds
00000002编号的订单要删除啦。。。。
After 6006 MilliSeconds
...Pros : High efficiency, low trigger latency.
Cons : Data lost on server restart, difficult to scale cluster‑wide, possible OOM for massive pending orders, higher code complexity.
(3) Time Wheel Algorithm
Idea : Use Netty's HashedWheelTimer which mimics a clock wheel; each tick moves a pointer and tasks are placed in slots based on delay.
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) {
System.out.println("要去数据库删除订单了。。。。");
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 + "秒过去了");
i++;
}
}
}Output:
1秒过去了
2秒过去了
3秒过去了
4秒过去了
5秒过去了
要去数据库删除订单了。。。。
6秒过去了Pros : High efficiency, lower latency than DelayQueue , simpler code.
Cons : Same restart‑data‑loss issue, cluster expansion is cumbersome, OOM risk for huge task sets.
(4) Redis Cache
Idea : Store order IDs in a sorted set (ZSET) where the score is the expiration timestamp; a consumer polls the smallest element and processes it when the current time exceeds the score.
# Add a single element
redis> ZADD page_rank 10 google.com
# Add multiple elements
redis> ZADD page_rank 9 baidu.com 8 bing.com
# Query with scores
redis> ZRANGE page_rank 0 -1 WITHSCORES
# Get score of a member
redis> ZSCORE page_rank bing.com
# Remove an element
redis> ZREM page_rank google.com 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 ts = (int)(cal.getTimeInMillis() / 1000);
getJedis().zadd("OrderId", ts, "OID0000001" + i);
System.out.println(System.currentTimeMillis() + "ms:redis生成了一个订单任务:订单ID为" + "OID0000001" + i);
}
}
public void consumerDelayMessage() {
Jedis jedis = getJedis();
while (true) {
Set
items = jedis.zrangeWithScores("OrderId", 0, 1);
if (items == null || items.isEmpty()) { try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } continue; }
int score = (int)items.iterator().next().getScore();
int now = (int)(Calendar.getInstance().getTimeInMillis() / 1000);
if (now >= score) {
String orderId = items.iterator().next().getElement();
Long num = jedis.zrem("OrderId", orderId);
if (num != null && num > 0) {
System.out.println(System.currentTimeMillis() + "ms:redis消费了一个任务:消费的订单OrderId为" + orderId);
}
}
}
}
public static void main(String[] args) {
AppTest app = new AppTest();
app.productionDelayMessage();
app.consumerDelayMessage();
}
}Pros: High accuracy, easy cluster scaling, persistence via Redis.
Cons: Requires Redis maintenance, adds operational complexity.
(5) Message Queue (RabbitMQ)
Idea : Use RabbitMQ's message TTL ( x-message-ttl ) and dead‑letter exchange to create a delayed queue; messages expire after the desired delay and are routed to a consumer queue.
Pros: Efficient, leverages RabbitMQ's distributed nature, supports persistence for reliability.
Cons: Increases system complexity and operational cost due to RabbitMQ management.
Overall, the article provides a comparative study of five backend delayed‑task strategies, each with code samples, execution results, and a clear list of advantages and disadvantages, helping engineers choose the most suitable solution for their specific scenario.
Top Architect
Top Architect focuses on sharing practical architecture knowledge, covering enterprise, system, website, large‑scale distributed, and high‑availability architectures, plus architecture adjustments using internet technologies. We welcome idea‑driven, sharing‑oriented architects to exchange and learn together.
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.