redis翻译_redis lua脚本

Available since 2.6.0.  加入版本2.6

Time complexity: Depends on the script that is executed. 时间复杂度: 取决于脚本的执行

出处:http://blog.csdn.net/column/details/redisbanli.html

Introduction to EVAL  介绍EVAL

EVAL and EVALSHA are
used to evaluate scripts using the Lua interpreter built into Redis starting from version 2.6.0.

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()和redis.pcall()类似,唯一的不同是,如果命令返回一个错误结束时,redis.call()会将这个错误提升到lua的error,让EVAL返回一个lua错误给调用者,redis.pcall()将捕获这个错误并且返回一个代表错误的
Lua table.

The arguments of the redis.call() and redis.pcall() functions
are all the arguments of a well formed Redis command:

redis.call()和redis.pcall()的参数可以是Redis所有命令的参数:

> 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).

所有的命令在执行之前都是先被解析的,以便确定操作在那些keys上。正确使用EVAL,key必须被明确定传递。在很多时候,像上面的写法是没有错的,但是特别注意,在redis集群中明确传递的key可以确保找到正确节点,上面的写法就不行(redis集群正在开发中,但是使得现在的脚本设计去支持集群才是好的脚本).

Note this rule is not enforced in order to provide the user with opportunities
to abuse the Redis single instance configuration, at the cost of writing scripts not compatible with Redis Cluster.

注意上面这条不强制规则,并不是使用户造成滥用redis实例,编写脚本不支持redis集群的代价的因素。

Lua
scripts can return a value that is converted from the Lua type to the Redis protocol using a set of conversion rules.

lua脚本的返回一个lua类型,根据redis协议转换规则转换的redis类型的值。

Conversion between Lua and Redis data types redis类型和lua类型的转换

Redis return values are converted into Lua data types when Lua calls a Redis command using call() or pcall(). Similarly Lua data types are converted into the Redis protocol when a Lua script
returns a value, so that scripts can control what EVAL will
return to the client.

当lua调用call()或者pcall()函数时,redis的返回类型被转换成lua的数据类型。同样的当lua脚本返回值时,lua数据类型被转换成redis数据类型,因此脚本可以控制EVAL的返回。

This
conversion between data types is designed in a way that if a Redis type is converted into a Lua type, and then the result is converted back into a Redis type, the result is the same as the initial value.

两种数据类型的转换,被设计成,如果从Redis类型转换成Lua类型,然后再从lua类型转换成redis类型,最后得到的结果和最初的值时一样的。

In
other words there is a one-to-one conversion between Lua and Redis types. The following table shows you all the conversions rules:

换句话说,Lua类型和Redis类型中,这是一个一一对应的转换。下面这个表就是转换规则:

Redis
to Lua
 conversion table.Redis 类型转换成Lua类型表:

  • Redis integer reply -> Lua number
  • Redis bulk reply -> Lua string
  • Redis multi bulk reply -> Lua table (may have other Redis data types nested)
  • Redis status reply -> Lua table with a single ok field containing the status
  • Redis error reply -> Lua table with a single err field containing the error
  • Redis Nil bulk reply and Nil multi bulk reply -> Lua false boolean type

Lua to Redis conversion
table.lua类型转换成Redis类型表:

  • Lua number -> Redis integer reply (the number is converted into an integer)
  • Lua string -> Redis bulk reply
  • Lua table (array) -> Redis multi bulk reply (truncated to the first nil inside the Lua array if any)
  • Lua table with a single ok field -> Redis status reply
  • Lua table with a single err field -> Redis error reply
  • Lua boolean false -> Redis Nil bulk reply.

There is an additional
Lua-to-Redis conversion rule that has no corresponding Redis to Lua conversion rule:

还有一个Lua转换成Redis的附加规则,这条规则没有对应的Redis转换成Lua:

  • Lua boolean true -> Redis integer reply with value of 1.

