Mastering Delayed Tasks: From Quartz to Redis and RabbitMQ
This article explores various techniques for implementing delayed tasks in Java, comparing Quartz database polling, JDK DelayQueue, Netty's HashedWheelTimer, Redis sorted sets, and RabbitMQ delayed queues, detailing their implementations, advantages, drawbacks, and practical code examples for handling order timeouts.
In development, delayed tasks such as automatically canceling an order after 30 minutes or sending an SMS after 60 seconds are common requirements.
These tasks are called delayed tasks and differ from scheduled tasks in several ways.
Scheduled tasks have a fixed trigger time; delayed tasks do not.
Scheduled tasks have execution cycles; delayed tasks execute once after an event.
Scheduled tasks often batch multiple jobs; delayed tasks usually handle a single job.
Solution Analysis
(1) Database Polling (Quartz)
Idea: a thread periodically scans the database for overdue orders and updates or deletes them.
Implementation using Quartz. Maven dependency:
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.2.2</version>
</dependency>Demo job prints a message while scanning:
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();
}
}Pros: simple, supports clustering. Cons: high memory consumption, latency up to the scan interval, heavy DB load for millions of orders.
(2) JDK DelayQueue
Idea: use java.util.concurrent.DelayQueue, which holds elements implementing Delayed and releases them when their delay expires.
Implementation of OrderDelay:
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);
}
public void print() {
System.out.println(orderId + " order will be deleted...");
}
}Demo runs five orders with a 3‑second delay, printing each order ID after the delay:
public class DelayQueueDemo {
public static void main(String[] args) {
List<String> list = Arrays.asList("00000001","00000002","00000003","00000004","00000005");
DelayQueue<OrderDelay> 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)));
OrderDelay od = queue.take();
od.print();
System.out.println("After " + (System.currentTimeMillis() - start) + " Milliseconds");
}
}
}Pros: high efficiency, low trigger latency. Cons: data lost on server restart, difficult cluster expansion, possible OOM for massive pending orders, higher code complexity.
(3) Netty HashedWheelTimer
Idea: use Netty's HashedWheelTimer, a time‑wheel algorithm similar to a clock.
Dependency:
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.24.Final</version>
</dependency>Demo task prints a message after 5 seconds:
public class HashedWheelTimerTest {
static class MyTimerTask implements TimerTask {
boolean flag = true;
public void run(Timeout timeout) {
System.out.println("Deleting order...");
flag = false;
}
}
public static void main(String[] args) {
MyTimerTask task = new MyTimerTask();
Timer timer = new HashedWheelTimer();
timer.newTimeout(task, 5, TimeUnit.SECONDS);
int i = 1;
while (task.flag) {
try { Thread.sleep(1000); } catch (InterruptedException e) {}
System.out.println(i + " seconds passed");
i++;
}
}
}Pros: efficient, low latency. Cons: similar restart and clustering issues as DelayQueue.
(4) Redis Sorted Set (ZSET)
Idea: store order ID as member and expiration timestamp as score in a Redis ZSET, periodically scan the first element.
Key commands:
ZADD key score member
ZRANGE key start stop WITHSCORES
ZSCORE key member
ZREM key member
Consumer checks if current time >= score, removes the member, and processes the order.
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 void productionDelayMessage() {
for (int i = 0; i < 5; i++) {
Calendar cal = Calendar.getInstance();
cal.add(Calendar.SECOND, 3);
int ts = (int) (cal.getTimeInMillis() / 1000);
jedisPool.getResource().zadd("OrderId", ts, "OID000000" + i);
}
}
public void consumerDelayMessage() {
while (true) {
Set<Tuple> items = jedisPool.getResource().zrangeWithScores("OrderId", 0, 0);
if (items.isEmpty()) {
try { Thread.sleep(500); } catch (InterruptedException e) {}
continue;
}
Tuple t = items.iterator().next();
int score = (int) t.getScore();
int now = (int) (Calendar.getInstance().getTimeInMillis() / 1000);
if (now >= score) {
Long removed = jedisPool.getResource().zrem("OrderId", t.getElement());
if (removed != null && removed > 0) {
System.out.println("Consumed order " + t.getElement());
}
}
}
}
}Pros: reliable, easy to scale horizontally, accurate timing. Cons: requires Redis maintenance; Redis Pub/Sub is fire‑and‑forget, so notifications can be lost on client disconnect.
(5) RabbitMQ Delayed Queue
RabbitMQ supports per‑message TTL (x-message-ttl) and dead‑letter exchange to emulate delayed messages.
Advantages: high efficiency, leverages RabbitMQ's clustering for easy horizontal scaling, messages can be persisted for reliability.
Drawbacks: adds operational complexity and cost due to the need to manage RabbitMQ.
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.
Architect's Guide
Dedicated to sharing programmer-architect skills—Java backend, system, microservice, and distributed architectures—to help you become a senior architect.
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.
