Backend Development 16 min read

Understanding Redisson DelayedQueue: Internal Mechanisms and Practical Usage

This article explains how Redisson's DelayedQueue works internally, covering its data structures, the basic workflow for sending and receiving delayed messages, the initialization process, and the scheduling logic that moves expired items from the delay queue to the target queue.

Architect
Architect
Architect
Understanding Redisson DelayedQueue: Internal Mechanisms and Practical Usage

Introduction

Because a distributed delayed‑queue was required at work, I investigated several options and settled on Redisson DelayedQueue. The purpose of this article is to record and explain its internal execution flow.

Basic Usage

Sending a delayed message (5 seconds) can be done with the following code:

public void produce() {
  String queuename = "delay-queue";
  RBlockingQueue
blockingQueue = redissonClient.getBlockingQueue(queuename);
  RDelayedQueue
delayedQueue = redissonClient.getDelayedQueue(blockingQueue);
  delayedQueue.offer("测试延迟消息", 5, TimeUnit.SECONDS);
}

Receiving messages only needs to poll the target queue; the delayed queue object is created but not used directly:

public void consume() throws InterruptedException {
  String queuename = "delay-queue";
  RBlockingQueue
blockingQueue = redissonClient.getBlockingQueue(queuename);
  RDelayedQueue
delayedQueue = redissonClient.getDelayedQueue(blockingQueue);
  String msg = blockingQueue.take(); // blocks until a message arrives
  // process the message ...
}

The producer and consumer can run in separate Java processes as long as they connect to the same Redis instance.

Internal Data Structures

Redisson creates three queues for a delayed queue:

Message Delay Queue – a sorted set (ZSET) ordered by expiration timestamp.

Message Order Queue – a list that preserves insertion order (not used in the core flow).

Message Target Queue – a regular list from which consumers retrieve ready messages.

When a message is offered, it is inserted into both the delay queue and the order queue. The delay queue stores the expiration time as the ZSET score, enabling fast lookup of the next message to expire.

Basic Process

1. Send delayed message : the message is stored in the delay queue and order queue; if the delay queue was previously empty, a Pub/Sub notification is published to alert listeners.

2. Get delayed message : consumers simply perform a blocking pop (BLPOP) on the target queue, which only contains expired messages.

3. Initialize delayed queue : a background task is started that subscribes to a dedicated channel and periodically moves expired items from the delay queue to the target queue.

Initialization Details

Calling redissonClient.getDelayedQueue(blockingQueue) creates a RedissonDelayedQueue instance and triggers its constructor, which starts a QueueTransferTask . This task registers two Pub/Sub listeners:

On subscription, pushTask() is invoked, which eventually calls pushTaskAsync() to move any already‑expired items.

On receiving a new message (the expiration timestamp), scheduleTask(startTime) schedules a timer to invoke pushTask() at the appropriate moment.

If the delay queue is empty, the task waits for a Pub/Sub notification before scheduling the next check.

Key Methods

offerAsync – sends a Lua script to Redis that inserts the message into the delay and order queues and publishes the earliest expiration time if the new message becomes the head of the delay queue.

pushTaskAsync – executes a Lua script that:

Retrieves up to 100 expired items from the delay queue, moves them to the target queue, and removes them from both delay and order queues.

Returns the next earliest expiration timestamp (or null if none).

scheduleTask – receives the next expiration timestamp and sets a Netty timeout to call pushTask() when the time arrives.

pushTask – calls pushTaskAsync() , handles errors, and either reschedules itself immediately or after a short delay.

Summary

During initialization, the delayed queue moves any already‑expired items to the target queue and determines the next expiration time. Subsequent messages trigger Pub/Sub notifications that cause the scheduler to move newly‑expired items at the correct moment. Consumers only need to block on the target queue, while producers simply use offer to schedule delayed delivery.

distributed systemsJavaRedisMessage QueueRedissonDelayedQueue
Architect
Written by

Architect

Professional architect sharing high‑quality architecture insights. Topics include high‑availability, high‑performance, high‑stability architectures, big data, machine learning, Java, system and distributed architecture, AI, and practical large‑scale architecture case studies. Open to ideas‑driven architects who enjoy sharing and learning.

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.