Skip to content

Redis - 脚本

Redis 允许使用内置的 Lua 解释器(Redis 2.6.0 起可用)执行服务器端脚本。Redis 脚本最主要且强大的优势是原子性。脚本总是作为一个单一的、不可中断的操作执行。在脚本执行期间,没有其他命令可以并发运行,这确保了复杂操作的数据一致性。

  • 原子性:将复杂的、多步骤的操作(如“先读后写”)作为单个原子事务执行,而无需使用 MULTI/EXEC 命令。
  • 性能:通过将多个命令组合成客户端和 Redis 服务器之间的一次往返,减少网络延迟。
  • 数据本地性:逻辑在数据所在的服务器上执行,这比将数据拉取到客户端、处理后再发送回去更高效。

你可以使用 EVAL 命令执行脚本。为了性能,更好的做法是使用 SCRIPT LOAD 命令将脚本缓存到服务器上,该命令会返回一个 SHA1 哈希值。然后你可以使用 EVALSHA 命令和其哈希值来执行已缓存的脚本,从而在后续调用中节省网络带宽。

EVAL script numkeys key [key ...] arg [arg ...]
  • script:Lua 脚本内容,以字符串形式表示。
  • numkeys:传递给脚本的键名数量(从后续参数中获取)。
  • key [key …]:脚本将要访问的键名。这些键名通过 Lua 中的 KEYS 表(例如 KEYS[1]、KEYS[2])提供。
  • arg [arg …]:脚本的额外参数。这些参数通过 Lua 中的 ARGV 表(例如 ARGV[1]、ARGV[2])提供。

实际示例:一个原子性的限流器

Section titled “实际示例:一个原子性的限流器”

让我们实现一个简单的限流器,它允许用户在特定时间窗口内执行一定数量的操作。这需要一个原子性的“递增并设置过期时间”操作,这正是 Lua 脚本的完美用例。

-- Lua 脚本 --
-- KEYS[1]:用户的键(例如 'rate_limit:user123')
-- ARGV[1]:过期时间窗口,单位秒
local current = redis.call('INCR', KEYS[1])
if tonumber(current) == 1 then
redis.call('EXPIRE', KEYS[1], ARGV[1])
end
return current

执行脚本:

# 用户 user123 的首次请求,时间窗口为 60 秒。
# 脚本将计数器递增到 1 并设置过期时间。
redis> EVAL "local current = redis.call('INCR', KEYS[1])\nif tonumber(current) == 1 then\n redis.call('EXPIRE', KEYS[1], ARGV[1])\nend\nreturn current" 1 rate_limit:user123 60
(integer) 1
# 60 秒内的第二次请求。计数器现在为 2。
redis> EVAL "... same script ..." 1 rate_limit:user123 60
(integer) 2
# 应用程序逻辑随后会检查返回的计数是否超过限制(例如,> 10)。
  • 优先使用 EVALSHA:始终尝试使用 SCRIPT LOAD 缓存脚本,并使用 EVALSHA 执行它们。大多数客户端库会自动处理此过程。
  • 保持脚本快速:脚本是阻塞的。长时间运行的脚本会使整个 Redis 服务器停滞。避免复杂的循环或 CPU 密集型逻辑。
  • KEYS vs. ARGV:始终通过 KEYS 表而不是 ARGV 传递键名。这对于 Redis 集群兼容性和未来的分析工具至关重要。
  • Redis 函数 (Redis 7+):Redis 7 引入了函数(Functions),这是一种更高级的脚本管理方式。函数被分组到库中,具有持久性,并在集群中进行复制,为独立的 EVAL 脚本提供了更健壮、更易管理的替代方案。
CommandDescription
EVAL script numkeys key [key …] arg [arg …]执行一个 Lua 脚本。
EVALSHA sha1 numkeys key [key …] arg [arg …]执行一个已通过 SHA1 哈希值缓存的 Lua 脚本。
SCRIPT LOAD script将脚本加载到脚本缓存中并返回其 SHA1 哈希值。
SCRIPT EXISTS sha1 [sha1 …]检查缓存中是否存在一个或多个脚本。
SCRIPT FLUSH [ASYNC|SYNC]从脚本缓存中删除所有脚本。
SCRIPT KILL终止当前正在执行的脚本。只能用于未执行任何写操作的脚本。
FUNCTION LOAD [REPLACE] function-code加载一个函数库(Redis 7+)。
FCALL function numkeys [key [key …]] [arg [arg …]]调用已加载库中的一个函数(Redis 7+)。