1. Lua脚本
1.1 简介
1.2 使用lua脚本好处
2. Redis客户端中执行Lua脚本
3. Redis执行lua脚本文件
3.1 编写Lua脚本文件
3.2 执行Lua脚本文件
4. Redis服务器中lua环境
4.1 创建并修改lua环境
4.2 lua伪客户端
4.3 lua_scripts字典
在redis分布式锁的那篇博客中我们介绍到,为了避免在分布式环境中释放别人的锁,释放锁时需要使用 GET + DEL 两条命令,而为了让着两条命令作为一个原子操作执行,我们可以使用Lua脚本来保证,接下来我们介绍Lua脚本
1. Lua脚本
1.1 简介
Lua 是一个小巧的脚本语言。是巴西里约热内卢天主教大学(Pontifical Catholic University of Rio de Janeiro)里的一个研究小组,由Roberto Ierusalimschy、Waldemar Celes 和 Luiz Henrique de Figueiredo所组成并于1993年开发。 其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。Lua由标准C编写而成,几乎在所有操作系统和平台上都可以编译,运行。Lua并没有提供强大的库,这是由它的定位决定的。所以Lua不适合作为开发独立应用程序的语言。Lua 有一个同时进行的JIT项目,提供在特定平台上的即时编译功能。 Lua脚本可以很容易的被C/C++ 代码调用,也可以反过来调用C/C++的函数,这使得Lua在应用程序中可以被广泛应用。不仅仅作为扩展脚本,也可以作为普通的配置文件,代替XML,ini等文件格式,并且更容易理解和维护。 Lua由标准C编写而成,代码简洁优美,几乎在所有操作系统和平台上都可以编译,运行。一个完整的Lua解释器不过200k,在目前所有脚本引擎中,Lua的速度是最快的。这一切都决定了Lua是作为嵌入式脚本的最佳选择。
1.2 使用lua脚本好处
(1)减少网络开销:可以将多个请求通过脚本的形式一次发送,减少网络时延和请求次数。
(2)原子性的操作:Redis会将整个脚本作为一个整体执行,中间不会被其他命令插入。因此在编写脚本的过程中无需担心会出现竞态条件,无需使用事务。
(3)代码复用:客户端发送的脚步会永久存在redis中,这样,其他客户端可以复用这一脚本来完成相同的逻辑。
(4)速度快:与其它语言的性能比较, 还有一个 JIT编译器可以显著地提高多数任务的性能; 对于那些仍然对性能不满意的人, 可以把关键部分使用C实现, 然后与其集成, 这样还可以享受其它方面的好处。
(5)可以移植:只要是有ANSI C 编译器的平台都可以编译,你可以看到它可以在几乎所有的平台上运行:从 Windows 到Linux,同样Mac平台也没问题, 再到移动平台、游戏主机,甚至浏览器也可以完美使用 (翻译成JavaScript).
(6)源码小巧:20000行C代码,可以编译进182K的可执行文件,加载快,运行快。
2. Redis客户端中执行Lua脚本
Redis中lua脚本命令的使用参考Redis常用命令这篇博客。Redis提供的lua脚本的命令包含如下几个,分别是:
(1)- EVAL
执行lua脚本。
(2)- SCRIPT LOAD
将脚本 script 添加到Redis服务器的脚本缓存中,并不立即执行这个脚本,而是会立即对输入的脚本进行求值。并返回给定脚本的 SHA1 校验和。如果给定的脚本已经在缓存里面了,那么不执行任何操作。
(3)- EVALSHA
考虑到我们通过eval执行lua脚本,脚本比较长的情况下,每次调用脚本都需要把整个脚本传给redis,比较占用带宽。为了解决这个问题,redis提供了EVALSHA命令允许开发者通过脚本内容的SHA1摘要来执行脚本。该命令的用法和EVAL一样,只不过是将脚本内容替换成脚本内容的SHA1摘要。
(4)- SCRIPT EXISTS
给定一个或多个脚本的 SHA1 校验和,返回一个包含 0 和 1 的列表,表示校验和所指定的脚本是否已经被保存在缓存当中
(5)- SCRIPT FLUSH
清除Redis服务端所有 Lua 脚本缓存
(6)- SCRIPT KILL
杀死当前正在运行的 Lua 脚本,当且仅当这个脚本没有执行过任何写操作时,这个命令才生效。 这个命令主要用于终止运行时间过长的脚本,比如一个因为 BUG 而发生无限 loop 的脚本,诸如此类。
下面简单介绍下EVAL命令的使用。Redis提供了EVAL命令可以使开发者像调用其他Redis内置命令一样调用脚本:
[EVAL] [脚本内容] [key参数的数量] [key …] [arg …]
可以通过key和arg这两个参数向脚本中传递数据,他们的值可以在脚本中分别使用KEYS和ARGV 这两个类型的全局变量访问。比如我们通过脚本实现一个set命令,通过在redis客户端中调用,那么执行的语句是:
eval "return ('set',KEYS[1],ARGV[1])" 1 hello world
EVAL命令是根据 key参数的数量-也就是上面例子中的1来将后面所有参数分别存入脚本中KEYS和ARGV两个表类型的全局变量。当脚本不需要任何参数时也不能省略这个参数。如果没有参数则为0:
eval "return (‘get’,’hello’)" 0
3. Redis执行lua脚本文件
上一节中介绍的命令,是在redis客户端中使用命令进行操作,接下来我们介绍的是直接执行 Lua 的脚本文件。
3.1 编写Lua脚本文件
-
local key = KEYS[1]
-
local val = ("GET", key);
-
-
if val == ARGV[1]
-
then
-
('SET', KEYS[1], ARGV[2])
-
return 1
-
else
-
return 0
-
end
3.2 执行Lua脚本文件
-
执行命令: redis-cli -a 密码 --eval Lua脚本路径 key [key …] , arg [arg …]
-
如:redis-cli -a 123456 --eval ./Redis_CompareAndSet.lua userName , zhangsan lisi
注意:"--eval"而不是命令模式中的"eval",一定要有前端的两个-,脚本路径后紧跟key [key …],相比命令行模式,少了numkeys这个key数量值。key [key …] 和 arg [arg …] 之间的“ , ”,英文逗号前后必须有空格,否则死活都报错。
-
## Redis客户端执行
-
127.0.0.1:6379> set userName zhangsan
-
OK
-
127.0.0.1:6379> get userName
-
"zhangsan"
-
-
## linux服务器执行
-
## 第一次执行:compareAndSet成功,返回1
-
## 第二次执行:compareAndSet失败,返回0
-
[root@vm01 learn_lua]# redis-cli -a 123456 --eval Redis_CompareAndSet.lua userName , zhangsan lisi
-
(integer) 1
-
[root@vm01 learn_lua]# redis-cli -a 123456 --eval Redis_CompareAndSet.lua userName , zhangsan lisi
-
(integer) 0
4. Redis服务器中lua环境
4.1 创建并修改lua环境
为了在redis服务器中执行Lua脚本,redis服务器内嵌了一个Lua环境,并对这个Lua环境进行了一系列修改,从而确保这个Lua环境可以满足服务器的需要。redis 服务器创建并修改Lua环境的整个过程由以下步骤组成:
(1)创建一个基础的Lua环境,之后的所有修改都是针对这个环境进行的
(2)载入多个函数库到Lua环境里面,让Lua脚本可以使用这些函数库进行数据操作
(3)创建全局表格redis,这个表格包含了对redis进行操作的函数,比如用于在Lua脚本中执行redis命令的函数
(4)使用redis自制的随机函数替换Lua原有的带有副作用的随机函数,从而避免在脚本中引入副作用
(5)创建排序辅助函数,Lua环境使用这个辅助函数来对一部分Redis命令的结果进行排序,从而消除这些命令的不确定性
(6)创建函数的错误报告辅助函数,这个函数可以提供更详细的出错信息
(7)对Lua环境中的全局环境进行保护,防止用户在执行lua脚本的过程中,将额外的全局变量添加到Lua环境中
(8)将完成修改的Lua环境保存到服务器状态的lua属性中,等待执行服务器传来的lua脚本。
4.2 lua伪客户端
因为执行redis命令必须要有相应的客户端状态,所以为了执行Lua脚本中包含的redis命令,redis服务器专门为lua环境创建了一个伪客户端,并由这个伪客户端负责处理Lua脚本中包含的所有redis命令。lua脚本使用或者函数执行一个redis命令,需要完成以下步骤:
(1)Lua环境将函数或者函数想要执行的命令传给伪客户端
(2)伪客户端将脚本想要执行的命令传给命令执行器
(3)命令执行器执行伪客户端传给它的命令,并将命令的执行结果返回给伪客户端
(4)伪客户端接收命令执行器返回的命令结果,并将这个结果返回给lua环境
(5)lua环境在接收到命令的结果后,将该结果返回给或者函数
(6)接收到结果的函数或者函数会将命令结果作为函数返回值返回给脚本中的调用者。
4.3 lua_scripts字典
除了伪客户端外,redis服务器伪lua环境创建了一个lua_scripts字典,这个字典的键为某个lua脚本的SHA1校验和,而字典的值则是SHA1校验和对应的lua脚本:
-
struct redisServer {
-
// ...
-
-
dict *lua_scripts;
-
-
// ...
-
};
redis服务器会将所有被EVAL命令执行过的Lua脚本,以及所有被SCRIPT LOAD命令载入过的Lua脚本都保存到这个字典中,这个字典有两个作用,一个时实现SCRIPT EXIST命令,另一个是实现脚本复制功能。
参考:/p/77484377