Redis从2.6版本开始引入对Lua脚本的支持,通过在服务器中嵌入Lua环境,Redis客户端可以使用Lua脚本,直接在服务器端原子地执行多个Redis命令。其中使用EVAL命令可以直接对输入的脚本进行求值:
127.0.0.1:6379> EVAL "return 'hello world'" 0
"hello world"
而使用EVALSHA命令则可以根据脚本的SHA1校验和来对脚本进行求值,但这个命令要求校验和对应的脚本必须至少被EVAL命令执行过
一次:
127.0.0.1:6379> EVAL "return 1+1" 0
(integer) 2
127.0.0.1:6379> EVALSHA "a27e7e8a43702b7046d4f6a7ccf5b60cef6b9bd9" 0 // 上一个脚本的校验和
(integer) 2
或者这个校验和对应的脚本曾经被SCRIPT LOAD命令载入过:
127.0.0.1:6379> SCRIPT Load "return 2*2"
"4475bfb5919b5ad16424cb50f74d4724ae833e72"
127.0.0.1:6379> EVALSHA "4475bfb5919b5ad16424cb50f74d4724ae833e72" 0
(integer) 4
为了在Redis服务器中执行Lua脚本,Redis在服务器内嵌了一个Lua环境(evnironment),并对这个Lua环境进行了一系列修改,从而确保这个Lua环境可以满足Redis服务器的需要。Redis服务器创建并修改Lua环境的整个过程由以下步骤组成:
服务器首先调用Lua的C API函数lua_open,创建一个新的Lua环境。因为lua_open函数创建的只是一个基本的Lua环境,为了让这个Lua环境可以满足Redis的操作要求,接下来服务器将对这个Lua环境进行一系列修改。
Redis修改Lua环境的第一步,就是将以下函数库载入到Lua环境里面:
服务器将在Lua环境中创建一个redis表格(table),并将它设置为全局变量,这个redis表格包含以下函数:
127.0.0.1:6379> EVAL "return redis.call('ping')" 0
PONG
为了保证相同的脚本可以在不同的机器上产生相同的结果,Redis要求所有传入服务器的Lua脚本,以及Lua环境中的所有函数,都必须是无副作用(side effect)的纯函数(pure function).但是,在之前载入Lua环境的match函数库中,用于生成随机数的math.random函数和math.randomseed函数都是带有副作用的,它们不符合Redis对Lua环境的无副作用要求。因为这个原因,Redis使用自制的函数替换了math库中原有的math.random函数和math.randomseed函数,替换之后的两个函数有以下特征:
--random-with-default-seed.lua
local i = 10
local seq ={}
while (i > 0) do
seq[i] = math.random(i)
i = i+1
end
return seq
无论执行这个脚本多少次产生的值都是相同的
E:\redis>redis-cli --eval test.lua
1) (integer) 1
2) (integer) 2
3) (integer) 2
4) (integer) 3
5) (integer) 4
6) (integer) 4
7) (integer) 7
8) (integer) 1
9) (integer) 7
10) (integer) 2
但是如果我们在另一个脚本里面调用math.randomseed将seed修改为10086
--random-with-default-seed.lua
math.randomseed(10086) -- change seed
local i = 10
local seq ={}
while (i > 0) do
seq[i] = math.random(i)
i = i-1
end
return seq
那么这个脚本生成的随机数绪列将和使用默认seed值0时生成的随机绪列不同:
E:\redis>redis-cli --eval test1.lua
1) (integer) 1
2) (integer) 1
3) (integer) 2
4) (integer) 1
5) (integer) 1
6) (integer) 3
7) (integer) 1
8) (integer) 1
9) (integer) 3
10) (integer) 1