The Redis method is manipulated through Lua scripting in the Go language

  • 2020-06-15 09:13:39
  • OfStack

preface

To reduce the cost of communicating with Redis in one of my base libraries, I encapsulated a series 1 operation in an LUA script, using the EVAL command provided by Redis to simplify the operation.

What EVAL can offer:

You can encapsulate several operations in the LUA script. If you have multiple Redis instructions, you can simply send all the parameters to Redis1 to get the result Redis guarantees that no other commands will be inserted during the Lua script, providing atomicity like database transaction 1 Redis will cache the script according to the SHA value of the script. The cached script does not need to transfer the Lua code again, which reduces the communication cost. In addition, if you change the Lua script in your own code, Redis will definitely use the latest code when executing.

Importing a common Go library such as "github.com/go-ES27en /redis" implements the following code.

Generate 1 section of Lua script


// KEYS: key for record
// ARGV: fieldName, currentUnixTimestamp, recordTTL
// Update expire field of record key to current timestamp, and renew key expiration
var updateRecordExpireScript = redis.NewScript(`
redis.call("EXPIRE", KEYS[1], ARGV[3])
redis.call("HSET", KEYS[1], ARGV[1], ARGV[2])
return 1
`)

When this variable is created, the Lua code is not executed and there is no need to have an existing Redis connection.

Lua script support provided by Redis. By default, there are two arrays, KEYS and ARGV. KEYS represents some key values passed in when the script is running, and ARGV represents some parameters passed in. Since the Lua code needs to be kept clean and hard to read, it's best to write some comments for these parameters

Note: the above 1 section of code using ' 'cross line,' line, although a blank carriage return, will be considered as 1 line, do not read the wrong code line number.

Run 1 section of the Lua script


 updateRecordExpireScript.Run(c.Client, []string{recordKey(key)}, 
         expireField,
         time.Now().UTC().UnixNano(), int64(c.opt.RecordTTL/time.Second)).Err()

At runtime, Run will first attempt to run the script through the cache via EVALSHA. If there is no cache, run with EVAL and the Lua script is passed in as a whole to Redis.

Limitations of the Lua script

Redis does not offer to introduce additional packages, such as os, etc. Only the package redis is available. The Lua script will run in one function and all variables must be declared using local When return returns multiple values, Redis will only give you the first one

Type restrictions in scripts

When the script returns nil, what you get in Go is err = redis.Nil (Same value as Get could not be found) When the script returns false, nil in Go, and true in Go, int64 type 1 Script returns {"ok":... }, Go is the status type of redis (true/false) Script returns {"err":... When}, the value of err is obtained in Go, which can also be passed return redis.error_reply("My Error") a When the script returns type number, Go gets type int64 The value 1 in the KEYS/ARGV passed into the script is of type string and should be converted to a numeric type using to_number

What happens if the script takes a long time to run?

During the Lua script run, in order to avoid data contamination by other operations, other commands will not be able to be executed during this period, 1 will not be able to continue executing other requests until completion of execution. When the Lua script runs longer than lua-ES108en-ES109en, other requests will receive Busy errors unless they are SCRIPT KILL (kill the script) or SHUTDOWN NOSAVE (close Redis without saving the result)

For more on Go, please refer to the following address. I have provided a summary of my experience with Go. https: / / redis io/commands/eval

A more "complex" script that requires a longer lifetime when getting an key value if the value is accessed more often. In addition, the update time is compared. If no update is needed, the value retrieved is returned directly; otherwise, redis.Nil is returned


// KEYS: rec:key, key
// ARGV: currentUnixTimestamp, hotHit, recordTTL, ttl
// When there's a hit,
var fetchRecordScript = redis.NewScript(local value = redis.call("GET", KEYS[2]) if(value == nil) then return nil end local hit = redis.call("HINCRBY", KEYS[1], "hit", 1) redis.call("EXPIRE", KEYS[1], ARGV[3]) local minHotHit = tonumber(ARGV[2]) local keyTTL = tonumber(ARGV[4]) if(hit > minHotHit)then keyTTL = keyTTL * 2 end redis.call("EXPIRE", KEYS[2], keyTTL) local expire = tonumber(redis.call("HGET", KEYS[1], "expire")) local unixTime = tonumber(ARGV[1]) if(expire == nil or expire < unixTime) then return nil else return value end)
// KEYS: key for record
// ARGV: fieldName, currentUnixTimestamp, recordTTL
// Update expire field of record key to current timestamp, and renew key expiration
var updateRecordExpireScript = redis.NewScript(redis.call("EXPIRE", KEYS[1], ARGV[3]) redis.call("HSET", KEYS[1], ARGV[1], ARGV[2]) return 1)

conclusion


Related articles: