当前位置:   article > 正文

记录go-redis使用集群时,报错:CROSSSLOT Keys in request don‘t hash to the same slot_lua crossslot keys in request don't hash to the sa

lua crossslot keys in request don't hash to the same slot

问题描述

需求:从一个变化的列表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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
//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()
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
// 获取下标值
n, err := RedisClient.EvalSha(copyIndexLuaHash, []string{id, time}).Result()
  • 1
  • 2

报错: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
	`
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
// 获取下标值
n, err := RedisClient.EvalSha(copyIndexLuaHash,nil, []string{id, time}).Result()
  • 1
  • 2

报错: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()
  • 1
  • 2

适配阿里云redis集群,报错:ERR for redis cluster, eval/evalsha number of keys can't be negative or zerorn

阿里云Redis集群对lua脚本限制如下: https://developer.aliyun.com/article/645851

  1. 所有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”。
  2. 所有key必须在1个slot上,否则返回错误信息,“-ERR eval/evalsha command keys must be in same slotrn”。
  3. 调用必须要带有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
	`
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
// 获取下标值
n, err := RedisClient.Eval(copyIndexScript, []string{id}, time).Result()
  • 1
  • 2

扩展: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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

在redis客户端机器上,如何测试这个脚本呢?如下:

redis-cli --eval ratelimiting.lua rate.limiting:127.0.0.1 , 10 3
  • 1
本文内容由网友自发贡献,转载请注明出处:https://www.wpsshop.cn/w/你好赵伟/article/detail/794043
推荐阅读
相关标签
  

闽ICP备14008679号