Also
there are two important rules to note:并且这里有两个重要的规则需要注意:

  • Lua has a single numerical type, Lua numbers. There is no distinction between integers and floats. So we always convert Lua numbers into integer replies, removing the decimal part of the number if any. If
    you want to return a float from Lua you should return it as a string, exactly like Redis itself does (see for instance the ZSCORE command).

Lua有一个数值类型,number。它没有整型和浮点型的区别。因此我们总是将Lua的numbers类型转换成integer
replies,如果有小数就删除小数部分。如果你想返回一个float,那么你应该把它作为一个string类型返回,就像Redis自己的做法(参照ZSCORE命令用法的例子)

在Lua中数组中没有nil值得表示方式,因为Lua中数组是一个table的含义,因此当Redis转换成Lua
数组遇到nil值就停止了(后面的也不转换了)。

Here
are a few conversion examples: 看几个例子

> 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.

最后一个例子是展示使用redis.call()或者redis.pcall()函数直接调命令,结果被直接从lua类型强制转换成Redis类型。

In
the following example we can see how floats and arrays with nils are handled:

下面例子说float和数组有nil值得处理:

> 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.

可以看见3.333转换成3,因为nil的原因string类型的bar没返回。

Helper functions to return Redis types 返回redis类型的帮助函数

There are two helper functions to return Redis types from Lua.

有两个从lua脚本返回Redis类型的帮助函数.

  • 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.error_reply(error_string) 返回一个错误应答.这个函数可以简单地返回你设置提示的 err字段表。

  • 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.status_reply(status_string) 返回一个状态应答。这个函数可以简单地返回你设置提示的ok字段表。

(下面是译者执行的例子)

Atomicity of scripts  脚本的原子性

Redis uses the same Lua interpreter to run all the commands. Also Redis guarantees that a script is executed in an atomic way: no other script or Redis command will
be executed while a script is being executed. This semantic is similar to the one ofMULTI / EXEC.
From the point of view of all the other clients the effects of a script are either still not visible or already completed.

Redis使用相同的Lua解释器去运行所有的命令。并且保证脚本的实现是原子性的:当一个脚本正在执行的时候没有其他的脚本或者命令可以被执行。意思和命令
MULTI/EXEC 相似(MULTI/EXEX是Redis事务)。需要注意的是,从其他客户端的角度来看,脚本的影响是:其他客户端不能访问redis或者已经执行完毕(也就是其他客户端不能执行命令或者脚本)。

However this also means that executing slow scripts is not a good idea. It is not hard to create
fast scripts, as the script overhead is very low, but if you are going to use slow scripts you should be aware that while the script is running no other client can execute commands.

然而这也是说,执行一个慢的脚本非常不好。不能快速创建脚本,因为它非常慢,如果你执行这个非常慢的脚本就该意识到,这个脚本在执行的时候其他客户端是不能执行命令的。

Error handling  错误处理

As already stated, calls to 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:

前面已经提到,调用redis.call()函数 时如果Redis命令错误将停止执行脚本并且返回错误,这个错误的方式很明显是被脚本生成的。

> 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.pcall()没有脚本错误,它仅仅是被指定格式上的这个错误对象被返回(作为Lua 表的err字段)。通过调用redis.pcall()返回得到的错误对象可以被用户使用脚本精确定义.

(例如)

127.0.0.1:6379> eval "return redis.pcall(‘get‘,‘foo‘)" 0

(error) WRONGTYPE Operation against a key holding the wrong kind of value

Bandwidth and EVALSHA 带宽和EVALSHA

The EVAL command
forces you to send the script body again and again. Redis does not need to recompile the script every time as it uses an internal caching mechanism, however paying the cost of the additional bandwidth may not be optimal in many contexts.

eval命令可以让你重复发送相同脚本。redis内部的缓存机制不需要每次都去重新编译相同的脚本,但是很多时候脚本附带的带宽开销可能不是最好的。

