Available since 2.6.0. 加入版本2.6
Time complexity: Depends on the script that is executed. 时间复杂度: 取决于脚本的执行
EVAL和EVALSHA是从Redis2.6.0版本使用内置脚本解释器引入的。
The first argument of EVAL is
a Lua 5.1 script. The script does not need to define a Lua function (and should not). It is just a Lua program that will run in the context of the Redis server.
EVAL的第一个参数是一个lua.5.1的脚本.这段脚本不需要定义lua方法函数(也不应该定义)。仅仅是一个运行在Redis 服务器的一段lua程序。
The second argument of EVAL is
the number of arguments that follows the script (starting from the third argument) that represent Redis key names. This arguments can be accessed by Lua using the KEYS global
variable in the form of a one-based array (so KEYS[1]
, KEYS[2]
,
...).
EVAL的第二个参数是一个数字,它表示紧跟着的脚本(第三个参数开始)中前多少个是Redis中的key的名称。这些Redis中的key的名称可以使用lua的数组 KEYS取出value(比如 KEYS[1],KEUS[2],...)。
All the additional arguments should not represent key names and can be accessed by Lua using the ARGV
global
variable, very similarly to what happens with keys (soARGV[1]
, ARGV[2]
, ...).
附加参数不代表key的名称并且和keys一样可以使用lua的ARGV全局变量数组访问(比如:ARGV[1],ARGV[2])。
The following example should clarify what stated above:
下面的例子应该可以说明上面的规定:
> eval "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 key1 key2 first second
1) "key1"
2) "key2"
3) "first"
4) "second"
Note: as you can see Lua arrays are returned as Redis multi bulk replies, that is a Redis return type that your client library
will likely convert into an Array type in your programming language.
注意:正如你所见,lua数组的返回是Redis的多重应答,这是redis的一个返回类型,你的客户端可能会转换成所用程序语言的数组。
It is possible to call Redis commands from a Lua script using two different Lua functions:
使用两个不同的lua函数去调用Redis命令:
redis.call()
redis.pcall()
redis.call()
is
similar to redis.pcall()
, the only difference is that if
a Redis command call will result in an error, redis.call()
will
raise a Lua error that in turn will force EVAL to
return an error to the command caller, while redis.pcall
will
trap the error and return a Lua table representing the error.redis.call()
and redis.pcall()
functions
are all the arguments of a well formed Redis command:> eval "return redis.call(‘set‘,‘foo‘,‘bar‘)" 0
OK
All Redis commands must be analyzed before execution to determine which keys the command will operate on. In order for this to be true for EVAL,
keys must be passed explicitly. This is useful in many ways, but especially to make sure Redis Cluster can forward your request to the appropriate cluster node (Redis Cluster is a work in progress, but the scripting feature was designed in order to play well
with it).ok
field containing the statuserr
field containing the errorok
field -> Redis status replyerr
field -> Redis error reply> 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!"
> eval "return redis.call(‘get‘,‘foo‘)" 0
"bar"
The last example shows how it is possible to receive the exact return value ofredis.call()
or redis.pcall()
from
Lua that would be returned if the command was called directly.> eval "return {1,2,3.3333,‘foo‘,nil,‘bar‘}" 0
1) (integer) 1
2) (integer) 2
3) (integer) 3
4) "foo"
As you can see 3.333 is converted into 3, and the bar string
is never returned as there is a nil before.redis.error_reply(error_string)
returns an error reply. This function simply returns the single field table with the err
field
set to the specified string for you.redis.status_reply(status_string)
returns a status reply. This function simply returns the single field table with the ok
field
set to the specified string for you.redis.call()
resulting in a Redis command error
will stop the execution of the script and return an error, in a way that makes it obvious that the error was generated by a script:> del foo
(integer) 1
> lpush foo a
(integer) 1
> eval "return redis.call(‘get‘,‘foo‘)" 0
(error) ERR Error running script (call to f_6b1bf486c81ceb7edf3c093f4c48582e3
Using redis.pcall()
no error is raised, but an error object is returned in the format specified above (as a Lua
table with an err
field). The script can pass the exact error to the user by returning the error object returned
by redis.pcall()
.redis.conf
would
be a problem for a few reasons:Different instances may have different implementations of a command.
Deployment is hard if we have to make sure all instances contain a given command, especially in a distributed environment.
Reading application code, the complete semantics might not be clear since the application calls commands defined server side.
If the server still remembers a script with a matching SHA1 digest, the script is executed.
如果服务器记得这个脚本的SHA1摘要,脚本就会被执行。
If the server does not remember a script with this SHA1 digest, a special error is returned telling the client to use EVAL instead.
> 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).
The client library implementation can always optimistically send EVALSHA under
the hood even when the client actually calls EVAL, in the hope the script was already seen by the server. If the NOSCRIPT
error
is returned EVAL will be used insteaNOSCRIPT
的错误在使用EVAL命令。runid
field in the INFO command
in order to make sure the server was not restarted and is still the same process.SCRIPT FLUSH. This command is the only way to force Redis to flush the scripts cache. It is most useful in a cloud environment where the same instance can be reassigned to a different user. It is also useful for testing client libraries‘ implementations of the scripting feature.
SRIPUT FLUSH。这个命令时使Redis清空脚本缓存的唯一方式。在云环境中当同一个实例被重新指定给不同的用户时这个命令是最有用的。在客户端测试脚本的实现时也非常有用。
SCRIPT EXISTS sha1 sha2... shaN. Given a list of SHA1 digests as arguments this command returns an array of 1 or 0, where 1 means the specific SHA1 is recognized as a script already present in the scripting cache, while 0 means that a script with this SHA1 was never seen before (or at least never seen after the latest SCRIPT FLUSH command).
SCRIPT EXISTS sha1 sha2...shaN。命令的参数是SHA1摘要的列表,它将返回一个由0或者1组成的数组,1表示对应的脚本已经被服务器认可放入脚本缓存了,0表对应的脚本服务器一直都没有见过(或者至少在最后一次执行SCRIPT FLUSH后从来没有见过)
SCRIPT LOAD script. This command registers the specified script in the Redis script cache. The command is useful in all the contexts where we want to make sure that EVALSHA will not fail (for instance during a pipeline or MULTI/EXEC operation), without the need to actually execute the script.
SCRIPT LOAD script. 这个命令是在Redis脚本缓存中缓存指定的脚本。这个命令在我们想确保EVALSHA不管在任何环境下都不会失败时非常有用(比如在管道或者 MULTI/EXEC 操作时),并且加载时它不会去执行脚本。
SCRIPT KILL. This command is the only way to interrupt a long-running script that reaches the configured maximum execution time for scripts. The SCRIPT KILL command can only be used with scripts that did not modify the dataset during their execution (since stopping a read-only script does not violate the scripting engine‘s guaranteed atomicity). See the next sections for more information about long running scripts.
SCRIPT KILL.这个命令是去中断正在运行并且运行时间达到配置最大时间的脚本的唯一方式。这个命令只能使用于脚本执行不修改数据的情况(因为停止脚本不能违反脚本引擎的原子性)。下一小节将看到关于长时间运行脚本的更多信息。
The only drawback with this approach is that scripts are required to have the following property:
唯一的缺点是,使用的脚本必须有下面的特点:
Lua does not export commands to access the system time or other external state.
没有导出命令来访问系统时间或其他外部状态。
Redis will block the script with an error if a script calls a Redis command able to alter the data set after a Redis random command like RANDOMKEY,SRANDMEMBER, TIME. This means that if a script is read-only and does not modify the data set it is free to call those commands. Note that a random command does not necessarily mean a command that uses random numbers: any non-deterministic command is considered a random command (the best example in this regard is the TIME command).
修改数据的脚本应该避免调用随机设置数据的命令,比如RANDOMKEY,SRANDMEMBER,TIME。反过来也就是说,如果一个脚本是只读的,不修改数据设置的,它就可以自由地调用这些命令。注意这里说的随机命令并不只是只那些随机数字的命令:任何非确定性的命令都被认为是一个随机命令(最好的例子就是TIME命令)。
Redis commands that may return elements in random order, like SMEMBERS(because
Redis Sets are unordered) have a different behavior when called from Lua, and undergo a silent lexicographical sorting filter before returning data to Lua scripts. So redis.call("smembers",KEYS[1])
will
always return the Set elements in the same order, while the same command invoked from normal clients may return different results even if the key contains exactly the same elements.
随机返回元素的命令,像SMEMBERS(因为Set是无序的)被Lua脚本调用时会有不同的结果,并且返回要经过lua过滤排序。因此 redis.call("smembers",KEY[1]) 总是以相同的顺序返回元素,当同一个命令客户端调用时将返回不同的结果,尽管key包含完全相同的元素。
Lua pseudo random number generation functions math.random
andmath.randomseed
are
modified in order to always have the same seed every time a new script is executed. This means that calling math.random
will always generate the same sequence
of numbers every time a script is executed if math.randomseed
is not used.
为了每次调用lua的math.random和math.randomseed方法都是一个相同的随机种子,Redis对math.random和mathrandomseed被进行了修正。意思是如果math.randomseed没有被调用,每次调用math.random都生成相同的数字序列。
However the user is still able to write commands with random behavior using the following simple trick. Imagine I want to write a Redis script that will populate a list with N random integers.
但是,使用以下简单的技巧,用户仍然能够编写与随机行为的相关的命令。想象一下,我想写一个Redis的脚本,将N个随机整数填充一个list。
I can start with this small Ruby program: 我可以用这个小Ruby程序启动
require ‘rubygems‘
require ‘redis‘
r = Redis.new
RandomPushScript = <<EOF
local i = tonumber(ARGV[1])
local res
while (i > 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)])
Every time this script executed the resulting list will have exactly the following elements:每次执行这个脚本列表将会有确切的以下要素:
> 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"
In order to make it a pure function, but still be sure that every invocation of the script will result in different random elements, we can simply add an additional argument to the script that will be used in order to seed the Lua pseudo-random number generator.
The new script is as follows:
RandomPushScript = <<EOF local i = tonumber(ARGV[1]) local res math.randomseed(tonumber(ARGV[2])) while (i > 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))
What we are doing here is sending the seed of the PRNG as one of the arguments. This way the script output will be the same given the same arguments, but we are changing
one of the arguments in every invocation, generating the random seed client-side. The
seed will be propagated as one of the arguments both in the replication link and in the
Append Only File, guaranteeing that the same changes will be generated when the AOF is reloaded or when the slave processes the script.
我们这里做的是发送PRNG的种子作为参数之一,这种方式给定相同的参数输出也将相同,但是每次调用时我都可以在客户端这边改变随机种子。当AOF重载或者从服务器处理脚本时,种子作为复制链接和附加文档的参数传输,保证生成相同的变化。
Note: an important part of this behavior is that the PRNG that Redis implements asmath.random
and math.randomseed
is
guaranteed to have the same output regardless of the architecture of the system running Redis. 32-bit, 64-bit, big-endian and
little-endian systems will all produce the same output.
注意:上面的做法最重要的是Redis 的PRNG实现 math.random和math.randomseed能保证Redis运行底层系统都有相同的输出。32-bit, 64-bit, big-endian 和little-endian系统都将产生相同的输出。
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‘
Accessing a non existing global variable generates a similar error.
使用不存在的全局变量的错误。The Redis Lua interpreter loads the following Lua libraries:
Redis的解释器加载了以下类库:
Valid formats:
> - big endian
< - little endian
![num] - alignment
x - pading
b/B - signed/unsigned byte
h/H - signed/unsigned short
l/L - signed/unsigned long
T - size_t
i/In - signed/unsigned integer with size `n‘ (default is size of int)
cn - sequence of `n‘ chars (from/to a string); when packing, n==0 means
the whole string; when unpacking, n==0 means use the previous
read number as the string length
s - zero-terminated string
f - float
d - double
‘ ‘ - ignored
Example:127.0.0.1:6379> eval ‘return struct.pack("HH", 1, 2)‘ 0
"\x01\x00\x02\x00"
127.0.0.1:6379> eval ‘return {struct.unpack("HH", ARGV[1])}‘ 0 "\x01\x00\x02\x00"
1) (integer) 1
2) (integer) 2
3) (integer) 5
127.0.0.1:6379> eval ‘return struct.size("HH")‘ 0
(integer) 4
The CJSON library provides extremely fast JSON manipulation within Lua.
CJSON提供了快速操作json的类库。
Example:
redis 127.0.0.1:6379> eval ‘return cjson.encode({["foo"]= "bar"})‘ 0
"{\"foo\":\"bar\"}"
redis 127.0.0.1:6379> eval ‘return cjson.decode(ARGV[1])["foo"]‘ 0 "{\"foo\":\"bar\"}"
"bar"
The cmsgpack library provides simple and fast MessagePack manipulation within Lua.
cmsgpack 提供了简单并且快速的MessagePack操作类库。
Example:
127.0.0.1:6379> eval ‘return cmsgpack.pack({"foo", "bar", "baz"})‘ 0
"\x93\xa3foo\xa3bar\xa3baz"
127.0.0.1:6379> eval ‘return cmsgpack.unpack(ARGV[1])‘ 0 "\x93\xa3foo\xa3bar\xa3baz"
1) "foo"
2) "bar"
3) "baz
The Lua Bit Operations Module adds bitwise operations on numbers. It is available for scripting in Redis since version 2.8.18.
Example:
127.0.0.1:6379> eval ‘return bit.tobit(1)‘ 0
(integer) 1
127.0.0.1:6379> eval ‘return bit.bor(1,2,4,8,16,32,64,128)‘ 0
(integer) 255
127.0.0.1:6379> eval ‘return bit.tohex(422342)‘ 0
"000671c6"
It supports several other functions: bit.tobit
, bit.tohex
, bit.bnot
, bit.band
, bit.bor
,bit.bxor
, bit.lshift
, bit.rshift
, bit.arshift
, bit.rol
, bit.ror
, bit.bswap
.
All available functions are documented in the Lua BitOp documentationbit.tobit
, bit.tohex
, bit.bnot
, bit.band
, bit.bor
,bit.bxor
, bit.lshift
, bit.rshift
, bit.arshift
, bit.rol
, bit.ror
, bit.bswap
.所有方法的文档在Lua
BitOp documentation上。Perform the SHA1 of the input string.
127.0.0.1:6379> eval ‘return redis.sha1hex(ARGV[1])‘ 0 "foo"
"0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33"
redis.log
function.redis.log(loglevel,message)
loglevel
is one of: loglevel
是下面中的一个
redis.LOG_DEBUG
redis.LOG_VERBOSE
redis.LOG_NOTICE
redis.LOG_WARNING
message
argument
is simply a string. Example:redis.log(redis.LOG_WARNING,"Something is wrong with this script.")
Will generate the following: 将生产:[32343] 22 Mar 15:21:39 # Something is wrong with this script.
Scripts should never try to access the external system, like the file system or any other system call. A script should only operate on Redis data and passed arguments.
脚本应该不要去使用外部的系统,比如文件或者其他的系统。一个脚本应该仅仅是操作Redis的数据和传递参数。
Scripts are also subject to a maximum execution time (five seconds by default). This default timeout is huge since a script should usually run in under a millisecond. The limit is mostly to handle accidental infinite loops created during development.
脚本也应该受制于一个最大执行时间(缺省是5秒)。因为脚本应该一般运行时间都是毫米级别的,所以这个缺省的时间是很大的。这个限制主要是去处理脚本运行期间可能发生的死循环。
It is possible to modify the maximum time a script can be executed with millisecond precision, either via redis.conf
or
using the CONFIG GET / CONFIG SET command. The configuration parameter affecting max execution time is called lua-time-limit
.
SHUTDOWN NOSAVE
.SHUTDOWN NOSAVE
that stops the server without saving
the current data set on disk (basically the server is aborted).
Care should be taken when executing EVALSHA in the context of a pipelined request, since even in a pipeline the order of execution of commands
must be guaranteed. IfEVALSHA will return a NOSCRIPT
error
the command can not be reissued later otherwise the order of execution is violated.
在管道里使用EVALSHA要小心,因为即使在管道也必须保证命令的执行顺序。如果EVALSHA返回NOSCRIPT的错误,之后就不会再被执行,否则就违反了执行顺序。
The client library implementation should take one of the following approaches:
客户端应该采取下面当中的一种方法处理:
Always use plain EVAL when in the context of a pipeline.
在管道环境中总是使用EVAL。
Accumulate all the commands to send into the pipeline, then check for EVALcommands and use the SCRIPT EXISTS command to check if all the scripts are already defined. If not, add SCRIPT LOAD commands on top of the pipeline as required, and use EVALSHA for all the EVAL calls.
积累所有的命令到管道里,然后使用EVAL命令运行检测,并且使用SCRIPT EXISTS去检测是否所有的脚本已经明确定义。如果没有,在管道请求头添加SCRIPT LOAD 命令,并且使用EVALSHA 代替所有的VAAL调用。
原文地址:http://blog.csdn.net/guobangli/article/details/46581159