问题描述
需求:从一个变化的列表list中取出第一条数据,list 10s更新一次,10s内不能一直取第一条,需要均衡;
bug代码:
// lua脚本
var copyIndexScript string = `
local value = redis.call("Get", KEYS[1])
if value == false then
redis.call("Set" , KEYS[1], "0")
redis.call("Expire" , KEYS[1], KEYS[2])
return 0
else
local value1 = redis.call("Incr" , KEYS[1])
return value1
end
`
// lua脚本缓存hash值
var copyIndexLuaHash string
//redis初始化
func init() {
RedisClient = redis.NewClusterClient(&redis.ClusterOptions{
Addrs: []string{"127.0.0.1:7000","127.0.0.1:7001","127.0.0.1:7002","127.0.0.1:7003","127.0.0.1:7004","127.0.0.1:7005"},
Password: "", //密码
})
//脚本会产生一个sha1哈希值,下次用的时候可以直接使用这个值
copyIndexLuaHash, _ = RedisClient.ScriptLoad(copyIndexScript).Result()
}
// 获取下标值
n, err := RedisClient.EvalSha(copyIndexLuaHash, []string{id, time}).Result()
报错:CROSSSLOT Keys in request don't hash to the same slot
原因:所有key必须在1个slot上
修改为:用参数传参;
// lua脚本
var copyIndexScript string = `
local value = redis.call("Get", ARGV[1])
if value == false then
redis.call("Set" , ARGV[1], "0")
redis.call("Expire" , ARGV[1], ARGV[2])
return 0
else
local value1 = redis.call("Incr" , ARGV[1])
return value1
end
`
// 获取下标值
n, err := RedisClient.EvalSha(copyIndexLuaHash,nil, []string{id, time}).Result()
报错:NOSCRIPT No matching script. Please use EVAL
原因:集群模式,在当前节点,获取缓存sha1,可能获取不到;干脆直接用eval
script load命令用于将脚本script添加到脚本缓存中,但并不立即执行这个脚本。如果给定的脚本已经在缓存里面了,那么不执行任何操作。在脚本被加入到缓存之后,通过EVALSHA命令,可以使用脚本的 SHA1 校验和来调用这个脚本。脚本可以在缓存中保留无限长的时间,直到执行SCRIPT FLUSH为止。
EVAL命令也会将脚本添加到脚本缓存中,但是它会立即执行输入的脚本。
// 获取下标值
n, err := RedisClient.Eval(copyIndexScript,nil, []string{id, time}).Result()
适配阿里云redis集群,报错:ERR for redis cluster, eval/evalsha number of keys can't be negative or zerorn
阿里云Redis集群对lua脚本限制如下: https://developer.aliyun.com/article/645851
- 所有key都应该由 KEYS 数组来传递,redis.call/pcall 中调用的redis命令,key的位置必须是KEYS array(不能使用Lua变量替换KEYS),否则直接返回错误信息,“-ERR bad lua script for redis cluster, all the keys that the script uses should be passed using the KEYS arrayrn”。
- 所有key必须在1个slot上,否则返回错误信息,“-ERR eval/evalsha command keys must be in same slotrn”。
- 调用必须要带有key,否则直接返回错误信息, “-ERR for redis cluster, eval/evalsha number of keys can’t be negative or zerorn”。
解决方案:
按照KEYS,ARGV传参
// lua脚本
var copyIndexScript string = `
local value = redis.call("Get", KEYS[1])
if value == false then
redis.call("Set" , KEYS[1], "0")
redis.call("Expire" , KEYS[1], ARGV[1])
return 0
else
local value1 = redis.call("Incr" , KEYS[1])
return value1
end
`
// 获取下标值
n, err := RedisClient.Eval(copyIndexScript, []string{id}, time).Result()
扩展:redis lua脚本做ip限流
ratelimiting.lua,内容如下
local times = redis.call('incr',KEYS[1])
if times == 1 then
redis.call('expire',KEYS[1], ARGV[1])
end
if times > tonumber(ARGV[2]) then
return 0
end
return 1
在redis客户端机器上,如何测试这个脚本呢?如下:
redis-cli --eval ratelimiting.lua rate.limiting:127.0.0.1 , 10 3