The background,

In the process of using Redis, I found that sometimes you need atomicity to operate the redis command, and the Lua script of Redis can achieve this function. For example: deduction inventory operation, limiting the flow operation and so on. Redis pipelining can execute a set of commands at a time, but it can’t do so if it requires some judgment based on the results of the previous step.

Second, use lua scripts

Redis uses the Lua 5.1 scripting specification, and we do not need to define Lua functions when writing scripts. You can’t use global variables and so on.

1. Lua script format and precautions

1, the format

EVAL script numkeys key [key …] arg [arg …]

127.0. 01.:6379> eval "return {KEYS[1],ARGV[1],ARGV[2]}" 1 key1 arg1 arg2
1) "key1"
2) "arg1"
3) "arg2"
127.0. 01.:6379>
Copy the code

2. Precautions

It is better to pass all the KEYS of redis operations in Lua through KEYS than to write them dead. Otherwise there may be problems in the case of Redis Cluster.

1, good writing

127.0. 01.:6379> eval "return redis.call('set',KEYS[1],'zhangsan')" 1 username
OK
127.0. 01.:6379> get username
"zhangsan"
Copy the code

The key operated by the redis command is obtained by KEYS.

2. Poor writing

127.0. 01.:6379> eval "return redis.call('set','username','zhangsan')" 0
OK
127.0. 01.:6379> get username
"zhangsan"
Copy the code

The redis command operates on keys that are written to death.

2. Load the script into Redis

Requirement: Define a Lua script here that returns the value of the input parameter +1.

Note:

When a Lua script is loaded into Redis, it is not executed immediately. Instead, it is cached and returns a SHA1 checksum, which can be executed later using EVALSHA.

Here we remember the hash value returned by the script after loading, which will be used in the next step.

127.0. 01.:6379> script load "return tonumber(KEYS[1])+1"
"ef424d378d47e7a8b725259cb717d90a4b12a0de"
127.0. 01.:6379>
Copy the code

3. Execute the Lua script

1. Perform eval

127.0. 01.:6379> eval "return tonumber(KEYS[1]) + 1" 1 100
(integer) 101
127.0. 01.:6379>
Copy the code

2. Run evalSHA

Ef424d378d47e7a8b725259cb717d90a4b12a0de has a value of step through the script on the load after load scripts.

127.0. 01.:6379> evalsha ef424d378d47e7a8b725259cb717d90a4b12a0de 1 100
(integer) 101
127.0. 01.:6379>
Copy the code

The benefit of performing over EVALsha is bandwidth savings. If our Lua script is long, sending the Lua script to the Redis server during execution may consume more bandwidth, while sending hash values will consume less bandwidth.

4. Check whether the script is in the redis server cache

127.0. 01.:6379> script load "return tonumber(KEYS[1])+1"
"ef424d378d47e7a8b725259cb717d90a4b12a0de"
127.0. 01.:6379> script exists ef424d378d47e7a8b725259cb717d90a4b12a0de
1) (integer) 1
127.0. 01.:6379> script exists not-exists-sha1
1) (integer) 0
127.0. 01.:6379>
Copy the code

5. Clear the script cache on the server

Note: we cannot clear the cache for a single script, only for all caches. In general, this is not necessary because even if there are a large number of scripts, it will not take up much server memory.


127.0. 01.:6379> script load "return tonumber(KEYS[1])+1"
"ef424d378d47e7a8b725259cb717d90a4b12a0de"
127.0. 01.:6379> script exists ef424d378d47e7a8b725259cb717d90a4b12a0de
1) (integer) 1
127.0. 01.:6379> script flush
OK
127.0. 01.:6379> script exists ef424d378d47e7a8b725259cb717d90a4b12a0de
1) (integer) 0
Copy the code

6. Kill running scripts

127.0. 01.:6379> script kill
Copy the code

Note:

  1. This command can kill only those that are runningOnly read the script.
  2. This command cannot be used to kill scripts that modify datashutdown nosaveCommand.
  3. script-executedDefault timeoutfor5 minutes, can be accessed throughredis.confProfile basedlua-time-limitModify configuration items.
  4. The script does not stop execution even when it reaches a timeout because this violates the atomicity of Lua scripts.

