Redis 如何实现延时队列?

前言

我们在前端开发过程中,经常需要用到延时处理的场景,比如在某个时间点批量发送定时任务、定时清除缓存等。针对这种场景,我们通常会选择使用延时队列。

Redis 是一个高性能的键值数据库,支持丰富的数据结构和丰富的指令,其中 zset(有序集合)就是实现延时队列的常见数据结构之一。本文将详细介绍基于 Redis 如何快速实现延时队列的具体实现方法,并提供示例代码及使用注意点进行指导。

zset 数据结构介绍

首先简要介绍下 Redis 中的有序集合 zset,在 Redis 中可直接使用 zset 来实现延时队列的存储和操作。

zset 实际上就是一个集合,其中每一个元素都被赋予一个浮点型的分数 score,根据 score 对集合中的元素进行升序或降序排列,同时每个元素都另外关联了一个字符串类型的 value。可以通过指令向 zset 添加、删除元素,或者修改元素的 score 值。zset 中的元素均为唯一的,元素的 score 值可以重复。

下面是一些常见的 Redis zset 操作指令:

  • zadd key score member [score member ...]:向 zset 添加一个或多个元素;
  • zrem key member [member ...]:从 zset 删除一个或多个元素;
  • zrange key start stop [WITHSCORES]:获取有序集合 key 中所有元素,或者指定区间的元素;
  • zrank key member:获取有序集合 key 中指定元素的排名;
  • zscore key member:获取有序集合 key 中指定元素的分数值;

延时队列实现方法

下面介绍如何在 Redis 中使用 zset 实现一个可靠的延时队列。

添加元素

可以使用 zadd 命令将任务作为 zset 中的元素加入队列,这个任务会被当做任务的“score”来插入有序集合,成为延时队列中的一个等待执行的任务。

由于浮点数排序是有精度的,我们需要确保每个任务的 score 值能够唯一确定任务,这里推荐使用时间戳作为 score 的值,如下所示:

---- ---------- --------- -------

其中,timestamp 表示任务应该被执行的时间,message 表示任务的内容。这样做的好处是,在任务处于“等待”状态时,将时间戳作为 score,保证了队列中任务的排序性,而任务出列时就可以根据时间戳获取当前需要执行的任务,比提交到后端处理任务列表中或在数据库中建立索引等其他实现方式更为高效。

注意点

当多个任务具有相同的时间戳 value 时,因为在有序集合中为任务分配的 score 值需要保证唯一性,所以我们可以使用一个唯一的 UUID 作为 value,来确保每个任务的元素在 zset 中唯一。

任务消费

当一个任务的“score”小于等于当前时间戳时,代表着该任务已经能够被消费了,我们可以使用 zrangebyscore 指令来获取当前可执行的任务,同时使用 zrem 指令将其从队列中移除。

当然,实际上这种取出来删除的方式并不是多么安全,存在很多竞态条件,比如在一个任务被执行的过程中,任务相关数据被意外删除等等。为了解决这些问题,一般需要参考 Redis 官方建议使用 RPOPLPUSH。

其具体操作流程如下:

  1. 首先备份到一个临时队列中: RPOPLPUSH delayQueue tmpQueue,保证这个任务不会被其他消费者拿走。
  2. 再使用 LREM 命令在 delayQueue队列中删除当前这个元素。LREM delayQueue 0 message。
  3. 执行任务。
  4. 如果任务处理失败(如过程异常崩溃等),将 tmpQueue 队列中的元素还原到 delayQueue 队列中:LREM tmpQueue 0 message;ZADD delayQueue timestamp message,后者将任务重新放回延时队列,等待下次重试。
  5. 如果任务处理成功,将 tmpQueue 队列中的元素删除即可:LREM tmpQueue 0 message。

示例代码

下面给出一个基于 Redis 的任务调度器示例代码。

准备工作:

  1. 搭建 Redis 环境
  2. 安装 Redis 模块: npm install redis
-- -- ----- --
----- ----- - -----------------

-- -- -----
----- ----------- - --------------------
  ----- ------------
  ----- -----
  --- -
---

-- -- ----- ----
----------------------- --- -- -
  --------------- ----- --- ---------
