redis怎样实现延时队列

[复制链接]
发表于 2025-11-17 22:40:26 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×
在 Redis 中实现延时队列(Delayed Queue)通常利用 Redis 的有序聚集(Sorted Set)(ZSET)来完成。Redis 的有序聚集支持按分数(score)对元素举行排序,因此可以用它来实现基于时间的延时队列。
延时队列的实现原理

延时队列的焦点头脑是将使命按照它们应该被实验的时间戳(通常是当前时间戳加上延时)存入 Redis 的有序聚集中。队列中的每个使命包罗:

  • 使命数据:使命的实际内容。
  • 延时实验的时间:使命应该被实验的时间,可以通逾期间戳来表现(score)。
根本步调


  • 参加队列:将使命放入 Redis 的有序聚集中,score 值为当前时间加上延伸时间,value 为使命内容。
  • 定时查抄:定时查抄有序聚集中 score 小于当前时间的使命(这些使命可以实验),然后将它们从队列中移除,并实验使命。
  • 删除已实验的使命:使命实验完后,须要从队列中删除
在 Java 中利用 Redis 实现延时队列,通常可以通过 Redis 的有序聚集(ZSET) 来实现。延时队列的焦点头脑是将使命的实验时间戳作为 ZSET 中的 score,并根据时间戳定期查抄是否有使命已经到达实验时间。Redis 有序聚集的特性使得它非常得当用来实现基于时间戳的延时队列。
Sorted set +job

下面是一个详细的示例,展示如安在 Java 中利用 Redis 实现延时队列:
依赖

起首,我们须要在项目中添加 Redis 客户端库(Jedis)。以下是 Maven 依赖设置:
  1. <dependency>
  2.     <groupId>redis.clients</groupId>
  3.     <artifactId>jedis</artifactId>
  4.     <version>4.2.3</version>
  5. </dependency>
复制代码
延时队列实现思绪


  •         将使命参加延时队列:每个使命在参加队列时会设定一个延伸时间,延伸时间到达后实验该使命。使命被存储在 Redis 的 ZSET 中,此中 score 是使命的实验时间(时间戳)。
  •         定期查抄队列:体系定期从队列中获取已经到期的使命,并实验它们。使命实验后从队列中移除。
步调分析


  • 利用 Jedis 毗连 Redis。
  • 将使命参加延时队列,并设置使命的实验时间(通逾期间戳)。
  • 定期查抄使命,获取超时使命并实验,然后删除它们。
代码实现
  1. import redis.clients.jedis.Jedis;
  2. import redis.clients.jedis.Tuple;
  3. import java.util.Set;
  4. import java.util.Iterator;
  5. import java.util.concurrent.TimeUnit;
  6. public class RedisDelayedQueue {
  7.     private static Jedis jedis = new Jedis("localhost", 6379); // Redis连接
  8.     // 将任务加入延时队列
  9.     public static void addTaskToQueue(String taskId, long delaySeconds) {
  10.         long timestamp = System.currentTimeMillis() / 1000 + delaySeconds; // 计算任务执行的时间戳
  11.         jedis.zadd("delay_queue", timestamp, taskId);  // 将任务添加到有序集合,score 为任务的执行时间戳
  12.         System.out.println("Task " + taskId + " added to queue with " + delaySeconds + " seconds delay.");
  13.     }
  14.     // 获取并处理超时的任务
  15.     public static void processTasks() {
  16.         long currentTimestamp = System.currentTimeMillis() / 1000;  // 当前时间戳(秒)
  17.         
  18.         // 获取所有超时的任务 (score <= 当前时间戳)
  19.         Set<Tuple> tasksToProcess = jedis.zrangeByScoreWithScores("delay_queue", "-inf", String.valueOf(currentTimestamp));
  20.         
  21.         Iterator<Tuple> iterator = tasksToProcess.iterator();
  22.         while (iterator.hasNext()) {
  23.             Tuple task = iterator.next();
  24.             String taskId = task.getElement();
  25.             System.out.println("Processing task " + taskId);
  26.             
  27.             // 执行任务逻辑 (你可以在这里调用实际的业务逻辑)
  28.             
  29.             // 从队列中删除已执行的任务
  30.             jedis.zrem("delay_queue", taskId);
  31.             System.out.println("Task " + taskId + " removed from queue.");
  32.         }
  33.     }
  34.     // 定期检查并处理任务
  35.     public static void startTaskProcessor() {
  36.         while (true) {
  37.             try {
  38.                 // 每隔1秒检查一次队列
  39.                 TimeUnit.SECONDS.sleep(1);
  40.                 processTasks();  // 处理超时的任务
  41.             } catch (InterruptedException e) {
  42.                 e.printStackTrace();
  43.             }
  44.         }
  45.     }
  46.     public static void main(String[] args) {
  47.         // 添加任务到延时队列
  48.         addTaskToQueue("task1", 5);  // 任务 1 延迟 5 秒执行
  49.         addTaskToQueue("task2", 10); // 任务 2 延迟 10 秒执行
  50.         // 启动任务处理器,定期检查队列
  51.         startTaskProcessor();
  52.     }
  53. }
