Backend Development 17 min read

Design and Implementation of Delayed Task Solutions for Payment Systems

This article compares delayed and scheduled tasks, analyzes several implementation approaches—including database polling with Quartz, JDK DelayQueue, Netty's HashedWheelTimer, Redis ZSET and key‑space notifications, and RabbitMQ delayed queues—provides code examples, and discusses the advantages and drawbacks of each method for handling order timeout scenarios in payment systems.

Java Architect Essentials
Java Architect Essentials
Java Architect Essentials
Design and Implementation of Delayed Task Solutions for Payment Systems

In payment systems, delayed tasks such as automatically canceling unpaid orders after a certain period or sending SMS after order creation are common requirements. The article first distinguishes delayed tasks from scheduled tasks: delayed tasks lack a fixed trigger time and execution cycle, usually handling a single job after an event, whereas scheduled tasks have explicit trigger times, cycles, and often batch multiple jobs.

Solution Analysis

(1) Database Polling with Quartz

For small projects, a thread can periodically scan the database for overdue orders and update or delete them. The Maven dependency for Quartz is:

<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.2.2</version>
</dependency>

The demo job implementation:

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();
    }
}

Running this code prints the message every 3 seconds. Pros: simple, easy to implement, supports clustering. Cons: high memory consumption, latency up to the scan interval, heavy database load for large order volumes.

(2) JDK DelayQueue

DelayQueue is an unbounded blocking queue where elements become available only after their delay expires. The task object must implement Delayed . Example implementation:

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) {
        if (other == this) return 0;
        OrderDelay t = (OrderDelay) other;
        long d = getDelay(TimeUnit.NANOSECONDS) - t.getDelay(TimeUnit.NANOSECONDS);
        return d == 0 ? 0 : (d < 0 ? -1 : 1);
    }
    public long getDelay(TimeUnit unit) {
        return unit.convert(timeout - System.nanoTime(), TimeUnit.NANOSECONDS);
    }
    void print() {
        System.out.println(orderId + "编号的订单要删除啦。。。。");
    }
}

Demo execution with a 3‑second delay:

public class DelayQueueDemo {
    public static void main(String[] args) {
        List
list = new ArrayList<>();
        list.add("00000001");
        list.add("00000002");
        list.add("00000003");
        list.add("00000004");
        list.add("00000005");
        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) {}
        }
    }
}

Output shows each order being processed after roughly 3 seconds. Pros: high efficiency, low trigger latency. Cons: data loss on JVM restart, difficult cluster expansion, possible OOM for massive order counts, higher code complexity.

(3) Time Wheel Algorithm (Netty HashedWheelTimer)

Using Netty's HashedWheelTimer provides a circular timing wheel where each tick represents a fixed duration. Maven dependency:

<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-all</artifactId>
    <version>4.1.24.Final</version>
</dependency>

Demo:

public class HashedWheelTimerTest {
    static class MyTimerTask implements TimerTask {
        boolean flag = true;
        public MyTimerTask(boolean flag) { this.flag = flag; }
        public void run(Timeout timeout) throws Exception {
            System.out.println("要去数据库删除订单了。。。。");
            flag = false;
        }
    }
    public static void main(String[] argv) {
        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++;
        }
    }
}

Pros: high efficiency, lower latency than DelayQueue, simpler code. Cons: same persistence issues as DelayQueue and similar cluster challenges.

(4) Redis ZSET

Redis sorted sets store order IDs as members with the expiration timestamp as the score. Basic commands:

ZADD key score member – add element

ZRANGE key start stop WITHSCORES – query ordered elements

ZSCORE key member – get score

ZREM key member – remove element

Producer adds orders with a 3‑second future timestamp; consumer repeatedly checks the smallest score and removes the order when the current time exceeds the score. Sample code (producer/consumer) is provided in the article.

To avoid duplicate consumption in a multi‑consumer scenario, the consumer checks the return value of ZREM and only processes the order when the removal count is greater than zero.

(5) Redis Key‑space Notifications

By enabling notify-keyspace-events Ex in redis.conf , Redis can publish an event when a key expires. A subscriber listens on the __keyevent@0__:expired channel and processes the cancelled order. Example subscriber implementation is shown.

Pros: persistence of messages in Redis, easy cluster scaling, high time accuracy. Cons: requires Redis maintenance.

(6) Message Queue – RabbitMQ Delayed Queue

RabbitMQ supports delayed messages via the x-message-ttl property and dead‑letter exchanges. By configuring a queue with x-dead-letter-exchange and x-dead-letter-routing-key , messages can be routed after the TTL expires, effectively implementing a delayed queue. The article mentions this approach but defers detailed implementation to a future post.

Pros: high efficiency, built‑in clustering, message persistence for reliability. Cons: added operational complexity and cost due to RabbitMQ management.

Overall, the article provides a comprehensive comparison of various delayed‑task mechanisms, guiding architects to choose the most suitable solution based on performance, reliability, and operational considerations.

backendJavaRedisSchedulingdelayed tasksQuartz
Java Architect Essentials
Written by

Java Architect Essentials

Committed to sharing quality articles and tutorials to help Java programmers progress from junior to mid-level to senior architect. We curate high-quality learning resources, interview questions, videos, and projects from across the internet to help you systematically improve your Java architecture skills. Follow and reply '1024' to get Java programming resources. Learn together, grow together.

0 followers
Reader feedback

How this landed with the community

login 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.