使用Lua脚本进行Redis查询

标签:无 1571人阅读 评论(0)

使用Lua脚本的好处


通过内嵌对Lua环境的支持,Redis解决了长久以来不能高效地处理CAS(check-and-set)命令的缺点,并且可以通过组合使用多个命令,实现以前很难实现或者不能高效实现的模式。

比如说在 Redis 中要先获一个值,然后根据这个值再去 Redis 中获得另一个相关联的值,如果不使用 Lua 脚本就会有两次与 Redis 交互,引入 Lua 脚本可以只用一次操作。

1、减少网络开销:可以将多个请求通过脚本的形式一次发送,减少网络时延和请求次数。

2、原子性的操作:Redis会将整个脚本作为一个整体执行,中间不会被其他命令插入。因此在编写脚本的过程中无需担心会出现竞态条件,无需使用事务。

3、代码复用:客户端发送的脚步会永久存在redis中,这样,其他客户端可以复用这一脚本来完成相同的逻辑。

Redis中使用Lua脚本的好处


Lua语法上需要注意的点


Lua 是一个非常轻量级,强大,高效,可内嵌的脚本语言。


  • 弱数据类型

  • 局部变量由关键字local定义,在函数内有效;没有local就是全局变量,在整个程序中有效

  • 注释符号 ——

  • 空值 nil

  • 不等于 ~=


关系运算符


与 and

或 or

非 not

连接 ..


如果第一个表达式为true,第二个表达式的运算结果是一个非布尔型的值,则输出这个值

>>print(26<27 and 200+12)

212


如果第一个表达式为false,第二个表达式的运算结果是一个非布尔型的值,则输出这个值

>>print(2>3 or 13)

13


用于连接两个字符串

>>print(“abc”..”def”)

abcdef


return 可以返回任意多个值


条件语句


if 表达式 then

语句块 

end


if 表达式 then

语句块1

else

语句块2

end


if 表达式1 then

语句块1

else if 表达式2 then

语句块2

……

else

语句块n

end


循环语句


while 循环条件 do

循环体

end


repeat

循环体

until 循环条件


for 变量=初始值,终止值,步长 do

循环体

end


for i = 3, 1, -1 do

print(i)

end

3

2

1


初始值,终止值,步长只在循环开始的时候运行一次。

在循环体中改变值并不能改变循环。



array = {}

Lua 索引值是以 1 为起始,可以指定 0 开始。可以以负数为数组索引值。


在Lua中,表(table)是十分重要的一种数据结构,通过表可以实现需要的大部分重要的数据结构,比如数组。

table类型实现了关联数组,关联数组是一种具有特殊索引方式的数组;不仅可以通过整数来索引它,还可以使用字符串或其它类型的值(除了nil)来索引它。此外,table没有固定的大小,可以动态得添加任意数量的元素到一个table中。


可以使用“#”号来获取数组的长度。


table.remove (table [, pos])

返回table数组部分位于pos位置的元素. 其后的元素会被前移. pos参数可选, 默认为table长度, 即从最后一个元素删起。



EVAL命令执行Lua脚本


  • 查看Lua版本

eval "return _VERSION" 0 


  • 执行Lua脚本

eval script numKeys key [key ...] arg [arg …]


script 为 Lua 脚本,numKeys 指明后面有多少个 KEY,都没有就写成 0,KEYS 对应于 Redis 的 key,而 ARGV 可以为其他任何意义的参数。KEYS 和 ARGV 数组(Lua 中叫做 Table)下标是基于 1 开始,Lua 拼接字符串是用 .. 双点,{} 表示数组。


使用 redis.call() 来直接操作 Redis 的命令,要用到的参数可由变量KEYS 或 ARGV 来提供

eval "local key = redis.call('get', KEYS[1]); return redis.call('get', key)" 1 key1

Redis在已知 key1 的情况下辗转由 key1 -> key2 -> hello 获得最终想要的值。


Lua 中可以使用任何 Redis 命令,只要注意变量类型在 Lua 与 Redis 之间的传递与匹配。



Lua 脚本的多种执行方式


Redis 中的 Lua 脚本是会预编译的,如果多次用 eval 命令执行的是相同的脚本,只会在第一次执行前编译好,其后相同的脚本直接执行编译好的代码。针对相同的脚本产生了相同的 hash


script load 来加载相同内容的脚本,返回一样的 hash 值。


频繁的 eval 相同的 Lua 代码块会增加一些网络传输量,特别是对于一大段较复杂的 Lua 脚本,这时候就最好显式的用 SCRIPT LOAD 加载,并用 EVALSHA 来调用它。


加载的脚本会用一个 hash 与其关联,以后使用时就用 EVALSHA 命令指定 hash 值来调用,KEY 或 ARGV 的传入方式是完全一样的。


127.0.0.1:6379> script load "return {KEYS[1], ARGV[1]}"

"d006f1a90249474274c76f5be725b8f5804a346b"

127.0.0.1:6379> evalsha d006f1a90249474274c76f5be725b8f5804a346b 1 key1 arg1

1) "key1"

2) “arg1"


  • 清除所有的脚本

script flush

  • 确认脚本是否存在

script exists 4a2267357833227dd98abdedb8cf24b15a986445



加载 Lua 脚本文件


如果 Lua 脚本比较复杂,最好是写在一个单独的 lua 脚本文件, 比如 lua 脚本文件名是 test.lua


$ redis-cli eval "$(cat test.lua)" 1 key1

"hello"

$ redis-cli script load  "$(cat test.lua)"

"f109a1ddf4c6dd8727a080910625642bdae3f00d"

$ redis-cli evalsha f109a1ddf4c6dd8727a080910625642bdae3f00d 1 key1

“hello"


为了防止某个脚本执行时间过长导致Redis无法提供服务(比如陷入死循环),Redis提供了lua-time-limit参数限制脚本的最长运行时间,默认为5秒钟(redis.conf配置文件中lua-time-limit 配置),如果 5 秒中未能返回则会告诉客户端 Redis is busy running a script, 由客户端来决定用 script kill 来杀掉该 busy 的脚本。


Java Jedis 对 Lua 的支持


Jedis jedis = new Jedis("localhost");

Object value = jedis.eval("return redis.call('mget', unpack(KEYS))", 4, "k1", "k3", "k2", "k4");

System.out.println(value);


Lua 的 unpack 函数能够进行如下转换

Java 的 Varargs -> Lua Array -> Redis 的 Varargs


Redis 集群中分 primary 和 replica 节点类型,replica 是只读的。replica 上加载的脚本不会同步到其他节点上,而在 primary 上加载的脚本可以任意节点上使用相同的 hash 执行。




参考资料:

https://www.runoob.com/lua/lua-tutorial.html

https://yanbin.blog/redis-builtin-lua-script/

https://redisbook.readthedocs.io/en/latest/feature/scripting.html

https://www.redisgreen.net/blog/intro-to-lua-for-redis-programmers/

https://redis.io/topics/ldb

https://www.compose.com/articles/a-quick-guide-to-redis-lua-scripting/

https://www.cnblogs.com/PatrickLiu/p/8391829.html

查看评论

暂无评论

发表评论
  • 评论内容:
      
首页
团队介绍
发展历史
组织结构
MESA大事记
新闻中心
通知
组内动态
科研成果
专利
论文
项目
获奖
软著
人才培养
MESA毕业生
MESA在读生
MESA员工
招贤纳士
走进MESA
学长分享
招聘通知
招生宣传
知识库
文章
地址:北京市朝阳区华严北里甲22号楼五层 | 邮编:100029
邮箱:nelist@iie.ac.cn
京ICP备15019404号-1