Comparison of Delayed Task Solutions in Java: Quartz, DelayQueue, Time Wheel, Redis and RabbitMQ
The article explains the concept of delayed tasks versus scheduled tasks in payment systems, outlines their key differences, and evaluates five practical implementation approaches—Quartz database polling, JDK DelayQueue, Netty's HashedWheelTimer, Redis ZSET/Keyspace notifications, and RabbitMQ delayed queues—detailing their code examples, advantages and drawbacks.
In this article we discuss the concept of delayed tasks in payment systems, contrast them with scheduled tasks, and list three main differences: scheduled tasks have a fixed trigger time and execution cycle, while delayed tasks are triggered after an event and have no periodic cycle.
We then analyze several implementation schemes.
1. Database polling (Quartz)
This approach is suitable for small projects and uses a thread to periodically scan the database for overdue orders.
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.2.2</version>
</dependency> 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 the demo prints "要去数据库扫描啦。。。" every three seconds.
2. JDK DelayQueue
This solution uses the JDK's DelayQueue , an unbounded blocking queue that only releases elements after their delay expires.
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 + "编号的订单要删除啦。。。。");
}
} public class DelayQueueDemo {
public static void main(String[] args) throws Exception {
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) {}
}
}
}The output shows each order being deleted after a three‑second delay.
3. Time Wheel (Netty HashedWheelTimer)
The time‑wheel algorithm works like a clock hand moving at fixed ticks; Netty provides HashedWheelTimer to schedule tasks with low latency.
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.24.Final</version>
</dependency> 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[] args) {
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++;
}
}
}The demo prints a second counter and deletes the order after five seconds.
4. Redis ZSET (sorted set) + polling
Orders are stored in a Redis sorted set where the score is the expiration timestamp; a consumer repeatedly checks the first element and removes it when the current time exceeds the score.
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, "OID0000001" + i);
System.out.println(System.currentTimeMillis() + "ms:redis生成了一个订单任务:订单ID为" + "OID0000001" + i);
}
}
public void consumerDelayMessage() {
Jedis jedis = getJedis();
while (true) {
Set
items = jedis.zrangeWithScores("OrderId", 0, 1);
if (items == null || items.isEmpty()) {
System.out.println("当前没有等待的任务");
Thread.sleep(500);
continue;
}
int score = (int) ((Tuple) items.toArray()[0]).getScore();
int now = (int) (Calendar.getInstance().getTimeInMillis() / 1000);
if (now >= score) {
String orderId = ((Tuple) items.toArray()[0]).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();
}
}After fixing the removal check, concurrent consumers no longer process the same order.
5. Redis Keyspace Notifications
By enabling notify-keyspace-events Ex in redis.conf , Redis can publish an event when a key expires, allowing a subscriber to react instantly.
public class RedisTest {
private static final String ADDR = "127.0.0.1";
private static final int PORT = 6379;
private static JedisPool jedis = new JedisPool(ADDR, PORT);
private static RedisSub sub = new RedisSub();
public static void init() {
new Thread(() -> jedis.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;
jedis.getResource().setex(orderId, 3, orderId);
System.out.println(System.currentTimeMillis() + "ms:" + orderId + "订单生成");
}
}
static class RedisSub extends JedisPubSub {
public void onMessage(String channel, String message) {
System.out.println(System.currentTimeMillis() + "ms:" + message + "订单取消");
}
}
}The subscriber prints a cancellation message three seconds after each order is set to expire.
6. RabbitMQ delayed queue
RabbitMQ can implement delayed messages by setting x-message-ttl on queues or messages and using dead‑letter exchanges to re‑route expired messages.
Advantages: high efficiency, easy horizontal scaling, and message persistence for reliability. Drawbacks: additional operational complexity and cost due to RabbitMQ dependency.
Overall, each solution has trade‑offs regarding memory consumption, latency, reliability, and scalability, and the choice depends on the specific requirements of the payment system.
Architecture Digest
Focusing on Java backend development, covering application architecture from top-tier internet companies (high availability, high performance, high stability), big data, machine learning, Java architecture, and other popular fields.
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.