复制代码
代码分析

1. 添加使命到延时队列

addTaskToQueue 方法罗致使命 ID 和延伸秒数,盘算使命的实验时间(时间戳),然后将使命 ID 和实验时间戳作为 score 存入 Redis 的 ZSET(有序聚集)。有序聚集的 score 是一个时间戳,使命会按时间戳排序。
  1. long timestamp = System.currentTimeMillis() / 1000 + delaySeconds; // 当前时间戳加上延迟秒数
  2. jedis.zadd("delay_queue", timestamp, taskId);  // 将任务添加到有序集合,score 为延迟的时间戳
复制代码
long timestamp = System.currentTimeMillis() / 1000 + delaySeconds; // 当前时间戳加上延伸秒数 jedis.zadd("delay_queue", timestamp, taskId); // 将使命添加到有序聚集,score 为延伸的时间戳
2. 获取并处理处罚超时使命

processTasks 方法会定期查抄延时队列,获取全部 score 小于便是当前时间戳的使命(即已经到期的使命)。可以通过 ZRANGEBYSCORE 或 ZRANGE 下令获取全部超时使命,并举行处理处罚(实验使命)。
  1. Set<Tuple> tasksToProcess = jedis.zrangeByScoreWithScores("delay_queue", "-inf", String.valueOf(currentTimestamp));
复制代码
 

  • zrangeByScoreWithScores("delay_queue", "-inf", currentTimestamp) 会返回全部 score 小于便是当前时间戳的使命。
  • 每个使命的 score 是使命的实验时间戳,使命 ID 是 element。
在获取到使命后,可以实验相应的使命逻辑,并从 Redis 队列中删除已实验的使命。
  1. jedis.zrem("delay_queue", taskId); // 从队列中删除已处理的任务
复制代码
3. 定期查抄并处理处罚使命

为了连续查抄是否有使命到期,可以创建一个定时使命,每隔肯定时间(比方每秒钟)查抄一次队列,并处理处罚全部超时的使命。
  1. while (true) {
  2.     try {
  3.         TimeUnit.SECONDS.sleep(1);  // 每隔1秒钟检查一次队列
  4.         processTasks();  // 处理超时的任务
  5.     } catch (InterruptedException e) {
  6.         e.printStackTrace();
  7.     }
  8. }
复制代码
总结

通过 Redis 的有序聚集(ZSET)实现延时队列是一个非常高效的办理方案。使命按照实验时间存储在 Redis 中,定期查抄并实验已经到期的使命。这种实现方式简单且高效,得当用在大多数须要延时实验使命的场景中。
优化发起:


  • 性能:如果使命量非常大,频仍查抄队列大概会影响性能。你可以利用 Redis Lua 脚本 来将查抄和处理处罚使命的逻辑原子化,镌汰网络来回。
  • 非常处理处罚:在实际生产情况中,代码应当有更美满的非常处理处罚和错误规复机制。
  • 使命实验:你可以将使命逻辑封装成一个接口或类,以便更加机动地处理处罚差别范例的使命。
盼望这个 Java 实现可以或许资助你明白怎样利用 Redis 实现延时队列。
发布订阅+Sorted set + job

在 Redis 中实现延时队列(Delayed Queue)可以通过发布/订阅(Pub/Sub)模式共同一些延机遇制来实现。由于 Redis 的 Pub/Sub 是及时的,没有内建的延时队列功能,我们须要利用一些技能本领来模拟延时结果,比方将消息推送到 Redis 中,设置一个延时的定时使命来发布消息。
延时队列的筹划思绪:


  • 发布者(Producer):发布一个消息到 Redis 延时队列。
  • 订阅者(Consumer):监听这个延时队列,等候消息到期后再处理处罚。
实现步调:


  • 将消息按延伸时间发布到 Redis:可以将消息放入一个有序聚集(Sorted Set),此中成员是消息内容,分数(score)是延伸的时间戳(当前时间 + 延伸时间)。然后通过定时使命来查抄并发布已到期的消息。
  • 订阅者监听频道:订阅者通过 Redis Pub/Sub 模式监听消息频道,当延时队列中的消息到期后,发布到订阅者监听的频道,消耗者举行处理处罚。
