Go Redis Lua 脚本

redis.Script

go-redis 支持 Lua 脚本 redis.Script在新窗口打开,在 这里在新窗口打开 查看使用示例。

在下面的示例中,Lua 脚本使用 GET, SET 实现了 INCRBY命令:

var incrBy = redis.NewScript(`
local key = KEYS[1]
local change = ARGV[1]

local value = redis.call("GET", key)
if not value then
  value = 0
end

value = value + change
redis.call("SET", key, value)

return value
`)

你可以像这样运行脚本:

keys := []string{"my_counter"}
values := []interface{}{+1}
num, err := incrBy.Run(ctx, rdb, keys, values...).Int()

在 go-redis 中,使用 EVALSHA在新窗口打开 执行脚本,如果 SHA 不存在,则使用 EVAL在新窗口打开

你可以在 GitHub在新窗口打开 中找到上面的示例。更多的例子,你可以参照 redis_rate在新窗口打开,它实现了一个漏桶算法的 限流器

Lua 和 Go 类型

下面是 Lua 和 Go 语言的类型对照表,Lua 的 number 是一个浮点型数字,用于存储整数和浮点数,在 Lua 中不区分整数和浮点数,但 Redis 总是将 Lua 数字转换为舍去小数部分的整数,例如 3.14 变成 3,如果要返回浮点值,将其作为字符串返回并用 Go 解析成 float64。

Lua returnGo interface{}
number (float64)int64 (舍弃小数)
stringstring
falseredis.Nil error
trueint64(1)
{ok = "status"}string("status")
{err = "error message"}errors.New("error message")
{"foo", "bar"}[]interface{}{"foo", "bar"}
{foo = "bar", bar = "baz"}[]interface{}{} (不支持)

调试 Lua 脚本

调试 Lua 脚本的最简单方法是使用 redis.log 将消息写入 Redis 日志文件或 redis-server 输出的函数:

redis.log(redis.LOG_NOTICE, "key", key, "change", change)

你也可以参照 Redis Lua 脚本调试器在新窗口打开

传递多个值

你可以 for 在 Lua 中使用循环来迭代传递的值,例如对数字求和:

local key = KEYS[1]

local sum = redis.call("GET", key)
if not sum then
  sum = 0
end

local num_arg = #ARGV
for i = 1, num_arg do
  sum = sum + ARGV[i]
end

redis.call("SET", key, sum)

return sum

结果:

sum, err := sum.Run(ctx, rdb, []string{"my_sum"}, 1, 2, 3).Int()
fmt.Println(sum, err)
// Output: 6 nil

循环 continue

Lua 循环中不支持 continue 语法,不过你可以使用嵌套 repeat 循环和 break 语句来模拟它:

local num_arg = #ARGV

for i = 1, num_arg do
repeat

  if true then
    do break end -- continue
  end

until true
end

错误处理

默认情况下,redis.call 函数会引发 Lua 错误并停止服务,如果要捕获错误,需要使用 redis.pcall 返回带有 err 字段的 Lua table:

local result = redis.pcall("rename", "foo", "bar")
if type(result) == 'table' and result.err then
  redis.log(redis.LOG_NOTICE, "rename failed", result.err)
end

要返回自定义错误,请使用 Lua table:

return {err = "error message goes here"}