---

-- ------
-------- ------------------ -------- -
  -- ----- --- ------------- ----- -
  ------------------------------ ---------- -------- ----- ---- -- -
    -- ----- -------------------- ---------
    ---- ------------------- ----------
  ---
-

-- ------- ----------------------
-------- ------------- -
  -- -------- --------------------- - -----
  -- ---------- ------------- ---------- - -------
  -- ----------- ------------- ---------- - ----- ----- - -
  --------------------------------------- -- --------------------- - ------ ------------- ----- ---- -- -
    -- ----- -------------------- ---------
    ---- -
      -- ----------- - -- -
        ----- ------- - -------
        ----- --------- - -------
        -- -- --------- ----
        ----------------------------------- ----------- ----- ---- -- -
          -- ----- -------------------- ---------
          ---- -
            -- -- ---------- --------
            ------------------------------ -- ------------- ----- ---- -- -
              -- ----- -------------------- ---------
              ---- -
                -- ----- -------------
                -------------------- -------------
              -
            ---
          -
        ---
      - ---- -
        ------------------------
      -
    -
  ---
-

-- ----------- - -- -------
----------------------------- - ----- - --- ------ --------
-- -- - ------------
------------------------ ------

总结

使用 Redis 实现延时队列具有很多的优势:

  • Redis 提供了轻量级、高性能的 zset 有序集合,开箱即用;
  • 通过延时队列实现任务调度的方式可以很好地缓解请求延时和请求压力问题;
  • 通过 Redis 的事务机制和相关指令可以实现类似锁、de-duplication 和削峰填谷这样的扩展操作;
  • 如果某个任务出现异常,我们可以通过备份队列+删除主队列的方式实现“不丢任务”的需求,可靠性极高。

不过,在使用 Redis 构建延时队列的过程中,需要注意一些问题,比如多个任务有相同的时间戳,需要使用唯一uuid作为标识;任务在消费过程中存在安全性问题,需要使用 RPOPLPUSH 等原子操作来处理。希望本文能够帮助大家对 Redis 延时队列有更好的理解和应用。

来源:JavaScript中文网 ,转载请联系管理员! 本文地址:https://www.javascriptcn.com/post/648b027548841e98949648ee


