Lua 是动态类型语言,变量不要类型定义,只需要为变量赋值。 值可以存储在变量中,作为参数传递或结果返回。
Lua 中有 8 个基本类型分别为:nil、boolean、number、string、userdata、function、thread 和 table。
数据类型 | 描述 |
---|---|
nil | 这个最简单,只有值nil属于该类,表示一个无效值(在条件表达式中相当于false)。 |
boolean | 包含两个值:false和true。 |
number | 表示双精度类型的实浮点数 |
string | 字符串由一对双引号或单引号来表示。可以用 2 个方括号 [[ ]] "来表示"一块"字符串。字符串连接使用的是 .. |
function | 由 C 或 Lua 编写的函数 |
userdata | 表示任意存储在变量中的C数据结构 |
thread | 表示执行的独立线路,用于执行协同程序 |
table | Lua 中的表(table)其实是一个"关联数组"(associative arrays),数组的索引可以是数字、字符串或表类型。在 Lua 里,table 的创建是通过"构造表达式"来完成,最简单构造表达式是{} ,用来创建一个空表。 |
通过type()
方法可以获取到变量的类型,返回值是string类型的。
type(X)
print("a" .. 'b') --字符串连接
字符串或串(String)是由数字、字母、下划线组成的一串字符。
Lua 语言中字符串可以使用以下三种方式来表示:
单引号间的一串字符。
双引号间的一串字符。
[[
与 ]]
间的一串字符。
与其他语言一样,使用\
-- 字符串全部转为大写字母。
string.upper(argument)
-- 字符串全部转为小写字母。
string.lower(argument)
--在字符串中替换。。
----mainString 为要操作的字符串, findString 为被替换的字符,replaceString 要替换的字符,num 替换次数(可以忽略,则全部替换)
string.gsub(mainString,findString,replaceString,num)
--在一个指定的目标字符串中搜索指定的内容(第三个参数为索引),返回其具体位置。不存在则返回 nil。
string.find (str, substr, [init, [end]])
--字符串反转
string.reverse(arg)
--返回一个类似printf的格式化字符串
string.format(...)
--char 将整型数字转成字符并连接, byte 转换字符为整数值(可以指定某个字符,默认第一个字符)。
string.char(...) 和 string.byte(arg[,int])
--计算字符串长度。
string.len(arg)
--返回字符串string的n个拷贝(重复n次)
string.rep(string, n)
--链接两个字符串
..
--返回一个迭代器函数,每一次调用这个函数,返回一个在字符串 str 找到的下一个符合 pattern 描述的子串。如果参数 pattern 描述的字符串没有找到,迭代函数返回nil。
string.gmatch(str, pattern)
--只寻找源字串str中的第一个配对. 参数init可选, 指定搜寻过程的起点, 默认为1。
--在成功配对时, 函数将返回配对表达式中的所有捕获结果; 如果没有设置捕获标记, 则返回整个配对字符串. 当没有成功的配对时, 返回nil。
string.match(str, pattern, init)
--截取字符串。j:截取结束位置,默认为 -1,最后一个字符。
string.sub(s, i [, j])
变量在使用前,需要在代码中进行声明,即创建该变量。
Lua 变量有三种类型:全局变量、局部变量、表中的域。
Lua 中的变量全是全局变量,那怕是语句块或是函数里,除非用 local 显式声明为局部变量。(与JavaScript类似)
局部变量的作用域为从声明位置开始到所在语句块结束。
变量的默认值均为 nil
。
在Lua 语言中,全局变量无须声明即可使用,使用未经初始化的全局变量不会导致错误。当使用未经初始化的全局变量时,得到的结果为 nil。
当把nil赋值给全局变量时,Lua会回收该全局变量。
a = "hello" .. "world"
-- Lua 可以对多个变量同时赋值,变量列表和值列表的各个元素用逗号分开,赋值语句右边的值会依次赋给左边的变量。
a, b = 10, 2*x --> a=10; b=2*x
-- 遇到赋值语句Lua会先计算右边所有的值然后再执行赋值操作,所以我们可以这样进行交换变量的值:
x, y = y, x -- swap 'x' for 'y'
a[i], a[j] = a[j], a[i] -- swap 'a[i]' for 'a[j]'
当变量个数和值的个数不一致时,Lua会一直以变量个数为基础采取以下策略:
a. 变量个数 > 值的个数 按变量个数补足nil
b. 变量个数 < 值的个数 多余的值会被忽略
对 table 的索引使用方括号 []
。Lua 也提供了 .
操作。
t[i]
t.i -- 当索引为字符串类型时的一种简化写法
gettable_event(t,i) -- 采用索引访问本质上是一个类似这样的函数调用
--
while(condition)
do
statements
end
-- 数值循环:var 从 exp1 变化到 exp2,每次变化以 exp3 为步长递增 var,并执行一次 "执行体"。exp3 是可选的,如果不指定,默认为1。
for var=exp1,exp2,exp3 do
<执行体>
end
--泛型for 循环。i是数组索引值,v是对应索引的数组元素值。ipairs是Lua提供的一个迭代器函数,用来迭代数组。
a = {"one", "two", "three"}
for i, v in ipairs(a) do
print(i, v)
end
-- 类似其他语言 do ... while。
repeat
statements
until( condition )
--嵌套循环。前面几个循环的互相嵌套
while(condition)
do
while(condition)
do
statements
end
statements
end
if(布尔表达式)
then
--[ 在布尔表达式为 true 时执行的语句 --]
end
--------------------------
if(布尔表达式)
then
--[ 布尔表达式为 true 时执行该语句块 --]
else
--[ 布尔表达式为 false 时执行该语句块 --]
end
------------------------
if( 布尔表达式 1)
then
--[ 布尔表达式 1 为 true 时执行该语句块 --]
if(布尔表达式 2)
then
--[ 布尔表达式 2 为 true 时执行该语句块 --]
end
end
Lua认为false和nil为假,true 和非nil为真。要注意的是Lua中 0 为 true。
通过关键字 function
声明一个函数。return
语句返回结果。
optional_function_scope function function_name( argument1, argument2, argument3..., argumentn)
function_body
return result_params_comma_separated
end
说明:
optional_function_scope
: 该参数是可选的制定函数是全局函数还是局部函数,未设置该参数默认为全局函数,如果你需要设置函数为局部函数需要使用关键字 local
。function_name
: 指定函数名称。argument1, argument2, argument3..., argumentn
: 函数参数,多个参数以逗号隔开,函数也可以不带参数。function_body
: 函数体,函数中需要执行的代码语句块。result_params_comma_separated
: 函数返回值,Lua语言函数可以返回多个值,每个值以逗号隔开。function maximum (a)
local mi = 1 -- 最大值索引
local m = a[mi] -- 最大值
for i,val in ipairs(a) do
if val > m then
mi = i
m = val
end
end
return m, mi
end
print(maximum({8,10,23,12,5}))
Lua 函数可以接受可变数目的参数,和 C 语言类似,在函数参数列表中使用三点 … 表示函数有可变的参数。
function add(...)
local s = 0
for i, v in ipairs{...} do --> {...} 表示一个由所有变长参数构成的数组 。inpairs没有使用括号,应该是语法编译。
s = s + v
end
return s
end
print(add(3,4,5,6,7)) --->25
通常在遍历变长参数的时候只需要使用 {…}
,然而变长参数可能会包含一些 nil
,那么就可以用 select
函数来访问变长参数了:select('#', …)
或者 select(n, …)
select('#', …)
返回可变参数的长度。
select(n, …)
用于返回从起点 n 开始到结束位置的所有参数列表。调用 select 时,必须传入一个固定实参 selector(选择开关) 和一系列变长参数。如果 selector 为数字 n,那么 select 返回参数列表中从索引 n 开始到结束位置的所有参数列表,否则只能为字符串 #,这样 select 返回变长参数的总数。
设定 A 的值为10,B 的值为 20:
操作符 | 描述 | 实例 |
---|---|---|
+ | 加法 | A + B 输出结果 30 |
- | 减法 | A - B 输出结果 -10 |
* | 乘法 | A * B 输出结果 200 |
/ | 除法 | B / A 输出结果 2 |
% | 取余 | B % A 输出结果 0 |
^ |
乘幂 | A^2 输出结果 100 |
- | 负号 | -A 输出结果 -10 |
设定 A 的值为10,B 的值为 20:
操作符 | 描述 | 实例 |
---|---|---|
== | 等于,检测两个值是否相等,相等返回 true,否则返回 false | (A == B) 为 false。 |
~= | 不等于,检测两个值是否相等,不相等返回 true,否则返回 false | (A ~= B) 为 true。 |
> | 大于,如果左边的值大于右边的值,返回 true,否则返回 false | (A > B) 为 false。 |
< | 小于,如果左边的值大于右边的值,返回 false,否则返回 true | (A < B) 为 true。 |
>= | 大于等于,如果左边的值大于等于右边的值,返回 true,否则返回 false | (A >= B) 返回 false。 |
<= | 小于等于, 如果左边的值小于等于右边的值,返回 true,否则返回 false | (A <= B) 返回 true。 |
设定 A 的值为 true,B 的值为 false:
操作符 | 描述 | 实例 |
---|---|---|
and | 逻辑与操作符。 若 A 为 false,则返回 A,否则返回 B。 | (A and B) 为 false。 |
or | 逻辑或操作符。 若 A 为 true,则返回 A,否则返回 B。 | (A or B) 为 true。 |
not | 逻辑非操作符。与逻辑运算结果相反,如果条件为 true,逻辑非为 false。 | not(A and B) 为 true。 |
操作符 | 描述 | 实例 |
---|---|---|
.. |
连接两个字符串 | a…b ,其中 a 为 "Hello " , b 为 “World”, 输出结果为 “Hello World”。 |
# |
一元运算符,返回字符串或表的长度。 | #“Hello” 返回 5 |
从高到低的顺序:
^
not - (unary)
* / %
+ -
.. ####################
< > <= >= ~= ==
and
or
*除了 ^ 和 … 外所有的二元运算符都是左结合的。
a+i < b/2+1 --> (a+i) < ((b/2)+1)
5+x^2*8 --> 5+((x^2)*8)
a < y and y <= z --> (a < y) and (y <= z)
-x^2 --> -(x^2)
x^y^z --> x^(y^z) ::::右结合的:::,注意顺序
Lua 数组的索引键值可以使用整数表示,数组的大小不是固定的。
array = {"Lua", "Tutorial"}
在 Lua 索引值是以 1
为起始,但你也可以指定 0
开始。
还可以以负数为数组索引值。数组的索引,不代表第几个元素,而是类似map的key。
array = {}
for i= -2, 2 do
array[i] = i *2
end
for i = -2,2 do
print(array[i])
end
--OUTPUT:
-4
-2
0
2
4
就是类似于value是一个map的map。
-- 初始化数组
array = {}
for i=1,3 do
array[i] = {}
for j=1,3 do
array[i][j] = i*j
end
end
泛型 for 在自己内部保存迭代函数,实际上它保存三个值:迭代函数、状态常量、控制变量。
for k, v in pairs(t) do
print(k, v)
end
泛型 for 的执行过程:
在Lua中我们常常使用函数来描述迭代器,每次调用该函数就返回集合的下一个元素。Lua 的迭代器包含以下两种类型:
无状态的迭代器是指不保留任何状态的迭代器,因此在循环中我们可以利用无状态迭代器避免创建闭包花费额外的代价。
每一次迭代,迭代函数都是用两个变量(状态常量和控制变量)的值作为参数被调用,一个无状态的迭代器只利用这两个值可以获取下一个元素。
这种无状态迭代器的典型的简单的例子是 ipairs
,它遍历数组的每一个元素,元素的索引需要是数值。
--迭代函数
function square(iteratorMaxCount,currentNumber)
if currentNumber<iteratorMaxCount
then
currentNumber = currentNumber+1
return currentNumber, currentNumber*currentNumber
end
end
for i,n in square,3,0
do
print(i,n)
end
--OUTPUT:
1 1
2 4
3 9
迭代的状态包括被遍历的表(循环过程中不会改变的状态常量)和当前的索引下标(控制变量),ipairs 和迭代函数都很简单,我们在 Lua 中可以这样实现:
--迭代函数。参数a:是状态常量,i:控制变量
function iter (a, i)
i = i + 1
local v = a[i]
if v then
return i, v
end
end
function ipairs (a)
return iter, a, 0
end
当 Lua 调用 ipairs(a)
开始循环时,他获取三个值:迭代函数 iter、状态常量 a、控制变量初始值 0;然后 Lua 调用 iter(a,0) 返回 1
, a[1]
(除非 a[1]=nil);第二次迭代调用 iter(a,1) 返回 2, a[2]……直到第一个 nil 元素。
迭代器需要保存多个状态信息而不是简单的状态常量和控制变量,最简单的方法是使用闭包,还有一种方法就是将所有的状态信息封装到 table 内,将 table 作为迭代器的状态常量,因为这种情况下可以将所有的信息存放在 table 内,所以迭代函数通常不需要第二个参数。
array = {"Google", "Runoob"}
function elementIterator (collection)
local index = 0
local count = #collection --collectioin 长度
-- 闭包函数
return function ()
index = index + 1
if index <= count
then
-- 返回迭代器的当前元素
return collection[index]
end
end
end
for element in elementIterator(array) -- elementIterator(array) 调用的返回值(闭包) ,作为一个迭代函数。
do
print(element)
end
--OUTPUT:
Google
Runoob
table 是 Lua 的一种数据结构用来帮助我们创建不同的数据类型,如:数组、字典等。
Lua table 使用关联型数组,你可以用任意类型的值来作数组的索引,但这个值不能是 nil。
Lua table 是不固定大小的,你可以根据自己需要进行扩容。
Lua也是通过table来解决模块(module)、包(package)和对象(Object)的。 例如string.format表示使用"format"来索引table string。
-- 初始化表
mytable = {}
-- 指定值
mytable[1]= "Lua"
-- 移除引用
mytable = nil
-- lua 垃圾回收会释放内存
序号 | 方法 | 用途 |
---|---|---|
1 | table.concat (table [, sep [, start [, end]]]): | concat是concatenate(连锁, 连接)的缩写. table.concat()函数列出参数中指定table的数组部分从start位置到end位置的所有元素, 元素间以指定的分隔符(sep)隔开。 |
2 | table.insert (table, [pos,] value): | 在table的数组部分指定位置(pos)插入值为value的一个元素. pos参数可选, 默认为数组部分末尾. |
3 | table.maxn (table) | 指定table中所有正数key值中最大的key值. 如果不存在key值为正数的元素, 则返回0。(Lua5.2之后该方法已经不存在了,本文使用了自定义函数实现) |
4 | table.remove (table [, pos]) | 返回table数组部分位于pos位置的元素. 其后的元素会被前移. pos参数可选, 默认为table长度, 即从最后一个元素删起。 |
5 | table.sort (table [, comp]) | 对给定的table进行升序排序。 |
其他 Redis用不不涉及,就不整理了。
https://www.runoob.com/lua/lua-strings.html
从定义上来说, Redis 中的脚本本身就是一种事务, 所以任何在事务里可以完成的事, 在脚本里面也能完成。 并且一般来说, 使用脚本要来得更简单,并且速度更快。
使用事务时可能会遇上以下两种错误:
对于发生在 EXEC 执行之前的错误,客户端以前的做法是检查命令入队所得的返回值:如果命令入队时返回 QUEUED ,那么入队成功;否则,就是入队失败。如果有命令在入队时失败,那么大部分客户端都会停止并取消这个事务。
从 Redis 2.6.5 开始,服务器会对命令入队失败的情况进行记录,并在客户端调用 EXEC 命令时,拒绝执行并自动放弃这个事务。
经过测试lua中发生异常处理方式和redis 事务一致,可以说这两个东西是一样的,但是lua支持缓存,可以复用脚本,这个是原来的事务所没有的
EVAL script numkeys key [key …] arg [arg …]
script
参数是一段 Lua 5.1 脚本程序,它会被运行在 Redis 服务器上下文中,这段脚本不必(也不应该)定义为一个 Lua 函数。
numkeys
参数用于指定键名参数的个数。
键名参数 key [key ...]
从 EVAL
的第三个参数开始算起,表示在脚本中所用到的那些 Redis 键(key),这些键名参数可以在 Lua 中通过全局变量 KEYS
数组,用 1
为基址的形式访问( KEYS[1]
, KEYS[2]
,以此类推)。
在命令的最后,那些不是键名参数的附加参数 arg [arg ...]
,可以在 Lua
中通过全局变量 ARGV
数组访问,访问的形式和 KEYS
变量类似( ARGV[1]
、 ARGV[2]
,诸如此类)。
> eval "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 key1 key2 first second
1) "key1"
2) "key2"
3) "first"
4) "second"
EVAL 命令要求在每次执行脚本的时候都发送一次脚本主体(script body)。Redis 有一个内部的缓存机制,因此它不会每次都重新编译脚本,不过在很多场合,付出无谓的带宽来传送脚本主体并不是最佳选择。
为了减少带宽的消耗, Redis 实现了 EVALSHA
命令,它的作用和 EVAL 一样,都用于对脚本求值,但它接受的第一个参数不是脚本,而是脚本的 SHA1 校验和(sum)。
EVALSHA
命令的表现如下:
以下是示例:
> set foo bar
OK
> eval "return redis.call('get','foo')" 0
"bar"
> evalsha 6b1bf486c81ceb7edf3c093f4c48582e38c0e791 0
"bar"
> evalsha ffffffffffffffffffffffffffffffffffffffff 0
(error) `NOSCRIPT` No matching script. Please use [EVAL](/commands/eval).
客户端库的底层实现可以一直乐观地使用 EVALSHA 来代替 EVAL ,并期望着要使用的脚本已经保存在服务器上了,只有当 NOSCRIPT
错误发生时,才使用 EVAL 命令重新发送脚本,这样就可以最大限度地节省带宽。
这也说明了执行 EVAL 命令时,使用正确的格式来传递键名参数和附加参数的重要性:因为如果将参数硬写在脚本中,那么每次当参数改变的时候,都要重新发送脚本,即使脚本的主体并没有改变,相反,通过使用正确的格式来传递键名参数和附加参数,就可以在脚本主体不变的情况下,直接使用 EVALSHA 命令对脚本进行复用,免去了无谓的带宽消耗。
清除Redis服务端所有 Lua 脚本缓存
script flush
给定一个或多个脚本的 SHA1 校验和,返回一个包含 0或 1 的列表,表示校验和所指定的脚本是否已经被保存在缓存当中
SCRIPT EXISTS sha1 [sha1 …]
将脚本 script 添加到Redis服务器的脚本缓存中,并不立即执行这个脚本,而是会立即对输入的脚本进行求值。并返回给定脚本的 SHA1 校验和。如果给定的脚本已经在缓存里面了,那么不执行任何操作。
SCRIPT LOAD script
杀死当前正在运行的脚本
SCRIPT KILL
杀死当前正在运行的 Lua 脚本,当且仅当这个脚本没有执行过任何写操作时,这个命令才生效。 这个命令主要用于终止运行时间过长的脚本,比如一个因为 BUG 而发生无限 loop 的脚本,诸如此类。
假如当前正在运行的脚本已经执行过写操作,那么即使执行SCRIPT KILL
,也无法将它杀死,因为这是违反 Lua 脚本的原子性执行原则的。在这种情况下,唯一可行的办法是使用SHUTDOWN NOSAVE
命令,通过停止整个 Redis 进程来停止脚本的运行,并防止不完整(half-written)的信息被写入数据库中。
在 Lua 脚本中,可以使用两个不同函数来执行 Redis 命令,它们分别是:
redis.call()
redis.pcall()
这两个函数的唯一区别在于它们使用不同的方式处理执行命令所产生的错误。
redis.call()
和 redis.pcall()
两个函数的参数可以是任何格式良好(well formed)的 Redis 命令:
> eval "return redis.call('set','foo','bar')" 0
OK
上面这段脚本的确实现了将键 foo
的值设为 bar
的目的,但是,它违反了 EVAL 命令的语义,因为脚本里使用的所有键都应该由 KEYS
数组来传递,就像这样:
> eval "return redis.call('set',KEYS[1],'bar')" 1 foo
OK
要求使用正确的形式来传递键(key)是有原因的,因为不仅仅是 EVAL 这个命令,所有的 Redis 命令,在执行之前都会被分析,籍此来确定命令会对哪些键进行操作。
因此,对于 EVAL 命令来说,必须使用正确的形式来传递键,才能确保分析工作正确地执行。除此之外,使用正确的形式来传递键还有很多其他好处,它的一个特别重要的用途就是确保 Redis 集群可以将你的请求发送到正确的集群节点。(对 Redis 集群的工作还在进行当中,但是脚本功能被设计成可以与集群功能保持兼容。)不过,这条规矩并不是强制性的,从而使得用户有机会滥用(abuse) Redis 单实例配置(single instance configuration),代价是这样写出的脚本不能被 Redis 集群所兼容。
当 Lua 通过 call()
或 pcall()
函数执行 Redis 命令的时候,命令的返回值会被转换成 Lua 数据结构。同样地,当 Lua 脚本在 Redis 内置的解释器里运行时,Lua 脚本的返回值也会被转换成 Redis 协议(protocol),然后由 EVAL 将值返回给客户端。
数据类型之间的转换遵循这样一个设计原则:如果将一个 Redis 值转换成 Lua 值,之后再将转换所得的 Lua 值转换回 Redis 值,那么这个转换所得的 Redis 值应该和最初时的 Redis 值一样。
换句话说, Lua 类型和 Redis 类型之间存在着一一对应的转换关系。
以下列出的是详细的转换规则:
从 Redis 转换到 Lua :
Redis integer reply -> Lua number # Redis 整数转换成 Lua 数字
Redis bulk reply -> Lua string # Redis bulk 回复转换成 Lua 字符串
Redis multi bulk reply -> Lua table (may have other Redis data types nested) #Redis 多条 bulk 回复转换成 Lua 表,表内可能有其他别的 Redis 数据类型
Redis status reply -> Lua table with a single ok field containing the status #Redis 状态回复转换成 Lua 表,表内的 `ok` 域包含了状态信息
Redis error reply -> Lua table with a single err field containing the error #Redis 错误回复转换成 Lua 表,表内的 `err` 域包含了错误信息
Redis Nil bulk reply and Nil multi bulk reply -> Lua false boolean type # Redis 的 Nil 回复和 Nil 多条回复转换成 Lua 的布尔值 `false`
从 Lua 转换到 Redis:
Lua number -> Redis integer reply # Lua 数字转换成 Redis 整数
Lua string -> Redis bulk reply # Lua 字符串转换成 Redis bulk 回复
Lua table (array) -> Redis multi bulk reply # Lua 表(数组)转换成 Redis 多条 bulk 回复
Lua table with a single ok field -> Redis status reply # 一个带单个 `ok` 域的 Lua 表,转换成 Redis 状态回复
Lua table with a single err field -> Redis error reply # 一个带单个 `err` 域的 Lua 表,转换成 Redis 错误回复
Lua boolean false -> Redis Nil bulk reply # Lua 的布尔值 `false` 转换成 Redis 的 Nil bulk 回复
从 Lua 转换到 Redis 有一条额外的规则,这条规则没有和它对应的从 Redis 转换到 Lua 的规则:
Lua boolean true -> Redis integer reply with value of 1 # Lua 布尔值 `true` 转换成 Redis 整数回复中的 `1`
示例:
#将 Lua 值转换成 Redis 值
> eval "return 10" 0
(integer) 10
> eval "return {1,2,{3,'Hello World!'}}" 0
1) (integer) 1
2) (integer) 2
3) 1) (integer) 3
2) "Hello World!"
#将 Redis 值转换成 Lua 值,然后再将 Lua 值转换成 Redis 值的类型转过程。
> eval "return redis.call('get','foo')" 0
"bar"
redis.call()
和 redis.pcall()
的唯一区别在于它们对错误处理的不同。
当 redis.call()
在执行命令的过程中发生错误时,脚本会停止执行,并返回一个脚本错误,错误的输出信息会说明错误造成的原因:
redis> lpush foo a
(integer) 1
redis> eval "return redis.call('get', 'foo')" 0
(error) ERR Error running script (call to f_282297a0228f48cd3fc6a55de6316f31422f5d17): ERR Operation against a key holding the wrong kind of value
和 redis.call()
不同, redis.pcall()
出错时并不引发(raise)错误,而是返回一个带 err
域的 Lua 表(table),用于表示错误:
redis 127.0.0.1:6379> EVAL "return redis.pcall('get', 'foo')" 0
(error) ERR Operation against a key holding the wrong kind of value
在编写脚本方面,一个重要的要求就是,脚本应该被写成纯函数(pure function)。
也就是说,脚本应该具有以下属性:
类似于幂等操作
为了确保脚本符合上面所说的属性, Redis 做了以下工作:
redis.call("smembers", KEYS[1])
,那么返回的总是排过序的元素。math.random
和 math.randomseed
进行修改,使得每次在运行新脚本的时候,总是拥有同样的 seed 值。这意味着,每次运行脚本时,只要不使用 math.randomseed
,那么 math.random
产生的随机数序列总是相同的。尽管有那么多的限制,但用户还是可以用一个简单的技巧写出带随机行为的脚本(如果他们需要的话)。
假设现在我们要编写一个 Redis 脚本,这个脚本从列表中弹出 N 个随机数。一个 Ruby 写的例子如下:
require 'rubygems'
require 'redis'
r = Redis.new
RandomPushScript = < 0) do
res = redis.call('lpush',KEYS[1],math.random())
i = i-1
end
return res
EOF
r.del(:mylist)
puts r.eval(RandomPushScript,[:mylist],[10,rand(2**32)])
这个程序每次运行都会生成带有以下元素的列表:
> lrange mylist 0 -1
1) "0.74509509873814"
2) "0.87390407681181"
3) "0.36876626981831"
4) "0.6921941534114"
5) "0.7857992587545"
6) "0.57730350670279"
7) "0.87046522734243"
8) "0.09637165539729"
9) "0.74990198051087"
10) "0.17082803611217"
上面的 Ruby 程序每次都只生成同样的列表,用途并不是太大。那么,该怎样修改这个脚本,使得它仍然是一个纯函数(符合 Redis 的要求),但是每次调用都可以产生不同的随机元素呢?
一个简单的办法是,为脚本添加一个额外的参数,让这个参数作为 Lua 的随机数生成器的 seed 值,这样的话,只要给脚本传入不同的 seed ,脚本就会生成不同的列表元素。
以下是修改后的脚本:
RandomPushScript = < 0) do
res = redis.call('lpush',KEYS[1],math.random())
i = i-1
end
return res
EOF
r.del(:mylist)
puts r.eval(RandomPushScript,1,:mylist,10,rand(2**32))
尽管对于同样的 seed ,上面的脚本产生的列表元素是一样的(因为它是一个纯函数),但是只要每次在执行脚本的时候传入不同的 seed ,我们就可以得到带有不同随机元素的列表。
Seed 会在复制(replication link)和写 AOF 文件时作为一个参数来传播,保证在载入 AOF 文件或附属节点(slave)处理脚本时, seed 仍然可以及时得到更新。
注意,Redis 实现保证 math.random
和 math.randomseed
的输出和运行 Redis 的系统架构无关,无论是 32 位还是 64 位系统,无论是小端(little endian)还是大端(big endian)系统,这两个函数的输出总是相同的。
为了防止不必要的数据泄漏进 Lua 环境, Redis 脚本不允许创建全局变量。如果一个脚本需要在多次执行之间维持某种状态,它应该使用 Redis key 来进行状态保存。
企图在脚本中访问一个全局变量(不论这个变量是否存在)将引起脚本停止, EVAL 命令会返回一个错误:
redis 127.0.0.1:6379> eval 'a=10' 0
(error) ERR Error running script (call to f_933044db579a2f8fd45d8065f04a8d0249383e57): user_script:1: Script attempted to create global variable 'a'
Lua 的 debug 工具,或者其他设施,比如打印(alter)用于实现全局保护的 meta table ,都可以用于实现全局变量保护。
实现全局变量保护并不难,不过有时候还是会不小心而为之。一旦用户在脚本中混入了 Lua 全局状态,那么 AOF 持久化和复制(replication)都会无法保证,所以,请不要使用全局变量。
避免引入全局变量的一个诀窍是:将脚本中用到的所有变量都使用 local
关键字定义为局部变量。
在 Lua 脚本中,可以通过调用 redis.log
函数来写 Redis 日志(log):
redis.log(loglevel, message)
其中, message
参数是一个字符串,而 loglevel
参数可以是以下任意一个值:
redis.LOG_DEBUG
redis.LOG_VERBOSE
redis.LOG_NOTICE
redis.LOG_WARNING
上面的这些等级(level)和标准 Redis 日志的等级相对应。
对于脚本散发(emit)的日志,只有那些和当前 Redis 实例所设置的日志等级相同或更高级的日志才会被散发。
以下是一个日志示例:
redis.log(redis.LOG_WARNING, "Something is wrong with this script.")
执行上面的函数会产生这样的信息:
[32343] 22 Mar 15:21:39 # Something is wrong with this script.
脚本应该仅仅用于传递参数和对 Redis 数据进行处理,它不应该尝试去访问外部系统(比如文件系统),或者执行任何系统调用。
除此之外,脚本还有一个最大执行时间限制,它的默认值是 5 秒钟,一般正常运作的脚本通常可以在几分之几毫秒之内完成,花不了那么多时间,这个限制主要是为了防止因编程错误而造成的无限循环而设置的。
最大执行时间的长短由 lua-time-limit
选项来控制(以毫秒为单位),可以通过编辑 redis.conf
文件或者使用 CONFIG GET parameter 和 CONFIG SET parameter value 命令来修改它。
当一个脚本达到最大执行时间的时候,它并不会自动被 Redis 结束,因为 Redis 必须保证脚本执行的原子性,而中途停止脚本的运行意味着可能会留下未处理完的数据在数据集(data set)里面。
因此,当脚本运行的时间超过最大执行时间后,以下动作会被执行:
SCRIPT KILL
和 SHUTDOWN NOSAVE
两个命令会被处理,对于其他命令请求, Redis 服务器只是简单地返回 BUSY
错误。SCRIPT KILL
命令将一个仅执行只读命令的脚本杀死,因为只读命令并不修改数据,因此杀死这个脚本并不破坏数据的完整性SHUTDOWN NOSAVE
,它通过停止服务器来阻止当前数据集写入磁盘在流水线请求的上下文中使用 EVALSHA 命令时,要特别小心,因为在流水线中,必须保证命令的执行顺序。
一旦在流水线中因为 EVALSHA 命令而发生 NOSCRIPT 错误,那么这个流水线就再也没有办法重新执行了,否则的话,命令的执行顺序就会被打乱。
为了防止出现以上所说的问题,客户端库实现应该实施以下的其中一项措施:
# --eval 后跟文件路径
redis-cli -a 123456 --eval ./Redis_CompareAndSet.lua userName , zhangsan lisi
--eval
而不是命令模式中的"eval",一定要有前端的--
,脚本路径后紧跟key [key …],相比命令行模式,少了numkeys
这个key数量值。key [key …] 和 arg [arg …] 之间的,
,英文逗号前后必须有空格,否则死活都报错
http://redisdoc.com/script/eval.html