On the other hand, defining commands using a special command or via redis.confwould
be a problem for a few reasons:

另一方面,用特殊命令或者通过redis.conf方式定义EVAL脚本命令,因为下面几个原因会有问题:

  • 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.

阅读应用代码时,因为应用调用的命令在服务器这边所以整个语义不清晰。

In order to avoid these
problems while avoiding the bandwidth penalty, Redis implements the EVALSHA command.

为了避开带宽问题和上面的问题,redis实现了EVALSHA命令。

EVALSHA works
exactly like EVAL, but instead of having a script as the first argument it has the SHA1 digest of a script. The behavior
is the following:

EVALSHA酷似EVAL,但它不是以一个脚本作为第一个参数,它的第一个参数脚本是SHA1摘要。它的行为如下:

  • 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.

如果服务器不记得这个脚本的SHA1摘要,将返回一个提示使用EVAL代替的错误。

Example:例如

> 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 instea

客户端可以乐观地这样实现,在发生EVAL命令前可以认为EVAL脚本已经被服务器知道了,所以发送EVALSHA。如果返回NOSCRIPT 的错误在使用EVAL命令。

Passing
keys and arguments as additional EVAL arguments is also very useful in this
context as the script string remains constant and can be efficiently cached by Redis.

因为脚本字符串保持不变,并且可以通过Redis有效缓存起来,那么通EVALSHA发EVAL的key参数和附加参数也是非常有用的。

Script cache semantics  缓存脚本的意义

Executed scripts are guaranteed to be in the script cache of a given execution of a Redis instance forever. This means that if an EVAL is
performed against a Redis instance all the subsequent EVALSHA calls will
succeed.

执行的脚本都保证在Redis的实例中永远缓存。意思是,在EVAL执行之后的脚本,那么接着使用EVALSHA命令都能成功调用执行。

The reason why scripts can be cached for long time is that it is unlikely for a well written application to have enough different scripts to cause memory problems. Every script is conceptually like the implementation of
a new command, and even a large application will likely have just a few hundred of them. Even if the application is modified many times and scripts will change, the memory used is negligible.

脚本缓存很长的时间是不太可能的,因为一个好的写应用有很多不同的脚本,脚本多了就会引起内存问题。每一个脚本的概念其实就是实现几个命令,甚至一个大的应用可能一个脚本有几百个命令。尽管应用有时会修改并且脚本会改变,但是减少的内存是微不足道的。

The
only way to flush the script cache is by explicitly calling the SCRIPT FLUSHcommand, which will completely
flush
 the scripts cache removing all the scripts executed so far.

唯一清除脚本缓存的方法是明确地调用SCRIPT
FLUSH命令,这个命令将完全删除到目前为止所有缓存的脚本。

This
is usually needed only when the instance is going to be instantiated for another customer or application in a cloud environment.

在一个实例被用在在集群中或者在云环境中,初始化时通常都需要这样做。

Also,
as already mentioned, restarting a Redis instance flushes the script cache, which is not persistent. However from the point of view of the client there are only two ways to make sure a Redis instance was not restarted between two different commands.

另外,如前所述,重启Redis将清除scipt,因为它不是持久化的。对应客户端来说,有两种方式使用两个命令可以确保Redis没有重启。

  • The connection we have with the server is persistent and was never closed so far.

客户端到目前为止一直和服务器连接着。

  • The client explicitly checks the runid field in the INFO command
    in order to make sure the server was not restarted and is still the same process.

执行INFO命令检查runid确保服务器没有重启并且还是同一个进程。

Practically
speaking, for the client it is much better to simply assume that in the context of a given connection, cached scripts are guaranteed to be there unless an administrator explicitly called the SCRIPT
FLUSH
 command.

具体来说,为一个一直连接服务器的客户端做一个假设是比较好的,这个假设就是这个连接缓存的脚本一直都在服务器缓存着的,除非有服务器管理权限的人执行
SCRIPT FLUSH命令。