利用 Jedis 库实现延时队列的代码示例

我们将实现以下几个部分:

  • 延时队列生产者:将消息放入 Redis 有序聚集(Sorted Set)。
  • 延时队列消耗者:定时从有序聚集中读取消息,并通过发布/订阅模式发布到一个频道。
1. 添加 Jedis 依赖

如果你利用的是 Maven 构建工具,确保你的 pom.xml 文件中包罗 Jedis 依赖:
  1. [/code]xml
  2. <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>4.3.1</version> </dependency>
  3. [size=3]2. [b]延时队列生产者(Producer)[/b][/size]
  4. 生产者将消息推入 Redis 的有序聚集 delayed_queue 中,消息的分数(score)是当前时间加上延伸的时间。
  5. [code]
  6. import redis.clients.jedis.Jedis;
  7. public class DelayedQueueProducer {
  8.     public static void main(String[] args) {
  9.         Jedis jedis = new Jedis("localhost", 6379);
  10.         // 模拟延时队列:将消息放入 Redis 有序集合中
  11.         // 设置消息延迟时间为 5 秒
  12.         long delayTime = 5000; // 延迟 5 秒
  13.         long delayUntil = System.currentTimeMillis() + delayTime;
  14.         String message = "Delayed message at " + System.currentTimeMillis();
  15.         // 将消息和延迟时间戳放入有序集合中
  16.         jedis.zadd("delayed_queue", delayUntil, message);
  17.         System.out.println("Message added to queue with delay: " + message);
  18.         jedis.close();
  19.     }
  20. }
复制代码
表明:

  • zadd("delayed_queue", delayUntil, message):将消息与延伸时间戳一同放入有序聚集中,delayUntil 是消息的逾期时间戳(即消息应该在此时间后被处理处罚)。
  • delayed_queue 是存储延时消息的 Redis 有序聚集(Sorted Set)。
3. 延时队列消耗者(Consumer)

消耗者不绝查抄 delayed_queue 有序聚集中的消息,如果当前时间已经到达消息的延伸时间,则将消息发布到 Redis 的 Pub/Sub 频道中。
  1. import redis.clients.jedis.Jedis;
  2. import redis.clients.jedis.JedisPubSub;
  3. public class DelayedQueueConsumer {
  4.     public static void main(String[] args) {
  5.         Jedis jedis = new Jedis("localhost", 6379);
  6.         // 定时任务:每隔 1 秒检查一次延时队列
  7.         while (true) {
  8.             // 获取当前时间戳
  9.             long currentTime = System.currentTimeMillis();
  10.             // 从有序集合中取出时间戳小于当前时间的消息(已经到期的消息)
  11.             var messages = jedis.zrangeByScore("delayed_queue", 0, currentTime);
  12.             // 如果有消息到期,将它们发布到 Pub/Sub 频道
  13.             for (String message : messages) {
  14.                 System.out.println("Publishing message: " + message);
  15.                 jedis.publish("delayed_channel", message);
  16.                 // 从有序集合中删除已发布的消息
  17.                 jedis.zrem("delayed_queue", message);
  18.             }
  19.             try {
  20.                 // 每隔 1 秒检查一次
  21.                 Thread.sleep(1000);
  22.             } catch (InterruptedException e) {
  23.                 e.printStackTrace();
  24.             }
  25.         }
  26.     }
  27. }
复制代码
表明:

  • zrangeByScore("delayed_queue", 0, currentTime):获取有序聚集中分数(即时间戳)小于当前时间的消息。
  • jedis.publish("delayed_channel", message):将已到期的消息发布到 Redis 的 Pub/Sub 频道 "delayed_channel"。
  • jedis.zrem("delayed_queue", message):删除已发布的消息,以克制重复发布。
4. 订阅者(Subscriber)

订阅者监听 Redis 频道 delayed_channel,罗致已到期的消息举行处理处罚。
  1. import redis.clients.jedis.Jedis;
  2. import redis.clients.jedis.JedisPubSub;
  3. public class DelayedQueueSubscriber {
  4.     public static void main(String[] args) {
  5.         Jedis jedis = new Jedis("localhost", 6379);
  6.         // 创建 JedisPubSub 订阅者
  7.         jedis.subscribe(new JedisPubSub() {
  8.             @Override
  9.             public void onMessage(String channel, String message) {
  10.                 // 接收到消息时的处理
  11.                 System.out.println("Received message: " + message);
  12.             }
  13.         }, "delayed_channel");  // 订阅 delayed_channel 频道
  14.     }
  15. }
