Implementing Delayed Tasks in E‑commerce Systems: Techniques, Code Samples, and Trade‑offs

This article explains the concept of delayed tasks versus scheduled tasks, presents common e‑commerce scenarios, and provides multiple implementation approaches—including JDK DelayQueue, ScheduledExecutorService, database polling, Redis sorted sets, Pulsar, ActiveMQ, and Netty time‑wheel—along with code examples and practical considerations.

Full-Stack Internet Architecture
Full-Stack Internet Architecture
Full-Stack Internet Architecture
Implementing Delayed Tasks in E‑commerce Systems: Techniques, Code Samples, and Trade‑offs

In modern e‑commerce, automatic order confirmation after a configurable delay is a typical delayed‑task scenario. Unlike fixed‑time scheduled tasks, delayed tasks are triggered by business events and usually execute only once.

Business scenarios include auto‑closing unpaid orders, automatic refunds after seller inactivity, and sending SMS notifications a few seconds after order creation.

1. JDK DelayQueue – Uses an unbounded blocking queue where elements implement Delayed. The following code demonstrates a producer adding delayed tasks and a consumer taking them:

public class DelayQueueTest {
    public static void main(String[] args) {
        DelayQueue<DelayTask> dq = new DelayQueue<>();
        // producer creates a 2‑second delayed task
        new Thread(new ProducerDelay(dq, 2000)).start();
        // consumer polls the queue
        new Thread(new ConsumerDelay(dq)).start();
    }
}

class ProducerDelay implements Runnable {
    DelayQueue<DelayTask> delayQueue;
    int delaySecond;
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    public ProducerDelay(DelayQueue<DelayTask> delayQueue, int delaySecond) {
        this.delayQueue = delayQueue;
        this.delaySecond = delaySecond;
    }
    @Override
    public void run() {
        for (int i = 1; i < 6; i++) {
            delayQueue.add(new DelayTask(delaySecond, i + ""));
            System.out.println(sdf.format(new Date()) + " Thread " + Thread.currentThread() + " added delayed task, id=" + i);
            try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); }
        }
    }
}

class ConsumerDelay implements Runnable {
    DelayQueue<DelayTask> delayQueue;
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    public ConsumerDelay(DelayQueue<DelayTask> delayQueue) { this.delayQueue = delayQueue; }
    @Override
    public void run() {
        while (true) {
            DelayTask delayTask = null;
            try { delayTask = delayQueue.take(); } catch (Exception e) { e.printStackTrace(); }
            if (delayTask != null) {
                System.out.println(sdf.format(new Date()) + " Thread " + Thread.currentThread() + " consumed delayed task, id=" + delayTask.getId());
            } else {
                try { Thread.sleep(200); } catch (InterruptedException e) {}
            }
        }
    }
}

@Data
@AllArgsConstructor
class DelayTask implements Delayed {
    String id;
    long delayTime = System.currentTimeMillis();
    public DelayTask(long delayTime, String id) { this.delayTime = this.delayTime + delayTime; this.id = id; }
    @Override
    public long getDelay(TimeUnit unit) { return unit.convert(delayTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS); }
    @Override
    public int compareTo(Delayed o) { return Long.compare(this.getDelay(TimeUnit.MILLISECONDS), o.getDelay(TimeUnit.MILLISECONDS)); }
    @Override
    public String toString() { return DateFormat.getDateTimeInstance().format(new Date(delayTime)); }
}

2. ScheduledExecutorService – Provides schedule for single‑run delayed tasks. Example:

MyScheduledRunnable runnable = new MyScheduledRunnable();
// business runs 2 seconds
runnable.setBizCostTime(2000L);
ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();
service.schedule(runnable, 1, TimeUnit.SECONDS);

3. Database polling – Insert a timeout record into a MySQL table with fields id, head, type, status, gmt_time. A Quartz job periodically scans the table, executes tasks whose gmt_time is past, and updates the status to avoid duplicate processing.

4. Redis sorted set – Use a Zset where the score is the target execution timestamp. Pull the smallest N entries whose score ≤ now, process them, and remove them atomically to prevent concurrent consumption.

List<String> result = cacheService.scanData(keyPrefix, nowTime, 3);
for (String record : result) {
    long affectRow = cacheService.removeData(keyPrefix, record);
    if (affectRow > 0) {
        System.out.println("Processing timeout record: " + record);
    }
}
Thread.sleep(800);

5. Pulsar delayed messages – Publish a message with deliverAfter(3, TimeUnit.MINUTES). The broker stores it until the delay expires and then forwards it to subscribers.

producer.newMessage().deliverAfter(3L, TimeUnit.Minute).value("Hello Pulsar!").send();

6. ActiveMQ delayed messages – Set the ScheduledMessage.AMQ_SCHEDULED_DELAY property (and optionally period and repeat) on a TextMessage before sending.

MessageProducer producer = session.createProducer(destination);
TextMessage message = session.createTextMessage("test msg");
long time = 60 * 1000;
message.setLongProperty(ScheduledMessage.AMQ_SCHEDULED_DELAY, time);
producer.send(message);

7. Netty time‑wheel – A high‑performance wheel timer groups many timeout tasks onto a single scheduler. Example code adds three tasks with different delays:

HashedWheelTimer timer = new HashedWheelTimer(1, TimeUnit.SECONDS, 10);
TimerTask task1 = new TimerTask() { @Override public void run(Timeout timeout) throws Exception { System.out.println("task1 executed"); } };
TimerTask task2 = new TimerTask() { @Override public void run(Timeout timeout) throws Exception { System.out.println("task2 executed"); } };
TimerTask task3 = new TimerTask() { @Override public void run(Timeout timeout) throws Exception { System.out.println("task3 executed"); } };
 timer.newTimeout(task1, 0, TimeUnit.SECONDS);
 timer.newTimeout(task2, 3, TimeUnit.SECONDS);
 timer.newTimeout(task3, 15, TimeUnit.SECONDS);

Each approach has trade‑offs: in‑memory queues risk OOM and data loss on restart; database polling adds load and latency; Redis offers distributed safety but requires careful batch sizing; message brokers simplify reliability but depend on broker features; Netty’s wheel timer is efficient for massive connections but has limited precision and no crash recovery.

For production systems, choose the solution that matches the required latency, durability, scalability, and operational complexity.

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.

Schedulingdelayed tasks
Full-Stack Internet Architecture
Written by

Full-Stack Internet Architecture

Introducing full-stack Internet architecture technologies centered on Java

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.