Redis 命令详解:Scripting / Functions 命令

摘要

Scripting / Functions 简介

  • Redis Scripting / Functions 是 Redis 提供的一套“服务器端可编程执行机制”,允许客户端把逻辑发送到 Redis 内部执行,从而:
1
2
3
4
将多条命令合并为一次原子执行
减少网络往返(RTT)
保证并发一致性
支持逻辑复用与版本化管理

Scripting / Functions 命令详解

一、Lua 脚本执行与管理

1.1 Sctipt 脚本执行(EVAL)

  • 用于直接在 Redis 中执行 Lua 脚本。
命令 语法 参数说明 示例 说明
EVAL EVAL script numkeys key [key ...] arg [arg ...] script:Lua 脚本
numkeys:key 数量
EVAL "return redis.call('GET', KEYS[1])" 1 k1 直接执行脚本
EVAL_RO EVAL_RO script numkeys key [key ...] arg [arg ...] 只读执行 EVAL_RO "return redis.call('GET', KEYS[1])" 1 k1 副本安全
EVALSHA EVALSHA sha1 numkeys ... sha1:脚本摘要 EVALSHA abc123 1 k1 缓存执行
EVALSHA_RO EVALSHA_RO sha1 numkeys ... 只读缓存执行 同上 副本安全
  • 核心特性

1
2
3
4
原子执行(单线程)
可以访问 KEYS / ARGV
会阻塞事件循环(脚本过长有风险)
支持脚本缓存

1.2 脚本运行管理(SCRIPT)

  • 用于管理 Lua 脚本缓存和执行状态。

  • 将脚本加载到缓存中,避免多次执行相同脚本时都要重新发送脚本到 Redis 服务器。

  • 不会持久化,redis重启后失效

命令 语法 参数说明 示例 说明
SCRIPT LOAD SCRIPT LOAD script 加载脚本并返回 SHA1 SCRIPT LOAD "return 1" 预热,不会持久化,redis重启后失效
SCRIPT EXISTS SCRIPT EXISTS sha1 [sha1 ...] 判断是否已缓存 SCRIPT EXISTS abc123 校验
SCRIPT FLUSH SCRIPT FLUSH [ASYNC] 清空脚本缓存 SCRIPT FLUSH 运维
SCRIPT KILL SCRIPT KILL 终止正在执行脚本 SCRIPT KILL 紧急
SCRIPT DEBUG SCRIPT DEBUG YES|SYNC|NO 脚本调试模式 SCRIPT DEBUG YES 调试

二、Redis Functions(函数库与调用)

  • Redis 7 引入,用于替代大规模 Lua 脚本管理。

2.1 函数调用

命令 语法 参数说明 示例 说明
FCALL FCALL function numkeys key [key ...] arg [arg ...] function:函数名 FCALL stock.decr 1 stock:1 5 调用函数
FCALL_RO FCALL_RO function numkeys ... 只读调用 FCALL_RO metrics.get 1 k1 副本安全

2.2 函数库管理

  • FUNCTION LOAD 加载的脚本会被持久化保存,重启redis依旧有效。

命令 语法 参数说明 示例 说明
FUNCTION LOAD FUNCTION LOAD [REPLACE] payload payload:函数源码 FUNCTION LOAD <code> 加载函数,会持久化
FUNCTION LIST FUNCTION LIST [LIBRARY lib] 查看函数库 FUNCTION LIST 查询
FUNCTION DELETE FUNCTION DELETE library 删除函数库 FUNCTION DELETE mylib 清理
FUNCTION DUMP FUNCTION DUMP 导出函数库 FUNCTION DUMP 备份
FUNCTION RESTORE FUNCTION RESTORE dump [REPLACE] 恢复函数库 FUNCTION RESTORE <dump> 恢复
FUNCTION FLUSH FUNCTION FLUSH [ASYNC] 清空所有函数 FUNCTION FLUSH 高风险
FUNCTION KILL FUNCTION KILL 终止正在运行的函数 FUNCTION KILL 紧急停止
FUNCTION STATS FUNCTION STATS 运行统计 FUNCTION STATS 监控
  • Redis Functions 的优势

1
2
3
4
持久化(随 RDB / AOF 保存)
支持版本化与部署
支持多函数库
更适合平台化治理

Function 源码格式

1
2
3
4
5
6
7
8
9
10
11
#!lua name=ratelimit -- 这是 Redis Function 专用头声明
-- #!lua: 声明这是一个 Lua Function Library,目前只支持 Lua
-- name: library,函数库名称,全局唯一

redis.register_function('allow', function(keys, args) -- 函数注册 API,
-- allow : 函数名称
-- keys: KEY 数组
-- args: ARGV 数组
xxx -- 函数逻辑
return xxx -- 函数返回值,只支持 数组/整数/字符串/表格{a,b}作为返回值
end) -- 函数结束

示例

    • 场景:分布式滑动窗口限流器(企业级)
1
2
3
业务背景
每个用户:每分钟最多 5 次请求
支持:高并发、原子统计、自动过期、可部署升级
  • 数据模型设计

Key 类型 示例
rate:{userId} ZSET score=timestamp
  • 初始化数据

1
2
3
4
5
6
7
8
9
# 假设 userId = 1001
ZADD rate:1001 1704999990000 1704999990000
ZADD rate:1001 1704999995000 1704999995000
ZADD rate:1001 1704999998000 1704999998000
ZADD rate:1001 1705000001000 1705000001000
ZADD rate:1001 1705000002000 1705000002000