Lua and Redis data type conversion

There is a one-to-one conversion relationship between the Lua data type and the Redis data type. If you convert the Redis type to Lua and then to Redis, the result is the same as the initial value.

1. Type conversion

Redis to Lua conversion table.

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

2. Additional conversion rules

  1. Lua’s Boolean type. Lua’s True is converted to Redis’s 1

3. Three important rules

1. Number type

In Lua, there is only one type of number, and there is no difference between integer and floating point. If we return a floating point in Lua, we return an integer. If we return a floating point, we return it as a string.

127.0. 01.:6379> eval "The return of 3.98" 0
(integer) 3
127.0. 01.:6379> eval "Return '3.98'." " 0
"3.98"

Copy the code

2. Lua array exists nil

When Redis converts Lua arrays to the Redis protocol, the conversion stops if nil is encountered. Anything after nil is never returned.

127.0. 01.:6379> eval "return {1,2,'data',nil,'can not return value','vv'}" 0
1) (integer) 1
2) (integer) 2
3) "data"
127.0. 01.:6379>
Copy the code

3. Lua’s Table type contains build and values

An empty array of redis is returned in this case

127.0. 01.:6379> eval "return {key1 ='value1',key2='value2'}" 0
(empty array)
127.0. 01.:6379>
Copy the code

4. Output logs in lua scripts

This is usually useful when debugging our scripts.

redis.log(loglevel,message)
Copy the code

Value range of loglevel:

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

For example:

Five, a simple flow limiting case

1, requirements,

Within 1s, the maximum concurrency of the method can only be 5.

1s and 5 are passed as arguments.

2. Implementation steps

1. Write lua scripts


-- Prints the parameters passed in by the user
for i, v in pairs(KEYS) do
    redis.log(redis.LOG_NOTICE, "limit: key". i .."=". v)end
for i, v in pairs(ARGV) do
    redis.log(redis.LOG_NOTICE, "limit: argv". i .."=". v)end

-- Key of traffic limiting
local limitKey = tostring(KEYS[1])
-- Times of limiting traffic
local limit = tonumber(ARGV[1])
-- How long does it expire
local expireMs = tonumber(ARGV[2])

-- Number of times that have been executed
local current = tonumber(redis.call('get', limitKey) or '0')

Set a breakpoint
redis.breakpoint()

redis.log(redis.LOG_NOTICE, "limit key: ".tostring(limitKey) .. "In [".tostring(expireMs) .. "]ms has been accessed".tostring(current) .. "Times, maximum access:". limit .."Time")

- the current limit
if (current + 1 > limit) then
    return { true }
end

-- Access limit not reached
-- Number of visits +1
redis.call("incrby", limitKey, "1")
if (current == 0) then
    -- Set the expiration time
    redis.call("pexpire", limitKey, expireMs)
end

return { false }

Copy the code

2. Lua script is executed in the program

Full code: gitee.com/huan1993/sp…

Debug lua scripts

Once we’ve written a Lua script, what if something goes wrong during execution? Here’s how to debug lua scripts.

1. Several small commands in the Lua script

Make a breakpoint in the script

redis.breakpoint()
Copy the code

2. Breakpoint debugging

1. Run the command

redis-cli --ldb --eval limit.lua invoked , 1 1000 limity. lua needs to debug the Lua file invoked is the value 1 passed to the KEYS in the Lua script and 1000 is the value passed to the ARGV in the Lua script, separating the values of KEYS and ARGVCopy the code

2. Some debug instructions

  • help: Lists available debug directives
  • sn: Runs to the current line and stops (the current line has not yet been executed)
  • c: Run to the next breakpoint, that is, run to the lua script existsredis.breakpoint()Place of method
  • list: lists some source code around the current line
  • p: Prints out the values of all local variables
  • p <var>: Prints the value of a specific local variable
  • r: Run the redis command
-- eg:
r set key value 
r get key
Copy the code

3. Debug running results

Vii. Reference documents

  1. redis.io/topics/ldb
  2. Redis. IO/commands/ev…