0%

Redis中的时间计算

在使用Redis时, 通常会使用EXPIRE, PEXPIRE系列函数设置生存时间, 当需要获取超时时间时使用TTL, PTTL获取剩余的生存时间。

同样的, 还可以使用EXPIREAT, PEXPIREAT以unix timestamp格式设置超时时间点, 但需要注意的是, Redis 7.0 之前没有函数能获取设置timestamp, 只能使用TTL, PTTL获取剩余的生存时间。

在实际使用中, 经常需要比较新旧生存周期的大小来决定是否更新数据, 如果你很幸运的使用7.0及之后的版本, 可以使用EXPIREAT, PEXPIREAT配合GT参数来设置过期时间, 如果新的过期时间小于已有过期时间, 则会返回0

那么老版本的Redis该如何实现这种功能呢?

在Redis中使用TIME命令可以获取当当前的timestamp, 加上TTL, PTTL获取的值之后就可以得到原本设置的过期时间。

需要注意的是TIME命令返回两个字符串(比如 “1672400893” 和 “528370” ), 第一个字符串中的数字代表以秒计时的unix timestamp, 第二个字符串中的数字代表当前这一秒中已经过去多少微秒。

为了完成数学计算, 需要使用tonumber函数将字符串转换为数字, 如果只需要秒及精度, 只使用第一个字符串的返回值即可, 如果需要毫秒级精度, 则需要将第一个数字乘1000, 将第二个数字除以1000, 并将两个数字相加。

以下LUA展示了如何实现带超时的SET命令毫秒时间戳的比较, 只有当新的超时时间戳大于已有的超时时间戳, 新的value才能成功设置, 返回服务器最终采用的时间戳, 客户端应该比较返回时间戳与传入时间戳是否相等, 若不等于则说明设置失败。

参数
KEYS[1]: SET 命令的 KEY
AEGV[1]: SET 命令的 VAL
AEGV[2]: 超时时间戳(ms)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
local expirePTTL = redis.call('PTTL', KEYS[1])
-- 如果PTTL返回值<0则说明key不存在或者没有设置过expire, 此时直接设置新值
if (expirePTTL >= 0) then
local serverTime = redis.call('TIME')
-- 计算原始过期时间戳
local expireTime = tonumber(serverTime[1]) * 1000 + tonumber(serverTime[2] / 1000) + expirePTTL
-- 如果原始过期时间戳大于传入时间戳则直接返回
-- 此处没有使用大于等于保证了如果返回时间戳与传入时间戳相等, 那么传入值一定被设置过一次
if (expireTime > tonumber(ARGV[2])) then
return expireTime
end
end

-- 设置值和过期时间
redis.call('SET', KEYS[1], ARGV[1])
redis.call('PEXPIREAT', KEYS[1], ARGV[2])
return ARGV[1]