首页 > 解决方案 > 如何以服务器端时间戳作为分数存储在 Redis 排序集中?

问题描述

我想使用一个排序集来存储对象,使用 redis-server 时间戳作为分数。

我知道我可以使用带有*id 的 Redis Streams,但是 Redis Streams 有一些限制,包括我不能编辑对象,我不能使用等级或字典排序,我不能真正删除中间的对象、联合或相交等。

我想以原子方式执行此操作,并使用 redis-server 时间戳,这样我就可以使用多个客户端ZADD而不必担心时钟同步。

这该怎么做?

标签: redis

解决方案


解决方案是使用 Lua 脚本:

local time = redis.call('TIME')
local ts = time[1]..string.format('%06d', time[2])
return redis.call('ZADD', KEYS[1], ts, ARGV[1])

这里我们使用 RedisTIME命令。命令返回:

  • 以秒为单位的 unix 时间
  • 微秒

所以我们可以连接这两者并使用微秒时间戳。我们需要对微秒部分进行零填充。

由于排序集适用于高达 2^53 的整数值,因此我们的时间戳一直到 2255 年都是安全的。

这是 Redis-Cluster 安全的,因为我们存储在一个密钥中。要使用多个密钥,如果要比较时间戳,请确保使用哈希标签将它们放在同一个节点上。

您可以修改脚本以使用低于微秒的分辨率。

这里的EVAL命令,简单的传递键和值作为参数,无需事先创建排序集:

EVAL "local time = redis.call('TIME') local ts = time[1]..string.format('%06d', time[2]) return redis.call('ZADD', KEYS[1], ts, ARGV[1])" 1 ssetKey myVal

与往常一样,您可能想要加载脚本并使用EVALSHA.

> SCRIPT LOAD "local time = redis.call('TIME') local ts = time[1]..string.format('%06d', time[2]) return redis.call('ZADD', KEYS[1], ts, ARGV[1])"
"81e366e422d0b09c9b395b5dfe03c03c3b7b3bf7"
> EVALSHA 81e366e422d0b09c9b395b5dfe03c03c3b7b3bf7 1 ssetKey myNewVal
(integer) 1

关于 Redis 版本的说明。如果您正在使用:

  • Redis 3.2 之前的版本:对不起,你不能使用TIME(非确定性命令)然后用ZADD.
  • Redis 版本大于 3.2 但 < 5.0:添加redis.replicate_commands()在脚本之上。将脚本视为纯函数
  • Redis 5.0 一上来:你很好。

推荐阅读