Implementing Delayed Tasks: Strategies, Code Samples, and Trade‑offs
This article explains various approaches to implementing delayed tasks in backend systems, comparing database polling, Java's DelayQueue, Netty's time wheel, Redis sorted sets and keyspace notifications, and RabbitMQ delayed queues, with detailed code examples and analysis of each method's pros and cons.
In many applications a delayed task is required, such as automatically cancelling an unpaid order after 30 minutes or sending a reminder SMS after 60 seconds. The article defines delayed tasks, distinguishes them from scheduled tasks, and presents four practical implementation strategies.
1. Database Polling
Periodically scan the order table to find records whose creation time exceeds the timeout, then update or delete them. The example uses quartz with a Maven dependency and a simple MyJob class that prints "要去数据库扫描啦。。。" 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) throws JobExecutionException {
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();
}
}2. JDK DelayQueue
Uses the built‑in java.util.concurrent.DelayQueue. Each element implements Delayed and becomes available only after its delay expires. The demo defines OrderDelay and a test class that enqueues five orders with a 3‑second delay.
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;
public 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);
}
public 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) throws Exception {
List<String> list = Arrays.asList("00000001","00000002","00000003","00000004","00000005");
DelayQueue<OrderDelay> queue = new DelayQueue<>();
long start = System.currentTimeMillis();
for (int i = 0; i < list.size(); i++) {
queue.put(new OrderDelay(list.get(i), TimeUnit.NANOSECONDS.convert(3, TimeUnit.SECONDS)));
OrderDelay od = queue.take();
od.print();
System.out.println("After " + (System.currentTimeMillis() - start) + " MilliSeconds");
}
}
}3. Netty Time Wheel (HashedWheelTimer)
Implements a circular timing wheel where each tick represents a fixed duration. The article adds the Netty dependency and shows a HashedWheelTimerTest that schedules a task after 5 seconds and prints a counter each second.
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.24.Final</version>
</dependency> package com.rjzheng.delay3;
import io.netty.util.*;
import java.util.concurrent.TimeUnit;
public class HashedWheelTimerTest {
static class MyTimerTask implements TimerTask {
boolean flag;
MyTimerTask(boolean flag) { this.flag = flag; }
public void run(Timeout timeout) throws Exception {
System.out.println("要去数据库删除订单了。。。。");
flag = false;
}
}
public static void main(String[] args) throws Exception {
MyTimerTask timerTask = new MyTimerTask(true);
Timer timer = new HashedWheelTimer();
timer.newTimeout(timerTask, 5, TimeUnit.SECONDS);
int i = 1;
while (timerTask.flag) {
Thread.sleep(1000);
System.out.println(i + "秒过去了");
i++;
}
}
}4. Redis Sorted Set (ZSET) + Polling
Stores order IDs as members and their expiration timestamps as scores. A consumer repeatedly checks the smallest score; if the current time exceeds it, the order is removed and processed. The article also discusses a race‑condition fix by checking the return value of zrem.
package com.rjzheng.delay4;
import redis.clients.jedis.*;
import java.util.*;
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生成了一个订单任务:订单ID为" + "OID000000" + i);
}
}
public void consumerDelayMessage() {
Jedis jedis = getJedis();
while (true) {
Set<Tuple> items = jedis.zrangeWithScores("OrderId", 0, 0);
if (items == null || items.isEmpty()) {
try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); }
continue;
}
Tuple tuple = items.iterator().next();
int score = (int) tuple.getScore();
int now = (int) (Calendar.getInstance().getTimeInMillis() / 1000);
if (now >= score) {
String orderId = tuple.getElement();
Long removed = jedis.zrem("OrderId", orderId);
if (removed != null && removed > 0) {
System.out.println(System.currentTimeMillis() + "ms:redis消费了一个任务:消费的订单OrderId为" + orderId);
}
}
}
}
public static void main(String[] args) {
AppTest app = new AppTest();
app.productionDelayMessage();
app.consumerDelayMessage();
}
}5. Redis Key‑space Notifications
Enables a pub/sub channel that notifies when a key expires. After configuring notify-keyspace-events Ex, the program sets keys with SETEX and listens on __keyevent@0__:expired to trigger order cancellation.
notify-keyspace-events Ex 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 pool = new JedisPool(ADDR, PORT);
private static RedisSub sub = new RedisSub();
public static void init() {
new Thread(() -> pool.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;
pool.getResource().setex(orderId, 3, orderId);
System.out.println(System.currentTimeMillis() + "ms:" + orderId + "订单生成");
}
}
static class RedisSub extends JedisPubSub {
@Override
public void onMessage(String channel, String message) {
System.out.println(System.currentTimeMillis() + "ms:" + message + "订单取消");
}
}
}6. RabbitMQ Delayed Queue
Leverages message TTL (x‑message‑ttl) and dead‑letter exchange (x‑dead‑letter‑exchange) to emulate delayed delivery. The approach provides high efficiency, persistence, and easy horizontal scaling, though it adds operational complexity due to RabbitMQ management.
Each method is accompanied by a concise list of advantages and disadvantages, helping readers choose the most suitable solution for their specific latency, reliability, and scalability requirements.
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.
IT Architects Alliance
Discussion and exchange on system, internet, large‑scale distributed, high‑availability, and high‑performance architectures, as well as big data, machine learning, AI, and architecture adjustments with internet technologies. Includes real‑world large‑scale architecture case studies. Open to architects who have ideas and enjoy sharing.
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.
