Lua 脚本与 Redis Functions

Redis 可以在服务端执行 Lua 代码,将多个读写步骤变成一次原子执行。这能减少网络往返,并避免客户端在“读后写”之间被其他请求插入。

何时需要脚本

如果 INCRSET NX EX、Sorted Set 等内置命令已经能解决问题,应优先使用内置命令。

一个简单 Lua 示例

只有当前值等于调用方提供的 token 时才删除键:

if redis.call("GET", KEYS[1]) == ARGV[1] then
  return redis.call("DEL", KEYS[1])
end

return 0

这比客户端先 GETDEL 更安全,因为比较和删除在 Redis 中原子执行。

可以使用 eval() 学习脚本调用:

const releaseLockScript: string = `
  if redis.call("GET", KEYS[1]) == ARGV[1] then
    return redis.call("DEL", KEYS[1])
  end
  return 0
`;

const deleted: unknown = await client.eval(releaseLockScript, {
  keys: ["nloop:lock:report"],
  arguments: [lockToken],
});

返回值来自 Redis 协议。项目中应根据脚本契约进行类型收窄,不要直接使用 any

KEYSARGV

不要把键偷偷放进 ARGV,否则 Cluster 无法正确理解脚本涉及的键。

Node-Redis 脚本定义

Node-Redis 允许在创建客户端时通过 scriptsdefineScript() 注册带类型转换的脚本。官方示例会定义:

这种方式适合稳定、重复使用的脚本,但对初学者抽象较多。建议先理解 EVALKEYSARGV 和原子性,再封装成类型安全方法。

Redis Functions

Redis Functions 将函数库加载到服务端,可通过 FCALL 调用。与临时脚本相比,它更接近由 Redis 管理和部署的服务端程序。

使用 Functions 前要明确:

脚本最佳实践

特别提醒:分布式锁

SET NX EX 加“比较 token 后删除”可以构成单 Redis 实例上的基础租约,但完整分布式锁还涉及:

涉及资金、库存等强一致业务时,不要把几行 Redis 代码当作完整锁方案。应选择经过验证的算法或在权威数据库中实现并发控制。