复制代码
表明:

  • jedis.subscribe() 用于订阅 delayed_channel 频道,当生产者发布消息时,消耗者会罗致到这些消息。
5. 实验流程


  •         启动订阅者:起首启动 DelayedQueueSubscriber 类,订阅 Redis 频道 delayed_channel。
  •         启动消耗者:启动 DelayedQueueConsumer 类,它会每隔 1 秒查抄一次延时队列 delayed_queue 中的消息,并将已到期的消息发布到 delayed_channel 频道。
  •         启动生产者:启动 DelayedQueueProducer 类,生产者将消息和延伸时间一起发布到 Redis 的 delayed_queue 有序聚集。
  •         消息处理处罚:当消息到期,消耗者将其发布到 delayed_channel 频道,订阅者罗致到并处理处罚这些消息。
6. 总结


  • 通过利用 Redis 的 有序聚集(Sorted Set)和 发布/订阅(Pub/Sub)模式,我们实现了一个简单的延时队列。
  • 生产者将消息推入有序聚集,并通逾期间戳设置消息的延伸时间。
  • 消耗者定时查抄延时队列中的消息,并将已到期的消息发布到频道。
  • 订阅者罗致到发布的消息并举行处理处罚。
这种方法的优点是实现简单且与 Redis 高效共同,实用于不须要高精度定时的延时队列场景。
Redis Streams + job

入参:map 
出参:list<map>
利用 Redis Streams 实现延时队列 java实现例子
利用 Redis Streams 来实现延时队列,是一种非常高效且得当高并发的方式。与传统的 List 或 Sorted Set 数据布局相比,Redis Streams 提供了更加机动的消耗模子,尤其实用于须要队列功能、消息确认、长期化等的场景。
筹划思绪

在 Redis Streams 中实现延时队列的焦点思绪是:

  • 生产者将消息推送到 Redis Streams,并设置一个延伸时间(即消息应该在多久之后处理处罚)。
  • 消耗者定期查抄当前时间与消息的延伸时间举行对比,只有当消息的延伸时间已经到达时,才开始处理处罚该消息。
重要步调


  • 生产者:将消息发送到 Redis Stream,同时在消息中存储一个延伸时间(延伸时间戳)。
  • 消耗者:利用一个定时使命或循环,不绝查抄流中的消息,当消息的延伸时间已颠末去时,将其处理处罚并从流中删除。
Redis Streams 延时队列 Java 示例

以下是怎样利用 Java 和 Redis Streams 来实现一个简单的延时队列:
1. 添加 Jedis 依赖

起首,你须要确保你的 pom.xml 文件中包罗 Jedis 依赖:
2. 延时队列生产者(Producer)

生产者将消息推送到 Redis Stream 中,并设置一个延伸时间。
  1. import redis.clients.jedis.Jedis;
  2. import redis.clients.jedis.StreamEntryID;
  3. import java.util.HashMap;
  4. import java.util.Map;
  5. public class DelayedQueueProducer {
  6.     public static void main(String[] args) {
  7.         // 创建 Jedis 客户端
  8.         Jedis jedis = new Jedis("localhost", 6379);
  9.         // 设置消息的延迟时间(例如 5 秒后)
  10.         long delayTime = 5000; // 延迟 5 秒
  11.         long delayUntil = System.currentTimeMillis() + delayTime;
  12.         // 构造消息内容
  13.         String message = "This is a delayed message at " + System.currentTimeMillis();
  14.         // 构造消息数据
  15.         Map<String, String> messageData = new HashMap<>();
  16.         messageData.put("message", message);
  17.         messageData.put("delayUntil", String.valueOf(delayUntil));
  18.         // 向流中添加消息,流的名称是 "delayed_stream"
  19.         StreamEntryID entryID = jedis.xadd("delayed_stream", messageData);
  20.         System.out.println("Message added to stream with delay: " + message + ", Message ID: " + entryID);
  21.         // 关闭连接
  22.         jedis.close();
  23.     }
  24. }
复制代码
表明:

  • jedis.xadd("delayed_stream", messageData) 将消息添加到 Redis Stream 中。
  • messageData 包罗消息内容和延伸时间戳 (delayUntil),用于记载消息的延伸时间。
3. 延时队列消耗者(Consumer)