The
fact that the user can count on Redis not removing scripts is semantically useful in the context of pipelining.

在使用管道的情况下,用户对没有被删除的脚本进行计数是很有用的。

For
instance an application with a persistent connection to Redis can be sure that if a script was sent once it is still in memory, so EVALSHA can be used against those scripts in a pipeline without the chance of an error being generated due to an unknown script
(we‘ll see this problem in detail later).

例如,一个一直连接Redis的应用就能确保一个脚本发生一次之后就一直在reids内存中,因此EVALSHA可以防备那些在管道里面的脚本,使之没有机会产生未知脚错误(稍后将看到这个问题)。

A common pattern is to call SCRIPT
LOAD
 to load all the scripts that will appear in a pipeline, then use EVALSHA directly
inside the pipeline without any need to check for errors resulting from the script hash not being recognized.

一个普遍的做法是调用SCRIPT LOAD去加载在一个管道里出现所有的脚本,然后直接在管道里使用EVALSHA,而不用去检测脚本没有被认可产生的错误。

The SCRIPT command   脚本命令

Redis offers a SCRIPT command that can be used in order to control the scripting subsystem. SCRIPT currently accepts three different commands:

Redis为了去控制脚本子系统提供SCRIPT命令。SCRIPT通常接收三个不同的命令:

  • 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.这个命令是去中断正在运行并且运行时间达到配置最大时间的脚本的唯一方式。这个命令只能使用于脚本执行不修改数据的情况(因为停止脚本不能违反脚本引擎的原子性)。下一小节将看到关于长时间运行脚本的更多信息。

Scripts as pure functions  脚本作为纯粹的方法

A very important part of scripting is writing scripts that are pure functions. Scripts executed in a Redis instance are replicated on slaves by sending the script -- not the resulting commands. The same happens for the Append Only
File. The reason is that sending a script to another Redis instance is much faster than sending the multiple commands the script generates, so if the client is sending many scripts to the master, converting the scripts into individual commands for the slave
/ AOF would result in too much bandwidth for the replication link or the Append Only File (and also too much CPU since dispatching a command received via network is a lot more work for Redis compared to dispatching a command invoked by Lua scripts).

脚本一个非常重要的部分是作为一个纯粹的方法写脚本。脚本运行在Redis实例上可以通过发送该脚本使得该脚本可以 被 slaves 复制--作为没有返回的命令.和添加只读文件是一样的。原因是发送一个脚本到其他Redis实例比发送几个命令使之产生脚本快,所以如果客户端发送一些脚本到master上,使脚本为
slave/SOF 转换成单个的命令的结果是为应答或者增加文件花费更多的带宽(而且Redis一个命令通过网络接收调用与使用脚本比较要消耗更多的CPU)。

The only drawback with this approach is that scripts are required to have the following property:

唯一的缺点是,使用的脚本必须有下面的特点:

  • The script always evaluates the same Redis write commands with the same arguments given the same input data set. Operations
    performed by the script cannot depend on any hidden (non-explicit) information or state that may change as script execution proceeds or between different executions of the script, nor can it depend on any external input from I/O devices.

脚本的执行结果总是与给出相同参数的命令的执行结果相同。脚本操作不能依赖隐藏的信息或者可变的状态作为脚本执行的结果或者不同的执行过程,也不能依赖与使用I/O设备的输入信息。

Things
like using the system time, calling Redis random commands like RANDOMKEY,
or using Lua random number generator, could result into scripts that will not always evaluate in the same way.

就像使用系统时间,使用Redis 随机命令
如RANDOMKEY,或者使用Lua随机数触发器,都使得同样的脚本有不同的结果。

In
order to enforce this behavior in scripts Redis does the following:

