Redis - 脚本
Redis - Lua 脚本
Section titled “Redis - Lua 脚本”Redis 允许使用内置的 Lua 解释器(Redis 2.6.0 起可用)执行服务器端脚本。Redis 脚本最主要且强大的优势是原子性。脚本总是作为一个单一的、不可中断的操作执行。在脚本执行期间,没有其他命令可以并发运行,这确保了复杂操作的数据一致性。
为何使用 Lua 脚本?
Section titled “为何使用 Lua 脚本?”- 原子性:将复杂的、多步骤的操作(如“先读后写”)作为单个原子事务执行,而无需使用
MULTI/EXEC命令。 - 性能:通过将多个命令组合成客户端和 Redis 服务器之间的一次往返,减少网络延迟。
- 数据本地性:逻辑在数据所在的服务器上执行,这比将数据拉取到客户端、处理后再发送回去更高效。
执行脚本:EVAL 和 EVALSHA
Section titled “执行脚本:EVAL 和 EVALSHA”你可以使用 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)。最佳实践和现代发展
Section titled “最佳实践和现代发展”- 优先使用 EVALSHA:始终尝试使用
SCRIPT LOAD缓存脚本,并使用EVALSHA执行它们。大多数客户端库会自动处理此过程。 - 保持脚本快速:脚本是阻塞的。长时间运行的脚本会使整个 Redis 服务器停滞。避免复杂的循环或 CPU 密集型逻辑。
- KEYS vs. ARGV:始终通过
KEYS表而不是ARGV传递键名。这对于 Redis 集群兼容性和未来的分析工具至关重要。 - Redis 函数 (Redis 7+):Redis 7 引入了函数(Functions),这是一种更高级的脚本管理方式。函数被分组到库中,具有持久性,并在集群中进行复制,为独立的
EVAL脚本提供了更健壮、更易管理的替代方案。
| Command | Description |
|---|---|
| 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+)。 |