一、关于evalsha的谣言
1、eval 和 evalsha基本使用
关于Redis lua的两个命令eval和evalsha,网上大多数文章都会介绍,eval带的参数是lua脚本内容,evalsha可以通过script load把lua脚本加载到Redis缓存起来,推荐使用evalsha**(性能更强)**
(1)、 eval
eval script numkeys [key [key ...]] [arg [arg ...]]
例如简单命令
> eval "return ARGV[1]" 0 hello
"hello"
(2)、 evalsha
(a) 脚本
> cat xxx.lua
return 'Immabe a cached script'
(b) 上传脚本返回sha
redis> script load xxx.lua
"c664a3bf70bd1d45c4284ffebb65a6f2299bfc9f"
(c) 直接执行sha
redis> evalsha c664a3bf70bd1d45c4284ffebb65a6f2299bfc9f 0
"Immabe a cached script"
2、evalsha性能确实更强!
这里给出一个lua脚本,是在没有Redis 6.2 zadd gt|lt功能前需要用lua来实现:
local var = redis.call('zscore',KEYS[1], ARGV[1])
if((not var) or tonumber(var) < tonumber(ARGV[2])) then
redis.call('zadd',KEYS[1],ARGV[2],ARGV[1])
end
return redis.call('zcard',KEYS[1])
类似实现:
(1) eval
./memtier_benchmark -s 10.108.203.169 -p 12615 --requests=50000 -c 10 --hide-histogram --command="eval \"local var = redis.call('zscore',KEYS[1], ARGV[1]) if( (not var) or tonumber(var) < tonumber(ARGV[2])) then redis.call('zadd',KEYS[1],ARGV[2],ARGV[1]) end return redis.call('zcard',KEYS[1])\" 1 score-rank zhangsan 10"
结果:
(2) evalsha
./memtier_benchmark -s 10.108.203.169 -p 12615 --requests=50000 -c 10 --hide-histogram --command="evalsha 2d78dd089e00088bb4a3daf07b8c4ec45dcba32e 1 score-rank zhangsan 10"
结果:
结论:确实evalsha性能会更好一些,大概10%-20%左右,这里我没测试复杂的脚本,但必须说一句用lua场景拼性能不太合适,我理解redis中的lua是一个辅助功能,如果要拼性能可以自定义命令或者使用module来实现更好
3、各种用法
(1) 迷信evalsha的
-
使用Redis主从结构:知道Redis主节点,使用script load加载lua脚本获取到${sha1}
-
使用集群模式,找DBA同学帮忙加载进去的(这种都埋坑,还记得 Redis只有script sha,还能找到lua脚本吗
(2) 不信evalsha的,觉得eval可靠的
redis.eval("xxxxx");
(3) 组合使用
try{
//自己计算sha1
redis.eval(sha1);
} catch (Exception e) {
if ("NOSCRIPT No matching script"){
redis.eval
}
}
这里有个冷知识,但是非常实用,那就是执行eval也可以生成sha1,下面我们做一个实验
127.0.0.1:12615> script load "return 'hello lua lua'"
"30c1c434e1d7aa18dd38735615b2fdf063306715"
127.0.0.1:12615> evalsha 30c1c434e1d7aa18dd38735615b2fdf063306715 0
"hello lua lua"
127.0.0.1:12615> info memory
number_of_cached_scripts:1
127.0.0.1:12615> script flush
OK
127.0.0.1:12615> evalsha 30c1c434e1d7aa18dd38735615b2fdf063306715 0
(error) NOSCRIPT No matching script. Please use EVAL.
127.0.0.1:12615> info memory
used_memory_scripts:0
继续用eval来测试
127.0.0.1:12615> eval "return 'hello lua lua'" 0
"hello lua lua"
127.0.0.1:12615> evalsha 30c1c434e1d7aa18dd38735615b2fdf063306715 0
"hello lua lua"
127.0.0.1:12615> info memory
number_of_cached_scripts:1
其实就是eval也会缓存,这个在官网里有说明,可能以前看的不够仔细
二、Redis 官方对eval的认知
其实官方对evalsha或者script cache的认知已经很清楚,这玩意就是缓存,不想保障SLA,因此在github上也有类似讨论,其实在Redis 5中已经添加了lua-replicate-commands(默认是yes),保持script cache具备SLA,https://github.com/redis/redis/pull/9812有相关讨论:
上面这个讨论已经说的很清楚:大概就是5.0用lua-replicate-commands先保证script cache SLA,但是也是临时的,3年后的Redis 7.0不保证了,给了一个缓冲期(但一般不会看的这么细。。)。
三、Redis 7.0的关于lua重要变化
1、RDB文件不在保存lua脚本
我做了一组实验,确实在Redis 7.0的RDB里看不到lua脚本了?
(1) xxd 6.0.15.rdb
(2) xxd 6.2.13.rdb
(3) xxd 7.0.11.rdb
有人说这个RDB的lua有啥用呢,我知道的可能有两个作用
(1) lua是个不错的功能,但可能是杀手
我们知道在redis里如果lua执行时间较长(lua-time-limit),redis必须使用shutdown才能恢复。所以一些大厂会让业务在使用lua前进行登记,或者对于存量可能采用分析RDB来统计
(2) 保住only evalsha:有时候业务是没错的,错的是Redis维护方不行
如果涉及到集群同步(仅同步、扩容、升级),需要在模拟复制过程中解析RDB文件,同步script cache(其实就是执行eval)
2、Redis主从不同步script cache
我又做了一组实验,在所有主节点上加载lua:
$ sh script-load.sh
"30c1c434e1d7aa18dd38735615b2fdf063306715"
"30c1c434e1d7aa18dd38735615b2fdf063306715"
"30c1c434e1d7aa18dd38735615b2fdf063306715"
"30c1c434e1d7aa18dd38735615b2fdf063306715"
"30c1c434e1d7aa18dd38735615b2fdf063306715"
"30c1c434e1d7aa18dd38735615b2fdf063306715"
"30c1c434e1d7aa18dd38735615b2fdf063306715"
"30c1c434e1d7aa18dd38735615b2fdf063306715"
"30c1c434e1d7aa18dd38735615b2fdf063306715"
"30c1c434e1d7aa18dd38735615b2fdf063306715"
"30c1c434e1d7aa18dd38735615b2fdf063306715"
在不同版本的slave节点上执行evalsha:
3.0.7、3.2.13、4.0.14、5.0.14、6.0.15、6.2.13
./redis-cli -p 13307 evalsha 30c1c434e1d7aa18dd38735615b2fdf063306715 0
"hello lua lua"
./redis-cli -p 13313 evalsha 30c1c434e1d7aa18dd38735615b2fdf063306715 0
"hello lua lua"
./redis-cli -p 13414 evalsha 30c1c434e1d7aa18dd38735615b2fdf063306715 0
"hello lua lua"
./redis-cli -p 13514 evalsha 30c1c434e1d7aa18dd38735615b2fdf063306715 0
"hello lua lua"
./redis-cli -p 13615 evalsha 30c1c434e1d7aa18dd38735615b2fdf063306715 0
"hello lua lua"
./redis-cli -p 13623 evalsha 30c1c434e1d7aa18dd38735615b2fdf063306715 0
"hello lua lua"
7.0.11、7.2.7、7.4.2
./redis-cli -p 13711 evalsha 30c1c434e1d7aa18dd38735615b2fdf063306715 0
(error) NOSCRIPT No matching script. Please use EVAL.
./redis-cli -p 13727 evalsha 30c1c434e1d7aa18dd38735615b2fdf063306715 0
(error) NOSCRIPT No matching script. Please use EVAL.
./redis-cli -p 13742 evalsha 30c1c434e1d7aa18dd38735615b2fdf063306715 0
(error) NOSCRIPT No matching script. Please use EVAL.
结论:从节点上确实无法正确执行evalsha
3、更全的change
四、如何应对Redis 7.0?
2023~2024两年线上已经有上千个Redis 7.0集群,未发生这样的问题,还真得感谢业务同学代码写的代码相对规范,但也不可以掉以轻心,因为确实还有这样的.....Redis只有script sha,还能找到lua脚本吗?,也有这样的break change 升级Redis 7一个诡异问题定位
-
增量:管控lua脚本的使用(平台介入)、日常宣讲、Redis客户端测对only evalsha加以限制
-
存量:改造平台和相关中间件兼容Redis 7.0+、监控only evalsha进行整改等。
升级Redis 7.0是我碰到问题最多的一次,但是升级版本带来的收益也是显而易见的,所以历史前进的车轮不会停下的,希望早点用上Redis 8.0。