猜你喜欢

  • Cypress 如何处理页面上多个相同的元素

    在前端测试中,我们经常会遇到需求需要对页面上的多个相同元素进行操作。例如,一个页面上有多个相同的按钮或输入框,我们需要对每一个元素进行单独的测试操作。Cypress 是一款优秀的前端测试框架,它提供了...

    1 年前
  • React Native 之 iOS 原生模块开发

    近年来,React Native 日益成为前端开发领域的热门技术,它可以帮助开发者快速构建跨平台应用。但是,有时候我们需要一些原生的功能,这时候就需要使用 React Native 的原生模块开发。

    1 年前
  • 如何在 ES11 中使用 String.prototype.matchAll 方法

    在 ES11 中,引入了一种新的内置方法:String.prototype.matchAll(),这个方法可以让你在字符串中进行全局匹配,且返回一个可以遍历所有匹配结果的迭代器对象。

    1 年前
  • 使用 Node.js 和 Express 构建一个简单的 API 服务器

    近年来,前端开发越来越火热,前端工程师在开发过程中需要使用许多工具,其中 Node.js 和 Express 是两个必不可少的工具。他们能够构建出一个简单且强大的 API 服务器,可以帮助我们快速开发...

    1 年前
  • Next.js 如何使用 Marked.js 进行 Markdown 渲染

    在 Web 开发中,Markdown 是一种常用的文本格式,用于快速编写易读易写的文档。在 Next.js 项目中,我们经常需要将 Markdown 转换为 HTML,在页面中进行渲染。

    1 年前
  • RESTful API 和微服务的关系与区别

    在软件开发中,常常需要实现不同系统之间的数据交互和通信,而 RESTful API 和微服务是两种常见的实现方式。两者都是基于 HTTP 协议的架构风格,但在细节方面有所不同。

    1 年前
  • Socket.io 如何优化前端体验

    Socket.io 是一种实时通讯协议,使用它可以实现客户端和服务器之间的实时双向通讯。在前端开发中,Socket.io 可以用于优化用户体验,例如实时聊天、游戏等交互性强的应用。

    1 年前
  • 完美解决 Babel 和 TypeScript 的问题:ES10

    如果你正在从Babel或TypeScript的世界中寻找一种更先进的方法来编写JavaScript,那么ES10将是一种值得考虑的选择。ES10支持许多高级语言特性,这些特性可以让您更轻松地编写和维护...

    1 年前
  • # 如何使用 Webpack 实现 CDN 资源加速

    如何使用 Webpack 实现 CDN 资源加速 前言 随着前端开发的不断发展,前端工程化构建工具出现,Webpack 作为其中一种工具受到广泛的关注和使用。Webpack 已经成为了前端开发不可或缺...

    1 年前
  • 常用的 Sequelize 数据库操作

    Sequelize 是 Node.js 中一款流行的 ORM 框架,它提供了非常便捷的数据存储和访问方法。本文将介绍 Sequelize 中一些最常用的数据库操作。

    1 年前
  • 前端开发必备 Vue.js 脚手架

    Vue.js 是当今最流行的前端框架之一,它提供了灵活性和易用性,可以让我们以更高效的方式构建 Web 应用程序。Vue.js 提供了一组完整的工具来帮助我们快速地构建应用程序,而 Vue.js 脚手...

    1 年前
  • TypeScript 中使用 Sequelize-typescript 库的教程及最佳实践

    前言 在使用 Node.js 进行后端开发时,我们经常需要使用数据库。而 Sequelize 是一款优秀的 ORM 库,可以方便地操作多种不同类型的数据库。 同时,使用 TypeScript 进行开发...

    1 年前
  • Angular 应用中解决数据缓存问题的方法

    在 Angular 应用中,缓存数据是一个常见的问题。我们通常需要将数据缓存到本地以提高应用程序性能。但是,缓存数据的方式不同,可能会影响到应用程序的性能和可维护性。

    1 年前
  • 用 SASS 实现复杂事件动画

    SASS 是一种 CSS 的预处理器,它提供了许多便捷的功能来优化 CSS 的开发体验。其中最为常用的就是变量、嵌套、继承、混合器等特性。但是 SASS 还有另外一个被广泛应用的特性,那就是它能够实现...

    1 年前
  • MongoDB 如何进行查询优化?

    在使用 MongoDB 进行数据存储的过程中,查询是至关重要的部分。查询的效率和优化不仅关系到数据的读取速度,还关系到系统的整体性能。因此,对 MongoDB 进行查询优化是非常必要的。

    1 年前
  • Chai 如何测试一个元素是否具有某种类名?

    在前端开发过程中,我们经常需要对页面上的元素进行验证。其中,测试元素是否具有某种类名是一种常见的需求。本文将介绍如何使用 Chai 进行类名测试,其目的是帮助您更好地进行前端开发测试。

    1 年前
  • CSS Grid 的 Gap 属性:掌握网格布局实现 UI 设计

    在前端开发中,网格布局是一种实现 UI 设计和页面布局的常用方式。而 CSS Grid 是可用于实现网格布局的一种强大工具。在使用 CSS Grid 进行网格布局时,我们可以使用其中的 Gap 属性来...

    1 年前
  • Material Design 下的圆角卡片实现方法

    圆角卡片一直是 Material Design 风格的常用组件,它优美的外观和丰富的交互效果成为了众多前端设计师喜欢的选择。在本文中,我们将详细介绍 Material Design 下的圆角卡片实现方...

    1 年前
  • 无障碍辅助技术在电商网站中的应用分析

    近年来,无障碍辅助技术越来越受到网站开发者们的重视。这项技术可以为那些身体或视觉上有障碍的用户提供更好的网络使用体验,让他们也能自由地浏览网站、参与网站活动和交易等。

    1 年前
  • 七个步骤,提高 JavaScript 性能

    JavaScript 是前端开发中不可或缺的一部分,但在网页加载速度、运行效率等方面, JavaScript 也经常成为瓶颈。本文将介绍七个步骤,帮助你提高 JavaScript 的性能。

    1 年前

相关推荐

    暂无文章