Redis中使用Lua脚本
什么是Lua
Lua
是一种轻量小巧的脚本语言,用标准C语言编写并以源代码形式开放。其设计目的就是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。从 Redis2.6
开始, Eval
命令使用内置的Lua
解释器执行脚本 ,不需要单独安装 Lua
为什么使用Lua
在使用redis
的过程中,我们会发现有些时候需要原子性去操作redis
命令,而为了保证多条命令组合的原子性, Redis
提供了简单的事务功能以及集成Lua
脚本来解决这个问题。其中redis
事务是基于乐观锁,lua
脚本是基于redis
的单线程执行命令。
好处
- 减少网络开销,将多个请求通过脚本的形式一次发送,减少网络时延
- 原子操作,
Redis
会将整个脚本作为一个整体执行,中间不会被其他请求插入。因此在脚本运行过程中无需担心会出现竞态条件,无需使用事务
- 复用,客户端发送的脚本会永久存在
Redis
中,这样其他客户端可以复用这一脚本,而不需要使用代码完成相同的逻辑
常用命令
1.EVAL
命令格式——EVAL script numkeys key [key …] arg [arg …]
- script
:用到的Lua
脚本,不必定义为一个Lua
函数
- numkeys
:指定key [key …]中key的个数,也就是指定的Lua
脚本需
要处理键的数量
- key [key …]
: 传递给Lua
脚本零到多个键,通过KEYS[INDEX]
获取
- arg [arg …]
: 附加参数,通过ARGV[INDEX]
获取
1 | // eg1:numkeys=1,keys数组只有1个元素key1 |
1 | // eg2:numkeys=0,arg数组元素中有1个元素value1 |
其中在 Lua
脚本中,可以使用两个不同函数来执行 Redis
命令,它们分别是: redis.call()
和 redis.pcall()
2.SCRIPT LOAD - SCRIPT EXISTS
- SCRIPT LOAD命令格式——
SCRIPT LOAD script
-SCRIPT LOAD 将脚本 script
添加到Redis
服务器的脚本缓存中,并不立即执行这个脚本,而是会立即对输入的脚本进行求值。并返回给定脚本的 SHA
校验和。
-下文中EVALSHA
命令中的sha1
参数,就是SCRIPT LOAD
命令执行的结果。
-如果给定的脚本已经在缓存里面了,那么不执行任何操作。
1 | //SCRIPT LOAD加载脚本,得到sha1值 |
- SCRIPT EXISTS命令格式——
SCRIPT EXISTS sha1 [sha1 …]
-给定一个或多个脚本的SHA
校验和,返回一个包含 0 和 1 的列表,表示校验和所指定的脚本是否存在在缓存中
1 | 127.0.0.1:6379> SCRIPT EXISTS 05f21e1ea87d154c7433c59641ef1383d6ab47c9 |
3.EVALSHA
命令格式——EVALSHA sha1 numkeys key [key …] arg [arg …]
-在脚本被加入到缓存之后,通过EVALSHA
命令,可以使用脚本的 SHA
校验和来调用这个脚本。
-脚本可以在缓存中保留无限长的时间,直到执行SCRIPT FLUSH
为止。
1 | 127.0.0.1:6379> EVALSHA 05f21e1ea87d154c7433c59641ef1383d6ab47c9 1 key1 value1 |
4.SCRIPT FLUSH
命令格式——SCRIPT FLUSH
-用于清除Redis
服务端所有 Lua
脚本缓存
1 | 127.0.0.1:6379> SCRIPT FLUSH |
5.SCRIPT KILL
命令格式——SCRIPT FLUSH
-杀死当前正在运行的 Lua
脚本
-仅当这个脚本没有执行过任何写操作时,命令才生效
-主要用于终止运行时间过长的脚本,比如一个因为 BUG 而发生无限 loop 的脚本
注意事项
lua
脚本中的redis
操作的key最好都是通过keys
来传递,而不要写死,否则在redis
cluster
的情况下可能有问题- 我们无法清除某一个脚本的缓存,只可以清除所有的缓存,一般情况下没有必要清除,因为即使有大量的脚本也不会太占用内存
script kill
命令只可以杀死正在运行的只读脚本,修改了数据的脚本只能使用shutdown nosave
命令杀死- 脚本执行的默认超时时间为
5分钟
,可以通过redis.conf
配置文件的lua-time-limit
配置项修改 - 脚本即使到达了超时时间,也不会停止执行,因为这违反了
lua
脚本的原子性
简单编写Lua脚本文件
当 Lua
脚本存在较多逻辑的时候,就很有必要单独编写一个独立的Lua
文件,下面我们来做一个尝试:
1 | redis.call('set',KEYS[1],ARGV[1]) |
将它取名并保存起来,接下来就可以在linux
操作系统上执行下面的命令进行测试:
1 | redis-cli --eval <文件名> key1 key2 , <value1> <value2> |
SpringBoot集成Redis调用Lua脚本
其中对Redis
的操作,主要用到了SpringBoot
里的RedisTemplate
模板,在完成所需脚本的编写后,添加到SpringBoot
项目的resources目录下,目录结构为:src/resources/xxx.lua
。RedisTemplate
提供了两种方法执行Lua
脚本:
1 | //script-脚本资源,keys-列表 |
RedisScript
参数决定了Lua
脚本资源的同时,也决定了返回值类型,我们在开发中,默认使用的是DefaultRedisScript
实现对象。接下来看一下实现的方式,不提供序列化方法:
1 | public void execLuaWithoutSerializer(){ |
在调用时传入了一个List,存储两个Key值,需要注意调用时设置的ResultType
必须与脚本中的返回类一致。
关于更具体的使用,可能会在下次博客中,跟处理lua
限流操作中遇到的一些问题放在一起讲。