redis lua脚本中禁用os.time(),必须用redis.call('time')获取服务端秒+微秒时间,返回{seconds, microseconds}字符串数组,需转为数值后计算毫秒级时间戳,且依赖服务器本地时钟。

Redis Lua脚本里不能直接调用 os.time()
Redis 的 Lua 环境是沙箱化的,禁用了 os、io、math.random 等标准库函数,os.time() 会直接报错:attempt to call a nil value (global 'os')。这不是权限问题,是 Redis 主动移除了这些模块来保证脚本的纯性与可重现性。
用 redis.call('time') 拿到秒+微秒级时间
redis.call('time') 是 Redis 提供的唯一可靠方式,它返回一个包含两个元素的数组:{seconds, microseconds}(都是字符串)。注意不是毫秒,也不是 Unix 时间戳整数,需要自己转换:
local now = redis.call('time')
local ts_sec = tonumber(now[1])
local ts_usec = tonumber(now[2])
local timestamp_ms = ts_sec * 1000 + math.floor(ts_usec / 1000)
- 不要用
tonumber(now[2]) / 1000再math.floor—— 浮点运算在 Lua 中可能引入精度误差(比如123456 / 1000可能得123.45599999999999) - 如果只要秒级时间戳,直接用
tonumber(now[1])就够了,更安全 -
redis.call('time')返回的是 Redis 服务器本地时间,和客户端时钟不同步;若需一致性时间,必须以服务端为准
别用 redis.call('eval') 或其他绕过方式伪造时间
有人尝试在脚本里嵌套 eval 调用另一个脚本、或用 redis.call('get', 'fake_time') 读预设键——这些都不是“当前时间”,而是缓存值或人为注入值,无法替代真实服务端时间。
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
- 依赖外部键模拟时间戳,会引入竞态:写入和读取之间可能有延迟或并发覆盖
-
redis.call('eval')在 Lua 脚本中调用自身或其他脚本,仍受相同沙箱限制,不会恢复os库 - 从客户端传时间戳(如
EVAL ... 0 1712345678)不可靠:网络延迟、客户端时钟不准、重放攻击都可能导致逻辑错误
时间精度够用吗?看场景再决定
redis.call('time') 的精度是微秒级,但实际可用精度取决于 Redis 单线程执行模型:脚本内两次调用 time 的结果可能完全一样,因为中间没发生事件循环切换。如果你在同一个脚本里多次需要“递增时间”,不能靠反复调用 time 来实现。
- 做日志打点、过期判断、幂等窗口校验,秒级或毫秒级已足够
- 需要严格单调递增序号?改用
INCR配合时间戳拼接,例如ts_ms .. ':' .. redis.call('incr', 'seq:counter') - 高频脚本中频繁调用
time不影响性能,但没必要——一次获取、多次复用即可
真正容易被忽略的是:所有基于时间的逻辑,都默认绑定在 Redis 实例所在机器的系统时钟上。如果运维没做 NTP 同步,或者跨机房部署多个 Redis 实例,它们的 time 结果可能不一致——这时候“当前时间”本身就成了分布式上下文里的模糊概念。










