摘要
Scripting / Functions 简介
Redis Scripting / Functions 是 Redis 提供的一套“服务器端可编程执行机制”,允许客户端把逻辑发送到 Redis 内部执行,从而:
1 2 3 4 将多条命令合并为一次原子执行 减少网络往返(RTT) 保证并发一致性 支持逻辑复用与版本化管理
Scripting / Functions 命令详解
一、Lua 脚本执行与管理
1.1 Sctipt 脚本执行(EVAL)
命令
语法
参数说明
示例
说明
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)
命令
语法
参数说明
示例
说明
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(函数库与调用)
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
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
监控
1 2 3 4 持久化(随 RDB / AOF 保存) 支持版本化与部署 支持多函数库 更适合平台化治理
Function 源码格式
1 2 3 4 5 6 7 8 9 10 11 #!lua name=ratelimit redis.register_function('allow' , function (keys, args) xxx return xxx end )
示例
1 2 3 业务背景 每个用户:每分钟最多 5 次请求 支持:高并发、原子统计、自动过期、可部署升级
Key
类型
示例
rate:{userId}
ZSET
score=timestamp
1 2 3 4 5 6 7 8 9 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 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 - windowredis.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 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" "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
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 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 )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 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)" "ratelimit" 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)
1 2 FCALL allow 1 rate:1001 5 60000 1705000000000
1 2 3 FUNCTION DELETE ratelimit "OK"