Backend Development 8 min read

Implementing a Delayed Queue with Redis Zset in Java Spring Boot

This article explains the concept of delayed queues, outlines typical use cases, compares implementation options, and provides a complete Java Spring Boot example that uses Redis Zset to create, manage, and execute delayed tasks with accompanying code snippets and test results.

IT Architects Alliance
IT Architects Alliance
IT Architects Alliance
Implementing a Delayed Queue with Redis Zset in Java Spring Boot

First, a delayed queue is a data structure similar to a normal FIFO queue but each element carries a specified delay time, causing it to be processed only after that time has elapsed; it can be viewed as a time‑weighted ordered heap.

Application Scenarios

Automatically cancel an order if payment is not received within X minutes after successful placement.

Send a reminder to a delivery person shortly before an order’s timeout.

Notify participants a few minutes before a scheduled meeting starts.

Other time‑based trigger scenarios.

Possible Solutions

JDK DelayQueue API.

Quartz scheduler.

Redis Zset (the method demonstrated here).

Message queue systems.

Other custom implementations.

Implementation Using Redis Zset

The following example uses Redis Zset to implement a delayed queue.

zset is a special Redis data type that combines a set (ensuring unique values) with a score attribute; sorting by score provides an ordered collection, useful for time‑based ordering such as delayed tasks.

1. pom.xml

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

2. application.yml

spring:
  redis:
    host: localhost
    port: 6379
    password: xxx
    database: 1

3. Delayed task interface

public interface RedisDelayTask {
    String getId();
    String getValue();
    long getDelayTime();
    void execute();
}

4. Abstract task implementation

public abstract class AbstractRedisDelayTask implements RedisDelayTask {
    protected String id;
    protected String value;
    private long delayTime;
    public AbstractRedisDelayTask(String id, String value, long delayTime) {
        this.id = id;
        this.value = value;
        this.delayTime = delayTime;
    }
    @Override public String getId() { return id; }
    @Override public String getValue() { return value; }
    @Override public long getDelayTime() { return delayTime; }
    @Override public String toString() {
        return "RedisDelayTask{id='" + id + "', value='" + value + "', delayTime=" + delayTime + "}";
    }
}

5. Concrete notification task

public class NoticeTask extends AbstractRedisDelayTask {
    private static final Logger LOGGER = LoggerFactory.getLogger(NoticeTask.class);
    public NoticeTask(String id, String value, long delayTime) {
        super(id, value, delayTime);
    }
    @Override public void execute() {
        LOGGER.info("task execute, {}", this);
    }
}

6. Task manager

@Component
public class RedisDelayQueueManager implements InitializingBean {
    @Autowired
    private StringRedisTemplate redisTemplate;
    private Map
tasks = new ConcurrentHashMap<>();
    public void addTask(RedisDelayTask task) {
        long delayedTime = System.currentTimeMillis() + TimeUnit.MILLISECONDS.convert(task.getDelayTime(), TimeUnit.SECONDS);
        boolean r = redisTemplate.opsForZSet().add(task.getId(), task.getValue(), delayedTime);
        if (r) { tasks.put(task.getId(), task); }
    }
    private void checkAndExecuteTask() {
        while (true) {
            for (String taskId : tasks.keySet()) {
                Set
> tuples = redisTemplate.opsForZSet()
                    .rangeByScoreWithScores(taskId, 0, System.currentTimeMillis());
                if (!CollectionUtils.isEmpty(tuples)) {
                    for (ZSetOperations.TypedTuple
tuple : tuples) {
                        RedisDelayTask task = tasks.remove(taskId);
                        if (task != null) {
                            task.execute();
                            redisTemplate.opsForZSet().remove(taskId, tuple.getValue());
                        }
                    }
                }
            }
        }
    }
    @Override public void afterPropertiesSet() throws Exception {
        new Thread(this::checkAndExecuteTask, "redis-delay-task").start();
    }
}

7. Test case

@RunWith(SpringRunner.class)
@SpringBootTest(classes = RedisApplication.class)
public class RedisDelayTaskTest {
    @Autowired
    private RedisDelayQueueManager redisDelayQueueManager;
    @Test
    public void addTask() throws IOException {
        NoticeTask task = new NoticeTask("notice-task", "notice-task-value", 5);
        redisDelayQueueManager.addTask(task);
        NoticeTask task2 = new NoticeTask("notice-task2", "notice-task-value2", 10);
        redisDelayQueueManager.addTask(task2);
        System.in.read();
    }
}

Execution Result

2022-01-22 17:27:58.140 INFO [edis-delay-task] c.springboot.demo.redis.task.NoticeTask : task execute, RedisDelayTask{id='notice-task', value='notice-task-value', delayTime=5} 2022-01-22 17:28:03.925 INFO [edis-delay-task] c.springboot.demo.redis.task.NoticeTask : task execute, RedisDelayTask{id='notice-task2', value='notice-task-value2', delayTime=10}
BackendJavaRedisSpringBootZsetDelayQueue
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

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.