EVAL、EVALSHA命令

Redis从2.6.0版本开始提供了eval命令,通过内置的Lua解释器,可以让用户执行一段Lua脚本并返回数据。因为Redis单线程模型的特点,可以保证多个命令的原子性(因为最近的项目需要用到简单的分布式锁,所以会用到lua来释放锁)

脚本性能

  1. Redis保证了脚本执行的原子性,所以在当前脚本没执行完之前,别的命令和脚本都是等待状态,所以一定要控制好脚本中的内容,防止出现需要消耗大量时间的内容(逻辑相对简单)。

带宽优化

  1. 为了避免每次执行都重复的将Lua脚本内容发送,Redis提供了evalsha命令,只需要将Lua脚本内容的SHA1校验和发送即可(evalsha 6b1bf486c81ceb7edf3c093f4c48582e38c0e791 0)。
  2. Lua脚本中的变量(动态数据)请使用KEYSARGV获取,如果把变量放在脚本中,必然会导致每次的脚本内容都不同(SHA1),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是统一管理的,也没那么麻烦)

官方地址:https://redis.io/commands/cluster-keyslot

// 部分代码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) {logger.error("解锁失败:" + 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 local 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 是 yesfoo{}{bar}键的 hash tag就是它本身。foo{{bar}}键的 hash tag 是 {bar

总结

  • redis集群版的lua脚本,可以通过key的部分字符串hash来解决
  • redis集群版的分布式是会根据KEY进行hash取模然后打到不同的slot,这种思想是典型的分而治之。分治,分流,降级。

思考

如果某个业务都通过key{mykey}去储存获取内容,所有的操作都会hash到同一个slot,这个slot所在的节点压力就会变大(不均衡),如果解决?

lua脚本在redis集群中执行报错--Lua script attempted to access a non local key in a cluster node...相关推荐

  1. redis集群添加节点报错Either the node already knows other nodes (check with CLUSTER NODES) or contains some k

    [README] redis集群添加节点报错 [ERR] Node 192.168.163.202:6380 is not empty. Either the node already knows o ...

  2. redis集群关闭 启动报错_使用虚拟机搭建 Redis 集群,实现数据库的负载均衡功能。...

    实操题目:使用虚拟机搭建 Redis 集群,实现数据库的负载均衡功能.并使用图文描述整个过程.先创建集群: ①创建集群需要使用ruby脚本,所以要先安装ruby环境 安装ruby环境:yum inst ...

  3. shell脚本遍历redis集群所有节点日志,并入库到hadoop中,进行数据分析

    shell脚本遍历redis集群所有节点,并入库到hadoop中的数据库中,进行数据分析,在此留存,以备他用 [AAAAAAnilRedis@FXYY-FS-redis2 ~]$ cat /home/ ...

  4. Redis集群客户端添加数据报错(error) CLUSTERDOWN The cluster is down

    在连接到Redis集群,向集群中添加数据的时候报错了 (error) CLUSTERDOWN The cluster is down 首先呢,检查集群的运行情况,使用搭建集群是用到的redis-tri ...

  5. java连接cdh集群_有一台电脑需要通过beeline的方式连接到CDHclouderahadoop集群,提示报错。...

    有一台电脑需要通过beeline的方式连接到CDHclouderahadoop集群,提示报错. 码农的苦恼 2020-02-24 10:25:48 目前客户端服务器已经开通了3000-60000的端口 ...

  6. 你了解Redis集群中的秘密吗?

    ​前言 今天早上北京地铁,看见一个正在学习Redis的小姐姐,很优秀 在之前的Redis系列文章中,介绍了Redis的持久化.主从复制以及哨兵机制,主从复制+哨兵机制,既可以解决主master和从sl ...

  7. redis集群中slot迁移的BUG:clusterManagerMoveSlot failed: ERR I don‘t know about node xx,解决办法如下文

    这个BUG很奇怪,先放BUG出现的图片吧 报错:clusterManagerMoveSlot failed: ERR I don't know about node xxx redis找不到他自己创建 ...

  8. python redis 集群_python 连接redis集群 ,常见报错解决。

    背景:工作需要,处理的数据需要通过redis进行缓存处理,之后方便统计分析. 目标:python连接redis进行读取&写入. 连接 redis 与 redis集群 是不同的 !!! 一.连接 ...

  9. 记录go-redis使用集群时,报错:CROSSSLOT Keys in request don‘t hash to the same slot

    问题描述 需求:从一个变化的列表list中取出第一条数据,list 10s更新一次,10s内不能一直取第一条,需要均衡: bug代码: // lua脚本 var copyIndexScript str ...

最新文章

  1. 原 EOS智能合约开发入门
  2. f2 柱状图滚动 钉钉小程序_钉钉小程序图表库AntV - F2 使用总结
  3. SQL SERVER中求上月、本月和下月的第一天和最后一天[转]
  4. Python函数参数匹配模型(上)
  5. oracle union 类型转换,Oracle 中 union 和union all 的简单使用说明
  6. Android开发第二次课 布局方式
  7. SQL中Case语句用法讨论
  8. python中的help和dir_Python中help()和dir()函数的用法是什么?
  9. 小学计算机兴趣小组计划书,兴趣小组计划
  10. 初级软考程序员不会c语言,初级程序员考试就这么简单
  11. 苹果电脑安装windows系统 失败后 磁盘空间丢失
  12. Flutter调用JSON序列化出现type ‘String‘ is not a subtype of type ‘MapString, dynamic‘
  13. 十四、中国为什么没有自己的指令集
  14. 一张图带你解读--如何从零开始学习接口自动化
  15. PERL 连接FTP实现文件上传下载
  16. Windows自带的造字功能使用
  17. [激光原理与应用-22]:《激光原理与技术》-8- 激光产生技术-选模技术:横模、纵模
  18. 上海市食品药品监督管理局
  19. DSSS、OFDM、FHSS的区别与联系
  20. 数字孪生城市技术图谱和平台架构

热门文章

  1. linux提示光标下无字符串,vim复制、粘贴、删除、撤销、移动光标(linux)
  2. 自定义JAVA注解_深入理解Java:自定义java注解
  3. k8s angular mysql_Angular 实践:如何优雅地发起和处理请求
  4. git ssh配置完后拉取代码_Git中SSH key配置秘钥生成和如何拉取代码
  5. numpy 矩阵乘法_一起学习Python常用模块——numpy
  6. java memcachedclient_memcached client — memcached client for java使用 | 学步园
  7. plsql 无法解析指定的连接标识符_Java方法加载、解析、存储、调用
  8. 计算机临床医学自动化哪个好,大学专业难度排行前十名,临床医学位列第二
  9. 【CSS 伪类】顺序
  10. nutch开发(六)