消耗者利用定时查抄的方式来处理处罚 Redis Stream 中的消息。每隔一段时间,它会查抄消息的延伸时间,只有当消息的延伸时间到达时才开始处理处罚。
  1. import redis.clients.jedis.Jedis;
  2. import redis.clients.jedis.StreamEntryID;
  3. import redis.clients.jedis.api.stream.StreamEntry;
  4. import java.util.List;
  5. import java.util.Map;
  6. public class DelayedQueueConsumer {
  7.     public static void main(String[] args) {
  8.         Jedis jedis = new Jedis("localhost", 6379);
  9.         // 消费者 ID,表示消费者实例
  10.         String consumerName = "consumer1";
  11.         String streamName = "delayed_stream";
  12.         while (true) {
  13.             // 获取当前时间
  14.             long currentTime = System.currentTimeMillis();
  15.             // 使用 XREAD 来读取流数据并且过滤掉延迟还没有到的消息
  16.             List<Map.Entry<String, List<StreamEntry>>> messages = jedis.xread(
  17.                     Map.of(streamName, "0"),  // 从流的开头读取数据
  18.                     1,                        // 阻塞 1 秒
  19.                     0,                        // 获取最多 1 条消息
  20.                     1,                        // 设置阻塞时间为 1 秒
  21.                     0
  22.             );
  23.             if (messages != null && !messages.isEmpty()) {
  24.                 for (Map.Entry<String, List<StreamEntry>> entry : messages) {
  25.                     for (StreamEntry streamEntry : entry.getValue()) {
  26.                         // 获取消息内容和延迟时间戳
  27.                         Map<String, String> fields = streamEntry.getFields();
  28.                         String message = fields.get("message");
  29.                         long delayUntil = Long.parseLong(fields.get("delayUntil"));
  30.                         // 检查当前时间是否已到达延迟时间
  31.                         if (currentTime >= delayUntil) {
  32.                             System.out.println("Processing message: " + message);
  33.                             // 完成消息后,删除它
  34.                             jedis.xdel(streamName, streamEntry.getID());
  35.                         } else {
  36.                             // 延迟时间还没有到,跳过该消息
  37.                             System.out.println("Skipping message due to delay: " + message);
  38.                         }
  39.                     }
  40.                 }
  41.             }
  42.             // 可以调整消费的时间间隔,例如每秒检查一次
  43.             try {
  44.                 Thread.sleep(1000);
  45.             } catch (InterruptedException e) {
  46.                 e.printStackTrace();
  47.             }
  48.         }
  49.     }
  50. }
复制代码
表明:

  • jedis.xread() 方法用于从 Redis Stream 中读取消息。我们利用 0 作为流的起始位置,表现从流的开始读取数据。
  • currentTime 与 delayUntil 举行比力,如果当前时间大于或便是 delayUntil,分析该消息已到期,消耗者会开始处理处罚该消息并从流中删除它。
  • 如果消息的延伸时间还没有到达,消耗者会跳过该消息并等候下一轮查抄。
4. 处理处罚流程总结


  • 生产者:

    • 将消息发送到 Redis Stream,并附加一个 delayUntil 字段来表现消息的延伸时间。

  • 消耗者:

    • 定时查抄 Redis Stream 中的消息,当消息的 delayUntil 时间已到时,消耗者开始处理处罚该消息,并删除该消息。

5. 优化和扩展


  • 消耗确认:在实际应用中,消耗者大概须要对已处理处罚的消息举行确认。可以利用 Redis Streams 的 XACK 功能来实现消息确认。
  • 并发消耗:你可以为同一个 Stream 设置多个消耗者,通过利用消耗者组(Consumer Groups)来提升处理处罚本领。
  • 壅闭读取:通过 XREAD 可以实现壅闭读取,当有新消息到来时,消耗者会自动收到关照。
6. 性能思量


  • Redis Streams 可以高效地处理处罚大量消息,且提供了长期化和自动重试等功能。如果消息非常频仍或数量巨大,可以思量通过消耗者组(Consumer Groups)来实现多个消耗者的并发处理处罚。
  • 定时查抄流中的消息固然简单,但对于非常高精度的延时处理处罚大概不如一些专门的延时队列体系(如 Kafka 等)。但对于大部分业务场景,Redis Streams 是一个高效且易于实现的选择。
7. 总结

利用 Redis Streams 实现延时队列是一种非常高效且机动的方法,实用于须要高并发、消息长期化和可靠性的场景。通过简单的时间戳比力和定时查抄,消耗者可以高效地处理处罚延时消息。同时,Redis Streams 提供了许多其他高级功能(如消耗者组、壅闭读取等),使其成为实现消息队列的一个非常精良的选择。
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表