为了强制这个做法,脚本应该这样做:

  • 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,SRANDMEMBERTIME.
    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.randomseedis 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:

    为了使脚本成为一个纯粹的函数,但仍然确保每一次调用有不同的随机元素,我们可以给脚本添加额外的参数作为Lua伪随机数字生成器的种子。修改的脚本如下:

    
    
    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系统都将产生相同的输出。

Global variables protection 全局变量的保护

Redis scripts are not allowed to create global variables, in order to avoid leaking data

into the Lua state. If a script needs to maintain state between calls (a pretty uncommon need) it should use Redis keys instead.

为了Lua状态避免数据泄露,Redis脚本不允许创建全局变量。如果脚本调用过程需要维护全局状态变量应该使用Redis 的key代替。

When global variable access is attempted the script is terminated and EVAL returns

with an error:

当企图去使用全局变量时,脚本将被终止,并且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‘

Accessing a non existing global variable generates a similar error.
使用不存在的全局变量的错误。

Using Lua debugging functionality or other approaches like altering the meta table used

to implement global protections in order to circumvent globals protection is not hard.

However it is difficult to do it accidentally. If the user messes with the Lua global state,

the consistency of AOF and replication is not guaranteed: don‘t do it.

使用Lua的调试功能或其他方法,如改变使用的元表以规避全局保护并不难。但是很难做到不小心。如果用户与Lua全局状态混乱,
AOF和复制的一致性是没有保证的:不要这样做。

Note for Lua newbies: in order to avoid using global variables in your scripts simply

declare every variable you are going to use using the local keyword.

lua新手要注意:为了避免使用全局变量,声明变量时应该明确使用local关键字。

Using SELECT inside scripts在脚本中使用SELECT

It is possible to call SELECT inside
Lua scripts like with normal clients, However one

subtle aspect of the behavior changes between Redis 2.8.11 and Redis 2.8.12. Before

the 2.8.12 release the database selected by the Lua script was transferred to
the calling script as current database. Starting from Redis 2.8.12 the database selected by the Lua script only affects the execution of the script itself, but does not modify the database

selected by the client calling the script.

在客户端的脚本中使用 SELECT就像一般的客户端一样,只有在 2.8.11和2.8.12有一点变化。在2.8.12之前选择的数据库是通过Lua脚本传送到Lua脚本当前的数据库。2.8.12之后,通过Lua选择的数据库仅仅影响脚本本身的执行,但是通过客户端调用脚本不修改数据库的选择。

The semantic change between patch level releases was needed since the old behavior was inherently
incompatible with the Redis replication layer and was the cause of bugs.

需要修补程序级别版本之间的语义变化,因为旧的行为不符合Redis的复制层的本质,这个是bug的原因。

Available libraries 有效的类库

The Redis Lua interpreter loads the following Lua libraries:

Redis的解释器加载了以下类库:

  • base lib.
  • table lib.
  • string lib.
  • math lib.
  • debug lib.
  • struct lib.
  • cjson lib.
  • cmsgpack lib.
  • bitop lib
  • redis.sha1hex function.

Every
Redis instance is guaranteed to have all the above libraries so you can be sure that the environment for your Redis scripts is always the same.

每一个Redis实例都确保加载了上面所有的类库,因此你可以确保你的脚本运行环境总是一样的。

struct,
CJSON and cmsgpack are external libraries, all the other libraries are standard Lua libraries.

除了struct,CJSON和cmsgpack
是外部类库,其他所有都是Lua的标准类库。

struct

struct is a library for packing/unpacking structures within Lua.

struct
是 压缩/解压缩 结构库。

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

CJSON

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"

cmsgpack

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

bitop

The Lua Bit Operations Module adds bitwise operations on numbers. It is available for scripting in Redis since version 2.8.18.

Lua位运算模块增加了对数字的位运算。从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.tobitbit.tohexbit.bnotbit.bandbit.bor,bit.bxorbit.lshiftbit.rshiftbit.arshiftbit.rolbit.rorbit.bswap.
All available functions are documented in the Lua BitOp documentation

