以下示例最适合哪种 Redis 数据类型

What Redis data type fit the most for following example

我有以下情况:

  1. 获取数字数组(来自 REDIS)有条件地
  2. 对每个数字做一些异步的事情(根据数字从数据库中获取一些东西)
  3. 对于来自 DB 的结果集中的每件事,做另一个异步的事情

定期重复 1. 2. 3. 因为新数字将不断添加到 REDIS structure.Those 数字代表以毫秒为单位的 unix 时间戳,因此开箱即用,这些数字将始终按时间排序加法

有条件地 表示从 REDIS 中获取那些小于或等于当前 unix 时间戳(以毫秒为单位)的 unix 时间戳(Date.now())

问题是哪种 REDIS 数据类型最适合此用例,请记住此代码将扩展到 N 个实例,因此 N 个实例将共享对单个 REDIS 实例的访问。为了平均分担负载,每个实例将从 REDIS 中读取例如第一个(最旧的)5 个数字。 数字是唯一的(添加相同的数字应该会默默地失败)所以 REDIS SET 似乎是一个不错的选择,但从 REDIS 集中读取 M 个第一个元素似乎是不可能的。

为了防止两个不同的代码实例读取相同的数字,REDIS 读取操作应该是原子的,它应该读取数字并删除它们。如果任何异步操作在特定数字(steps 2. and 3.)上失败,则应将数字重新添加到 REDIS 以再次处理。应尽快将它们重新添加到头部而不是末尾,以便再次处理。据我所知SADD会把它推到尾部。

SMEMBERS key 会阅读所有内容,对我来说就像一把锤子。我需要包含一些应用程序逻辑来获得前五个,而不是检查小于或等于 Date.now() 的内容,然后删除它们并以某种方式将所有内容包装在单个事务中。除此之外,设置的基数可能很大。
SSCAN 听起来很有趣,但我不知道它在如上所述的 "scaled" 环境中是如何工作的。除此之外,根据 REDIS 文档:SCAN 系列命令仅对返回的元素提供有限的保证,因为我们增量迭代的集合在迭代过程中可能会发生变化。像上面描述的集合会经常改变

更合适的数据结构是排序集 - 成员有一个非常适合存储时间戳的浮点分数,您可以执行范围搜索(即小于或等于给定值的任何值)。

相关的起点是 ZADD, ZRANGEBYSCORE and ZREMRANGEBYSCORE 命令。

保证reading and removing members, you can choose between the the following options: Redis transactions, Redis Lua script and in the next version (v4) a Redis module时的原子性。

Transactions

使用事务只是意味着在您的实例上执行以下代码运行:

MULTI
ZRANGEBYSCORE <keyname> -inf <now-timestamp>
ZREMRANGEBYSCORE <keyname> -inf <now-timestamp>
EXEC

其中 <keyname> 是您的密钥名称,<now-timestamp> 是当前时间。

Lua script

Lua 脚本可以被缓存并 运行 嵌入到服务器中,因此在某些情况下这是一种更可取的方法。如果您需要流量控制(请记住,MULTI 事务 returns 只有在执行后才具有值),这绝对是原子逻辑短片段的最佳方法。这样的脚本如下所示:

local r = redis.call('ZRANGEBYSCORE', KEYS[1], '-inf', ARGV[1])
redis.call('ZREMRANGEBYSCORE', KEYS[1], '-inf', ARGV[1])
return r

为了 运行 这个,首先使用 SCRIPT LOAD and then call it with EVALSHA 缓存它,像这样:

EVALSHA <script-sha> 1 <key-name> <now-timestamp>

其中 <script-sha>SCRIPT LOAD 返回的脚本的 sha1。

Redis modules

在不久的将来,一旦 v4 正式发布,您就可以编写和使用模块了。一旦这成为现实,您将能够使用我们制作的这个提供 ZPOP 命令的模块,并且还可以扩展以涵盖此用例。