EVAL script Numkeys key [key ...] arg [arg ...]
Starting with the Redis 2.6.0 release, the LUA script can be evaluated using the EVAL command with the built-in LUA interpreter.
The script parameter is a LUA 5.1 script that runs in the Redis server context, which does not have to (and should not) be defined as a LUA function.
The Numkeys parameter is used to specify the number of key name parameters.
Key Name parameter key [key ...] starting with the third parameter of EVAL, the Redis key (key) used in the script, these key name parameters can be accessed through the global variables keys array in Lua, in the form of a base address of 1 (keys[1], keys[2] , etc.).
At the end of the command, those additional parameters that are not key-name parameters arg [arg ...] can be accessed in Lua via a global variable ARGV array, accessed in the same form as the keys variable (argv[1], argv[2], and so on).
These long lengths of instructions can be summed up in a simple example:
> eval "return {keys[1],keys[2],argv[1],argv[2]}" 2 Key1 key2 First second
1) "Key1"
2) "Key2"
3) "First" C4/>4) "Second"
where "return {keys[1],keys[2],argv[1],argv[2]}" is an evaluated Lua script, the number 2 specifies the number of key name parameters, Key1 and Key2 are key name parameters, respectively, using keys[1] and keys[2] access, and the most The first and second are additional parameters that can be accessed through argv[1] and argv[2].
In a Lua script, you can use two different functions to execute Redis commands, respectively:
Redis.call ()
Redis.pcall ()
The only difference between the two functions is that they handle the errors produced by executing the commands in different ways, as described in the "Error Handling" section later.
The parameters of the Redis.call () and Redis.pcall () two functions can be any redis command in well-formed (good formed):
> eval "return Redis.call (' Set ', ' foo ', ' Bar ')" 0
OK
It is important to note that the above script does implement the purpose of setting the value of key Foo to bar, but it violates the semantics of the EVAL command, because all the keys used in the script should be passed by the keys array, like this:
> eval "return Redis.call (' Set ', keys[1], ' Bar ')" 1 foo
OK
There is a reason to ask for the correct form to pass the key (key), because it is not just the EVAL command, all Redis commands are parsed before execution to determine which keys the command will operate on.
Therefore, for the EVAL command, the key must be passed in the correct form to ensure that the analysis is performed correctly. In addition, there are many other benefits of using the correct form to pass the key, and one of its most important uses is to make sure that the Redis cluster can send your request to the correct cluster node. (The work on the Redis cluster is still in progress, but the scripting feature is designed to be compatible with cluster functionality.) However, this rule is not mandatory, which gives users the opportunity to abuse (abuse) Redis single-instance configuration (instance), at the cost of writing scripts that are not compatible with Redis clusters.
Converting between Lua data types and Redis data types
When Lua executes the Redis command through the call () or Pcall () function, the return value of the command is converted to the LUA data structure. Similarly, when the Lua script runs in the Redis built-in interpreter, the return value of the Lua script is also converted to the Redis Protocol (Protocol), and the value is returned to the client by EVAL.
Conversions between data types follow a design principle: If you convert a Redis value to a LUA value, and then convert the converted Lua value back to the Redis value, the Redis value for this conversion should be the same as the Redis value at the time of the original.
In other words, there is a one by one conversion relationship between the Lua type and the Redis type.
The following is a detailed translation rule:
Transitioning from Redis to Lua:
Redis integer reply, lua Number/redis integer converted to LUA numbers
Redis Bulk reply, Lua String/redis Bulk replies convert to LUA strings
Redis Multi Bulk reply, LUA table (May has other Redis data types nested)/Redis multiple bulk replies converted to LUA tables, there might be other Re in the table Dis data type
Redis status reply, Lua table with a single OK field containing the Status/redis state reply is converted to Lua tables, the OK field in the table contains the status information
Redis error reply, LUA table with a single err field containing the Error/redis error reply converted to Lua table, the Err field in the table contains an error message
Redis Nil Bulk reply and nil Multi bulk Reply-lua false Boolean Type/redis nil reply and nil multiple replies convert to Lua Boolean false
Converting from Lua to Redis:
Lua number---Redis integer Reply/lua numbers converted to Redis integers
Lua string-, Redis bulk Reply/lua strings converted to Redis bulk replies
Redis Multi Bulk Reply/lua table (array) converted to Redis multiple bulk replies, Lua Table (array)
Lua table with a single OK field--Redis status reply/Lua tables with one OK field converted to Redis State reply
Lua table with a single err field---Redis error reply/LUA tables with individual ERR fields converted to Redis fault reply
Lua Boolean false, Redis nil bulk Reply/lua Boolean value false converted to Redis nil bulk reply
There is an additional rule for converting from Lua to Redis, which does not have a rule that corresponds to the transition from Redis to LUA:
Lua boolean true Redis integer reply with value of 1/lua Boolean true converted to Redis integer 1 in reply
The following are examples of several types of conversions:
> eval "return" 0
(integer)
> 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"
In the three code examples above, the first two shows how to convert LUA values into Redis values, and the last example is more complex, demonstrating a type-to-process that converts a Redis value to a LUA value and then converts the LUA value into a Redis value.
The atomic nature of the script
Redis uses a single LUA interpreter to run all the scripts, and Redis guarantees that the script will be executed atomically (atomic): When a script is running, no other scripts or Redis commands are executed. This is similar to a transaction surrounded by multi/exec. In other clients ' view, the effect of the script (effect) is either invisible (not visible) or completed (already completed).
On the other hand, this also means that it is not a good idea to execute a slow-running script. It is not difficult to write a script that runs very quickly and Shunliu, because the script has very little overhead (overhead), but be careful when you have to use some slow-running scripts, because when these snail scripts run slowly, other clients cannot execute the commands because the server is busy.
Error handling
As mentioned in the previous command Introduction section, the only difference between Redis.call () and Redis.pcall () is that they differ in error handling.
When an error occurs during the execution of a command by Redis.call (), the script stops executing and returns a script error stating the cause of the error:
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
Unlike Redis.call (), Redis.pcall () does not raise (raise) errors when an error occurs, but instead returns a Lua table (table) with the Err field, which is used to indicate an error:
Redis 127.0.0.1:6379> EVAL "return Redis.pcall (' Get ', ' foo ')" 0
(Error) ERR operation against a key holding the WRO ng kind of value
Bandwidth and Evalsha
The EVAL command requires that you send the script body (the scripts body) every time you execute the script. Redis has an internal caching mechanism, so it does not recompile the script every time, but in many cases it is not the best choice to pay the unnecessary bandwidth to transfer the script body.
To reduce bandwidth consumption, Redis implements the Evalsha command, which, like EVAL, is used to evaluate the script, but the first parameter it accepts is not a script, but the SHA1 checksum (sum) of the script.
The Evalsha command behaves as follows:
If the server remembers the script specified by the given SHA1 checksum, execute the script
If the server does not remember the script specified by the given SHA1 checksum, it returns a special error reminding the user to use EVAL instead of Evalsha
The following is an 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. Use [EVAL] (/commands/eval).
The underlying implementation of the client library can always be optimistic about using Evalsha instead of EVAL, and expects the script to be used to be saved on the server, using the EVAL command to resend the script only when the NOSCRIPT error occurs, so that bandwidth can be saved to a maximum.
This also illustrates the importance of using the correct format to pass the key name parameters and additional parameters when executing the EVAL command: Because if the arguments are written in the script, the script is re-sent every time the parameter changes, even if the body of the script does not change, instead, By using the correct format to pass the key name parameters and additional parameters, you can use the Evalsha command to reuse the script directly, without unnecessary bandwidth consumption, in the case of the script body being unchanged.
Script Cache
Redis guarantees that all scripts that have been run are permanently saved in the script cache, which means that when the EVAL command executes a script successfully on a Redis instance, then all Evalsha commands for that script are executed successfully.
The only way to flush the script cache is to explicitly call the Script FLUSH command, which empties the cache of all scripts that have run. This command is typically executed only in cloud computing environments where Redis instances are changed to other customers or instances of other applications.
The reason that the cache can be stored for a long time without causing memory problems is that they are very small and very few, even if the script is conceptually similar to implementing a new command, even though there are hundreds of scripts in a large-scale program, even though these scripts are often modified, The memory for storing these scripts is still negligible.
In fact, users will find it a good idea that Redis does not remove the script from the cache. For example, for a program that maintains a persistent link with Redis (persistent connection), it can be sure that the script executed once will remain in memory, so it can be used in the pipeline Evalsha command without worrying about errors due to missing scripts (we'll see some questions about scripting in the pipeline later on).
SCRIPT command
Redis provides the following script commands for controlling the scripting Subsystem (scripting Subsystem):
Script FLUSH: Clears all script caches
Script EXISTS: Checks whether the specified script exists in the script cache, based on the given script checksum
Script Load: Loads a script into the script cache, but does not run it immediately
Script kill: Kills scripts that are currently running
Pure function Script
One important requirement in scripting is that scripts should be written as pure functions.
In other words, the script should have the following properties:
For the same data set input, the Redis write commands that the script executes are always the same given the same parameters. The action performed by the script cannot depend on any hidden (non-explicit) data and cannot depend on the state in which the script is executed, or the script may change between different execution times, and it cannot rely on any external input from the I/O device.
Using system time, calling random commands like Randomkey, or using Lua's random number generator, like these, will cause the evaluation of the script not to produce the same results every time.
To ensure that the script conforms to the attributes described above, Redis does the following:
Lua does not have commands to access system time or other internal state
Redis returns an error that prevents such scripts from running: After executing random commands (such as Randomkey, Srandmember, or time), Redis commands that can modify the dataset are also executed. If the script simply performs a read-only operation, there is no such restriction. Note that random commands do not necessarily refer to commands with the RAND Word, and any non-deterministic commands are considered random commands, such as the Time command, which is a good example of this.
Whenever a command that returns an unordered element is called from a Lua script, the data that executes the command executes a slient dictionary order (lexicographical sorting) before it is returned to Lua. For example, because Redis's Set holds unordered elements, the smembers is executed directly in the Redis command-line client, and the returned elements are unordered, but if Redis.call ("Smembers", Keys[1]) is executed in the script, then the return are always ordered elements.
The pseudo-random number generation functions Math.random and math.randomseed of Lua are modified so that each time a new script is run, it always has the same seed value. This means that each time the script is run, the sequence of random numbers generated by Math.random is always the same as long as the math.randomseed is not used.
Although there are so many restrictions, users can use a simple technique to write scripts with random behavior (if they need to).
Let's say now we're going to write a Redis script that pops N random numbers out of a list. An example of Ruby writing is as follows:
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)])
Each run of this program produces a list with 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"
The Ruby program above only produces the same list each time, and the use is not too large. So how do you modify the script so that it is still a pure function (which meets the requirements of Redis), but each invocation can produce different random elements.
An easy way to do this is to add an extra parameter to the script that will be used as the seed value of the Lua random number generator, so that the script generates different list elements as soon as the script is passed into different seed.
The following is the modified script:
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))
Although the above script produces the same list element for the same seed (because it is a pure function), we can get a list with different random elements whenever we pass in a different seed each time we execute the script.
Seed propagates as a parameter when copying (replication link) and writing AOF files, ensuring that the seed is still updated in a timely manner when loading AOF files or satellite nodes (slave) to process scripts.
Note that the Redis implementation ensures that the output of the Math.random and Math.randomseed is independent of the system architecture that runs Redis, whether it's a 32-bit or 64-bit system, whether it's a small end (little endian) or a big-endian system, The output of the two functions is always the same.
Global variable Protection
To prevent unnecessary data leaks into the LUA environment, Redis scripts do not allow global variables to be created. If a script needs to maintain a state between multiple executions, it should use Redis key for state saving.
An attempt to access a global variable in a script (whether or not the variable exists) causes the script to stop, and the EVAL command returns an error:
redis 127.0.0.1:6379> eval ' a=10 ' 0 (Error) ERR error running script (call to F_933044DB579A2F 8FD45D8065F04A8D0249383E57): user_script:1: Script attempted to create global variable ' a '
Lua's debug tools, or other facilities, such as the Meta table for global protection of print (alter), can be used to implement global variable protection.
It is not difficult to implement global variable protection, but sometimes it is careless. Once the user has mixed Lua global state in the script, AOF Persistence and Replication (replication) will not be guaranteed, so do not use global variables.
One trick to avoiding introducing global variables is that all the variables used in the script are defined as local variables using the local keyword.
Library
The Redis built-in LUA interpreter loads the following LUA libraries:
Base
Table
String
Math
Debug
Cjson
Cmsgpack
Among other things, the Cjson library allows LUA to process JSON data very quickly, and in addition, it is the standard library for Lua.
Each Redis instance is guaranteed to load the libraries listed above, ensuring that each Redis script is running the same environment.
Using scripts to distribute Redis logs
In a Lua script, you can write a Redis log (log) by calling the Redis.log function:
Redis.log (loglevel, message)
Where the message parameter is a string, and the LogLevel parameter can be any one of the following values:
Redis. Log_debug
Redis. Log_verbose
Redis. Log_notice
Redis. Log_warning
These levels correspond to the levels of the standard Redis logs.
For script-emitting (emit) logs, only those logs that are the same or more advanced as the current Redis instance are set will be distributed.
The following is an example of a log:
Redis.log (Redis. Log_warning, "Something is wrong with this script.")
Executing the above function will produce this information:
[32343] 15:21:39 # Something is wrong with this script.
Sandbox (sandbox) and maximum execution time
Scripts should only be used to pass parameters and process Redis data, and it should not attempt to access external systems (such as file systems) or perform any system calls.
In addition, the script has a maximum execution time limit, its default value is 5 seconds, generally normal operation of the script can usually be done in a few milliseconds, not much time, the limit is mainly to prevent the programming error caused by the infinite loop set.
The length of the maximum execution time is controlled by the Lua-time-limit option (in milliseconds) and can be modified by editing the redis.conf file or by using the config GET and config SET commands.
When a script reaches its maximum execution time, it is not automatically terminated by Redis, because Redis must guarantee the atomicity of the script execution, and stopping the script from running means that it may leave the data set to the left of the unhandled data.
Therefore, when the script runs longer than the maximum execution time, the following actions are executed:
Redis records a script that is running out of time
Redis begins to re-accept command requests from other clients, but only SCRIPT KILL and SHUTDOWN nosave Two commands are processed, and for other command requests, the REDIS server simply returns a BUSY error.
You can kill a script that executes only read-only commands by using the script Kill command, because the read-only command does not modify the data, so killing the script does not compromise the integrity of the data
If the script has already executed a write command, the only action allowed is SHUTDOWN Nosave, which prevents the current data set from being written to disk by stopping the server
Evalsha in the pipeline (pipeline) context
Be especially careful when using the Evalsha command in the context of pipelining requests, because the order in which the commands are executed must be guaranteed in the pipeline.
Once the NOSCRIPT error occurs in the pipeline because of the Evalsha command, there is no way to re-execute the pipeline, otherwise the order of execution will be disrupted.
To prevent the above mentioned problems, the client library implementation should implement one of the following measures:
Always use the EVAL command in the pipeline
Check all the commands that are used in the pipeline, find the EVAL command in it, and use the script EXISTS command to check that the script you want to use is all stored in the cache. If all of the required scripts can be found in the cache, then you can safely change all the EVAL commands to the Evalsha command, otherwise the missing script will be added to the scripts LOAD command at the top of the pipeline.
Available Versions:
2.6.0+
complexity of Time:
EVAL and Evalsha can find the script to be executed in the O (1) complexity, and the remaining complexity depends on the script being executed.Evalsha SHA1 numkeys key [key ...] arg [arg ...]
The script that is cached in the server is evaluated based on the given SHA1 checksum code.
Caching the script to the server can be done through the script LOAD command.
Other places of this command, such as the way parameters are passed in, are the same as the EVAL command.
Available Versions:
2.6.0+
complexity of Time:
Depends on the complexity of the script.
redis> SCRIPT LOAD "return ' Hello Moto '" "
232fd51614574cf0867b83d384a5e898cfd24e5a"
redis> Evalsha " 232fd51614574cf0867b83d384a5e898cfd24e5a "0
" Hello moto "
Script EXISTS script [script ...]
Given the SHA1 checksum of one or more scripts, returns a list of 0 and 1 that indicates whether the checksum specified script has been saved in the cache.
For more information on using Redis to evaluate Lua scripts, see the EVAL command.
Available Versions:
2.6.0+
complexity of Time:
O (n), n is the number of the given SHA1 checksum.
return Value:
A list that contains 0 and 1, which indicates that the script does not exist in the cache, which means that the script is already in the cache.
The elements in the list and the given SHA1 checksum maintain correspondence, such as the value of the third element of the list represents the state of the third SHA1 checksum specified by the script in the cache.
redis> script Load "Return ' Hello Moto '" # Load a scripting
"232fd51614574cf0867b83d384a5e898cfd24e5a"
Redis > Script EXISTS 232fd51614574cf0867b83d384a5e898cfd24e5a
1) (integer) 1
redis> script flush # empty Cache
OK
redis> SCRIPT EXISTS 232fd51614574cf0867b83d384a5e898cfd24e5a
1) (integer) 0
SCRIPT FLUSH
Clears all Lua script caches.
For more information on using Redis to evaluate Lua scripts, see the EVAL command.
Available Versions:
2.6.0
Complexity of:
O (n), n is the number of scripts in the cache.
return Value:
Always return OK
redis> SCRIPT FLUSH
OK
SCRIPT KILL
Kills the currently running Lua script, which takes effect only if the script has not performed any write operations.
This command is used primarily to terminate scripts that run too long, such as a script that has an infinite loop due to a BUG, and so forth.
After script kill executes, the currently running scripts are killed, and the client executing the script exits from the blocking of the EVAL command and receives an error as the return value.
On the other hand, if a script that is currently running has already performed a write operation, it cannot be killed even if it executes script kill because it violates the atomic execution principle of Lua scripting. In this case, the only possible way is to use the SHUTDOWN nosave command to stop the script from running through the entire Redis process and to prevent incomplete (Half-written) information from being written to the database.
For more information on using Redis to evaluate Lua scripts, see the EVAL command.
Available Versions:
2.6.0
complexity of Time:
O (1)
return Value:
Execution successfully returns OK, otherwise an error is returned.
No script at execution time
Redis> SCRIPT KILL
(Error) ERR No scripts in execution right now.
When the script is successfully killed
redis> SCRIPT KILL
OK
(1.30s)
Attempt to kill a script that has performed a write operation, failed
redis> Script KILL
(Error) ERR Sorry The SCRIPT already executed write commands against the dataset. You can either wait for the script termination or kill the server in a hard-to-have using the SHUTDOWN nosave command.
(1.69s)
The following is an error returned to the client executing the script after the script has been killed:
Redis> EVAL "While true does end" 0
(Error) ERR error running script (call to F_694a5fe1ddb97a4c6a1bf299d9537c7d3d0f8 4E7): script killed by user with script KILL
... (5.00s)
Script LOAD Script
Script scripts are added to the script cache, but the script is not executed immediately.
The EVAL command will also add the script to the script cache, but it will immediately evaluate the input script.
If the given script is already in the cache, then do not do the action.
After the script is added to the cache, the script can be invoked with the Evalsha command, using the SHA1 checksum of the script.
Scripts can remain in the cache for an unlimited amount of time until the script FLUSH is executed.
For more information on using Redis to evaluate Lua scripts, see the EVAL command.
Available Versions:
2.6.0+
complexity of Time:
O (n), n is the length of the script, in bytes.
return Value:
SHA1 checksum for a given script
redis> SCRIPT LOAD "return ' Hello Moto '" "232fd51614574cf0867b83d384a5e898cfd24e5a" Redis > Evalsha 232fd51614574cf0867b83d384a5e898cfd24e5a 0 "Hello moto"