支持的方法有:bit.tobitbit.tohexbit.bnotbit.bandbit.bor,bit.bxorbit.lshiftbit.rshiftbit.arshiftbit.rolbit.rorbit.bswap.所有方法的文档在Lua
BitOp documentation
上。

redis.sha1hex

Perform the SHA1 of the input string.

执行输入的SHA1字符串。

Example:

127.0.0.1:6379> eval ‘return redis.sha1hex(ARGV[1])‘ 0 "foo"
"0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33"

Emitting Redis logs from scripts从脚本记录日志

It is possible to write to the Redis log file from Lua scripts using the redis.logfunction.

可以使用lua脚本的redis.log函数记录Redis的日志文件。

redis.log(loglevel,message)

loglevel is one of:  loglevel 是下面中的一个

  • redis.LOG_DEBUG
  • redis.LOG_VERBOSE
  • redis.LOG_NOTICE
  • redis.LOG_WARNING

They
correspond directly to the normal Redis log levels. Only logs emitted by scripting using a log level that is equal or greater than the currently configured Redis instance log level will be emitted.

与一般Redis日志的级别相同。仅仅是记录来自脚本,等效或者更高于当前Redis的日志配置的记录。

The message argument
is simply a string. Example:

参数message是一个简单的字符串。例如:

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.

Sandbox and maximum execution time  研究最大执行时间

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.

可以通过redis.conf或者使用CONGIG GET/CONFIG SET命令设置脚本运行时间为毫秒级别。

配置最大脚本运行时间的参数名是  lua-time-limit

When
a script reaches the timeout it is not automatically terminated by Redis since this violates the contract Redis has with the scripting engine to ensure that scripts are atomic. Interrupting a script means potentially leaving the dataset with half-written data.
For this reasons when a script executes for more than the specified time the following happens:

如果脚本达到最大运行时间没有自动中断,那是因为Redis的脚本引擎保证了脚本的原子性。中断脚本可能导致数据只写了一半。当一个脚本执行时间超过最大运行时间会是发生下面情况:

  • Redis logs that a script is running too long.
  • Redis日志脚本运行很长时间。
  • It starts accepting commands again from other clients, but will reply with a BUSY error to all the clients sending normal commands. The only allowed commands in this status are SCRIPT
    KILL
     and SHUTDOWN NOSAVE.
  • 开始接收来自其他客户端的命令,但是将返回一个BUSY的错误。这时仅仅允许运行的命令是 SCRIPT KILL 和SHUTDOWN NOSAVE
  • It is possible to terminate a script that executes only read-only commands using the SCRIPT KILL command.
    This does not violate the scripting semantic as no data was yet written to the dataset by the script.
  • 可能会使用SCRIPT KILL命令去终止只读模式的脚本。因为没有数据的写操作所以没有违反脚本的原子性。
  • If the script already called write commands the only allowed command becomesSHUTDOWN NOSAVE that stops the server without saving
    the current data set on disk (basically the server is aborted).
  • 如果脚本已经调用了写命令,现在唯一允许的命令是 SHUTDOWN NOSAVE,将导致服务器没有保存当前set的数据到硬盘上(主要是Redis 服务器终止了)

EVALSHA in the context of pipelining  在通道中使用EVALSHA

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/column/details/redisbanli.html

时间: 2024-10-09 06:00:44

redis翻译_redis lua脚本的相关文章

Php+Redis 实现Redis提供的lua脚本功能

