lua脚本在redis集中执⾏报错--Luascriptattemptedtoacces。。。
EVAL、EVALSHA命令
Redis从2.6.0版本开始提供了eval命令,通过内置的Lua解释器,可以让⽤户执⾏⼀段Lua脚本并返回数据。因为Redis单线程模型的特点,可以保证多个命令的原⼦性(因为最近的项⽬需要⽤到简单的分布式锁,所以会⽤到lua来释放锁)
脚本性能
1. Redis保证了脚本执⾏的原⼦性,所以在当前脚本没执⾏完之前,别的命令和脚本都是等待状态,所以⼀定要控制好脚本中的内容,防⽌出现需要消耗⼤量时间的
内容(逻辑相对简单)。
带宽优化
1. 为了避免每次执⾏都重复的将Lua脚本内容发送,Redis提供了evalsha命令,只需要将Lua脚本内容的SHA1校验和发送即可(evalsha
6b1bf486c81ceb7edf3c093f4c48582e38c0e791 0)。
2. Lua脚本中的变量(动态数据)请使⽤KEYS和ARGV获取,如果把变量放在脚本中,必然会导致每次的脚本内容都不同(SHA1),Redis缓存⼤量⽆⽤或者⼀次
性的脚本内容。
linux安装redis报错Redis Cluster 或阿⾥云Redis集版使⽤注意事项
Redis从3.0开始⽀持了Cluster功能,之前使⽤eval的时候可能没什么问题,但当切换成Cluster模式的时候,可能会出现⼀些问题:
1. ERR Error running script (call to f_4a610f5543b3c3450220da7bd47825d3b6bffae8): @user_script:1: @user_script: 1: Lua script attempted to access a non local
key in a cluster node
2. ERR eval/evalsha command keys must be in same slot(阿⾥云Redis集版)
上⾯的错误是因为Redis要求单个Lua脚本操作的key必须在同⼀个节点上,但是Cluster会将数据⾃动分布到不同的节点(虚拟的16384个slot,具体看官⽅⽂档),阿⾥云集版的官⽹其实也有对应说明:在Redis集版实例中,事务、脚本等命令要求所有的key必须在同⼀个slot中,如果不在同⼀个slot中将返回以下错误信息(:command keys must in same slot)
CLUSTER KEYSLOT key的⽂档中提供了解决⽅法,你需要将把key中的⼀部分使⽤{}包起来,redis将通过{}中间的内容作为计算slot的key,类似key1{mykey}、key2{mykey}(如果你的key是“REDIS_LOCK_FORPR”,可以讲该key的⼀部分⽤{}括起来,例如“REDIS_LOCK_{FORPR}”)这样的都会存放到同⼀个slot中(缺点是不能平滑的过度⽼业务,需要修改原来使⽤的key,如果之前的key是统⼀管理的,也没那么⿇烦)
// 部分代码
private static final String DISTRIBUTE_LOCK_SCRIPT_UNLOCK_VAL = "if" +
" redis.call('get', KEYS[1]) == ARGV[1]" +
" then" +
" return redis.call('del', KEYS[1])" +
" else" +
" return 0" +
" end";
Object eval = 0;
List<String> keys = new ArrayList<>();
keys.add(REDIS_LOCK_PREFIX + lockKey);
List<String> argv = new ArrayList<>();
argv.add(lockValue);
try {
// 这⾥不⽤指名有⼏个key,jedis内部会根据keys集合⼤⼩来获取
eval = jedis.eval(DISTRIBUTE_LOCK_SCRIPT_UNLOCK_VAL, keys, argv);
} catch (Exception e) {
<("解锁失败:" + e.getMessage());
} finally {
if (jedis != null) {
jedis.close();
}
}
集环境中 lua 处理
redis 集中,会将键分配的不同的槽位上,然后分配到对应的机器上,当操作的键为⼀个的时候,⾃然没问题,但如果操作的键为多个的时候,集如何知道这个操作落到那个机器呢?⽐如简单的mget命令,mget test1 test2 test3,还有我们上⾯执⾏脚本时候传⼊多个参数,带着这个问题我们继续。
⾸先⽤docker 启动⼀个redis 集,docker pull grokzen/redis-cluster,拉取这个镜像,然后执⾏docker run -p 7000:7000 -p 7001:7001 -p 7002:7002 -p 7003:7003 -p 7004:7004 -p 7005:7005 --name redis-cluster-script -e "IP=0.0.0.0" grokzen/redis-cluster启动这个容器,这个容器启动了⼀个 redis 集,3 主 3 从。
我们从任意⼀个节点进⼊集,⽐如redis-cli -c -p 7003,进⼊后执⾏cluster nodes可以看到集的信息,我们链接的是从库,执⾏set lua fun,有同学可能会问了,从库也可以执⾏写吗,没问题的,集会计算出 lua 这个键属于哪个槽位,然后定向到对应的主库。
执⾏mset lua fascinating redis powerful,可以看到集反回了错误信息,告诉我们本次请求的键没有落到同⼀个槽位上
(error) CROSSSLOT Keys in request don't hash to the same slot
同样,还是上⾯的 lua 脚本,我们加上集端⼝号,执⾏redis-cli -p 7000 --eval /tmp/limit_fun.lua limit_vgroup 192.168.1.19 , 10 3 1548660999,⼀样返回上⾯的错误。
针对这个问题,redis官⽅为我们提供了hash tag这个⽅法来解决,什么意思呢,我们取键中的⼀段来计算hash,计算落⼊那个槽中,这样同⼀个功能不同的key 就可以落⼊同⼀个槽位了,hash tag 是通过{}这对括号括起来的字符串,⽐如上⾯的,我们改为mset lua{yes} fascinating redis{yes} powerful,就可以执⾏成功了,我这⾥ mset 这个操作落到了 7002 端⼝的机器。
同理,我们对传⼊脚本的键名做hash tag 处理就可以了,这⾥要注意不仅传⼊键名要有相同的hash tag,⾥⾯实际操作的key 也要有相同的hash tag,不然会报错Lua script attempted to access a non loc
al key in a cluster node,什么意思呢,就拿我们上⾯的例⼦来说,执⾏的时候如下所⽰,可以看到,前⾯的两个键都加了 hash tag ——yes,这样没问题,因为脚本⾥⾯只是⽤了⼀个拼接的 key —— limit_vgroup{yes}_192.168.1.19{yes}。
redis-cli -c -p 7000 --eval /tmp/limit_fun.lua limit_vgroup{yes} 192.168.1.19{yes} , 10 3 1548660999
如果我们在脚本⾥⾯加上redis.call("GET", "yesyes")(别让这个键跟我们拼接的键落在⼀个solt),可以看到就报了上⾯的错误,所以在执⾏脚本的时候,只要传⼊参数键、脚本⾥⾯执⾏ redis 命令时候的键有相同的 hash tag 即可。
另外,这⾥有个 hash tag 规则:
键中包含{字符;建中包含{字符,并在{字符右边;并且{,}之间有⾄少⼀个字符,之间的字符就⽤来做键的 hash tag。
所以,键limit_vgroup{yes}_192.168.1.19{yes}的 hash tag 是yes。foo{}{bar}键的 hash tag就是它本⾝。foo{{bar}}键的 hash tag 是{bar。
redis集版的lua脚本,可以通过key的部分字符串hash来解决
redis集版的分布式是会根据KEY进⾏hash取模然后打到不同的slot,这种思想是典型的分⽽治之。分治,分流,降级。
如果某个业务都通过key{mykey}去储存获取内容,所有的操作都会hash到同⼀个slot,这个slot所在的节点压⼒就会变⼤(不均衡),如果解决?

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。