Redis Lua禁用math.random以保证确定性,可用SRANDMEMBER展开权重列表或手写LCG伪随机生成器;需规避内存超限、超时、参数截断等部署坑。

Redis Lua脚本里没有 math.random()?先确认运行环境
Redis 内置的 Lua 解释器(Lua 5.1)默认禁用 math.random 和 math.randomseed,直接调用会报错 attempt to call a nil value (field 'random')。这不是你写错了,是 Redis 主动阉割了随机数生成能力——为的是保证脚本在主从、AOF 重放时**确定性执行**。
所以别试 math.random(1, #arr),它一定失败。可行路径只有两条:要么用 Redis 自带的 redis.call('SRANDMEMBER', ...) 类命令间接实现,要么用确定性伪随机(如线性同余)手动算索引。
用 SRANDMEMBER + 权重预处理模拟加权随机
如果你的“权重”是整数且总和不大(比如每个元素权重 ≤ 100,总数 ≤ 1000),最稳的方式是把元素按权重展开成列表,再用 SRANDMEMBER 抽一个。例如:['a','a','b','c','c','c'] 表示 a:2, b:1, c:3。
但注意:展开操作必须在 Lua 脚本里完成,不能靠客户端传入大数组——Redis 对 Lua 脚本内存和执行时间有限制,展开后数组太大可能触发 OOM command not allowed when used memory > 'maxmemory' 或超时。
- 用
redis.call('LRANGE', KEYS[1], 0, -1)拉出原始数据(假设结构是item:weight的字符串,如"apple:5") - 在 Lua 中解析每项,用
table.insert循环 push 多次(权重值决定次数) - 最后调
redis.call('SRANDMEMBER', '__temp_key')不现实——Lua 不能直接创建新 key;改用math.random()替代方案(见下一条)
手写 LCG 伪随机生成器来算加权索引
既然 math.random 被禁,就自己实现一个轻量、确定性的随机数生成器,比如线性同余生成器(LCG)。它只需要几个整数运算,不依赖外部状态,同一输入种子永远输出相同序列,满足 Redis 确定性要求。
关键点:种子必须来自 Redis 可控变量,比如用 KEYS[1] 的长度、当前时间毫秒(redis.call('TIME') 返回两个整数,拼成 seed)、或客户端传入的 ARGV[1] 作为盐值。
- 定义 LCG 函数:
local function lcg(seed) return (seed * 1103515245 + 12345) % 2147483647 end - 对每个元素计算「累积权重区间」,比如 [0,2), [2,3), [3,6),然后用 LCG 输出值模总权重,查落在哪个区间
- 不要用
os.time()—— Lua 沙箱里不可用;可用redis.call('TIME')获取秒+微秒,组合成初始 seed - 权重为浮点数?别碰。Redis Lua 只支持 double,但浮点误差会导致区间边界错位,强制转整数或用定点缩放(如 ×100)
实际部署时最容易被忽略的坑
脚本上线后突然返回空或报错,大概率不是算法问题,而是这几处没卡死:
-
EVAL命令传参超过 1024 个ARGV项?Redis 会截断,导致权重数组不全 → 改用 Hash 结构存数据,用HGETALL读 - 脚本执行超时(默认 5 秒),尤其当权重总和上万、LCG 循环查区间太慢 → 提前用
redis.call('EXISTS', ...)快速失败,或把预计算逻辑移到客户端 - 从节点执行时报
NOSCRIPT?说明没用EVALSHA缓存,或脚本被 flush → 上线前先SCRIPT LOAD,记录 SHA1 后统一用EVALSHA - 权重动态更新频繁?别在 Lua 里反复
HGETALL,考虑用 Sorted Set 存score=weight,配合ZREVRANGEBYSCORE+ 客户端加权采样
真正复杂的权重逻辑,往往不该全压进 Lua;判断依据很简单:如果单次脚本执行 > 10ms 或代码行数 > 80,就该拆出去。

