<?php require_once "predis-0.8/autoload.php"; $config['schema'] = 'tcp'; $config['host']= "192.168.1.7"; $config['port'] = 6379; $redis = new Predis\Client($config); class wode extends Predis\Command\ScriptedCommand { public functio

在redis中使用lua脚本

在实际工作过程中,可以使用lua脚本来解决一些需要保证原子性的问题,而且lua脚本可以缓存在redis服务器上,势必会增加性能. 不过lua也会有很多限制,在使用的时候要注意. 在Redis中执行Lua脚本有两种方法:eval和evalsha eval EVAL script numkeys key [key ...] arg [arg ...] 其中: <1> script:你的lua脚本 <2> numkeys:  key的个数 <3> key:redis中各种数据

StackExchange.Redis加载Lua脚本进行模糊查询的批量删除和修改

前言 使用StackExchange.Redis没有直接相关的方法进行模糊查询的批量删除和修改操作,虽然可以通过Scan相关的方法进行模糊查询,例如:HashScan("hashkey", "*key*"),然后再使用相关的方法进行相关的批量操作,但是如果缓存数据量比较大,效率低下,那么可以使用Lua脚本进行模糊查询的批量操作:ScriptEvaluate(LuaScript.Prepare(...)). 通过keys进行模糊查询后的批量操作 批量删除 1 var

python redis客户端使用lua脚本

有一个需求,为一个key设置一个field存储时间戳,每当有新数据,判断新数据时间戳是否>之前的时间戳,如果是,更新时间戳,由于依赖中间执行结果,所以使用lua减少客户端和服务端通信次数 #!/usr/bin/python # -*- coding: utf-8 -*- import redis r = redis.Redis("127.0.0.1") lua = """ local key = KEYS[1] local field = ARGV[

在PHP中使用redis来操作lua脚本,使用$redis-&gt;eval()命令时出错,参数传递无效

使用$redis->eval命令时传递三个参数,第一个为lua脚本文件,第二个为key,第三个参数为key的个数向lua中传递参数时,在key中定义好,即可,示例正确代码 用lua脚本循环 eval "local rst={}; for i,v in pairs(KEYS) do rst[i]=redis.call('hgetall', v) end; return rst" 2 user:1 user:2 $z=$redis->EVAL('local rst={}; fo

redis翻译_redis管道

Redis is a TCP server using the client-server model and what is called a Request/Response protocol. redis使用的是基于tcp协议的client-server模型,也可以叫做Request/Response 协议模型. This means that usually a request is accomplished with the following steps: 它的意思是指通常一个请求完

《Redis设计与实现》学习笔记-Lua脚本

Redis从2.6开始支持Lua脚本,和事务的功能类似,可以通过Lua脚本原子的执行多个Redis命令.Redis提供了EVAL和EVALSHA命令执行lua脚本. 创建并修改Lua坏境 Redis在服务器内嵌了一个Lua坏境,并进行了一系列的修改,从而确保这个Lua坏境可以满足Redis服务器的需要,通过下列步骤创建并修改Lua坏境: 创建一个基础Lua坏境,通过调用Lua的C API函数lua_open. 载入多个函数库到Lua坏境中,让Lua脚本可以使用这些函数来进行数据操作.包括Lua核

Redis集成Lua脚本实现

作者:zhanhailiang 日期:2014-12-02 相关依赖 1. 环境部署 Redis安装配置教程及phpredis扩展安装测试 Redis安装以及php扩展 Windows下安装phpredis模块(当前Window环境下的php_redis.dll基本还是2.1.3,而Linux下的redis.so版本已经到了2.2.5,这可能导致部分指令集的支持程度不同) 2. Redis指令手册 php-redis中文帮助手册.chm(这份手册相对较旧,请参照Redis Commands) R

Redis Lua 脚本使用

Lua语言提供了如下几种数据类型:booleans(布尔).numbers(数值).strings(字符串).tables(表格). 下面是一些 Lua 的示例,里面注释部分会讲解相关的作用: -- -- -- 拿客 -- 网站:www.coderknock.com -- QQ群:213732117 -- 三产 创建于 2017年06月15日 12:04:54. -- 描述: -- -- local strings website = "coderknock.com" print(we