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.

IT Architects Alliance
IT Architects Alliance
IT Architects Alliance
Implementing Delayed Tasks: Strategies, Code Samples, and Trade‑offs

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.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaredisNettySchedulingRabbitMQdelayed tasks
IT Architects Alliance
Written by

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.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.