# 设置 key 自动过期,window = 60000 ms
PEXPIRE rate:1001 60000

Script 使用示例

  • 脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
local rateKey = KEYS[1]
local maxReq = tonumber(ARGV[1])
local window = tonumber(ARGV[2])
local now = tonumber(ARGV[3])

local minTime = now - window

-- 1. 清理过期请求
redis.call("ZREMRANGEBYSCORE", rateKey, 0, minTime)

-- 2. 当前请求数
local count = tonumber(redis.call("ZCARD", rateKey))

if count >= maxReq then
return count
end

-- 3. 记录请求
redis.call("ZADD", rateKey, now, now)

-- 4. 设置自动过期
redis.call("PEXPIRE", rateKey, window)

return count+1
  • 调用脚本

1
127.0.0.1:6379> EVAL "local rateKey = KEYS[1] local maxReq  = tonumber(ARGV[1]) local window  = tonumber(ARGV[2]) local now     = tonumber(ARGV[3]) local minTime = now - window redis.call(\"ZREMRANGEBYSCORE\", rateKey, 0, minTime) local count = tonumber(redis.call(\"ZCARD\", rateKey)) if count >= maxReq then return count end redis.call(\"ZADD\", rateKey, now, now) redis.call(\"PEXPIRE\", rateKey, window) return count+1" 1 rate:1001 5 60000 1705000003000
  • 预热后调用

1
2
3
4
5
6
127.0.0.1:6379> SCRIPT LOAD  "local rateKey = KEYS[1] local maxReq  = tonumber(ARGV[1]) local window  = tonumber(ARGV[2]) local now     = tonumber(ARGV[3]) local minTime = now - window redis.call(\"ZREMRANGEBYSCORE\", rateKey, 0, minTime) local count = tonumber(redis.call(\"ZCARD\", rateKey)) if count >= maxReq then return count end redis.call(\"ZADD\", rateKey, now, now) redis.call(\"PEXPIRE\", rateKey, window) return count+1"
## 脚本 SHA
"69ca329ae4744a8b509f9daefea0ddf6415defae"

## 调用
EVALSHA "69ca329ae4744a8b509f9daefea0ddf6415defae" 1 rate:1001 5 60000 1705000003000

SpringBoot 调用 Script

  • 直接运行脚本

1
2
3
4
5
6
7
8
9
10
11
Long count = redisTemplate.execute((RedisCallback<Long>) connection ->
connection.scriptingCommands().eval(
lua.getBytes(),
ReturnType.INTEGER,
1,
"rate:1001".getBytes(),
"5".getBytes(),
"60000".getBytes(),
"1705000003000".getBytes()
)
);
  • 预热脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 预热脚本
final String sha = redisTemplate.execute((RedisCallback<String>) connection ->
connection.scriptingCommands().scriptLoad(lua.getBytes())
);
// 执行脚本
final Long count = redisTemplate.execute((RedisCallback<Long>) connection ->
connection.scriptingCommands().evalSha(
sha.getBytes(),
ReturnType.INTEGER,
1,
"rate:1001".getBytes(),
"5".getBytes(),
"60000".getBytes(),
"1705000003000".getBytes()
)
);

Function 使用示例

目前 SpringBoot 仅支持 Lua 脚本,不支持 Function

  • Function 源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#!lua name=ratelimit

redis.register_function('allow', function(keys, args)

local rateKey = keys[1]
local maxReq = tonumber(args[1])
local window = tonumber(args[2])
local now = tonumber(args[3])

local minTime = now - window

-- 1. 清理过期请求
redis.call("ZREMRANGEBYSCORE", rateKey, 0, minTime)

-- 2. 当前请求数
local count = tonumber(redis.call("ZCARD", rateKey))

if count >= maxReq then
return count
end

-- 3. 记录请求
redis.call("ZADD", rateKey, now, now)

-- 4. 设置自动过期
redis.call("PEXPIRE", rateKey, window)

return count+1
end)
  • 加载 Function

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 加载 Function
127.0.0.1:6379> FUNCTION LOAD "#!lua name=ratelimit \n redis.register_function('allow', function(keys, args) local rateKey = keys[1] local maxReq = tonumber(args[1]) local window = tonumber(args[2]) local now = tonumber(args[3]) local minTime = now - window redis.call(\"ZREMRANGEBYSCORE\", rateKey, 0, minTime) local count = tonumber(redis.call(\"ZCARD\", rateKey)) if count >= maxReq then return count end redis.call(\"ZADD\", rateKey, now, now) redis.call(\"PEXPIRE\", rateKey, window) return count+1 end)"
## 输出 library_name
"ratelimit"

# 列出 Function
127.0.0.1:6379> FUNCTION LIST
1) 1) "library_name"
2) "ratelimit"
3) "engine"
4) "LUA"
5) "functions"
6) 1) 1) "name"
2) "allow"
3) "description"
4) (nil)
5) "flags"
6) (empty array)

  • 调用 Function

1
2
# 调用 Function: FCALL function_name num_keys key [key ...] arg [arg ...]
FCALL allow 1 rate:1001 5 60000 1705000000000
  • 删除 Function

1
2
3
FUNCTION DELETE ratelimit
## 输出
"OK"