go语言redis中使用lua脚本

在 Redis 中使用 Lua 脚本可以实现原子性操作、减少网络开销以及提高执行效率。

Redis 执行 Lua 脚本的原理

Redis 内置了 Lua 解释器,能够直接在服务器端执行 Lua 脚本。当执行 Lua 脚本时,Redis 会将脚本作为一个整体执行,保证脚本执行期间不会被其他命令插入,从而实现原子性操作。

基本使用方法

使用EVAL命令执行 Lua 脚本

EVAL命令用于在 Redis 中执行 Lua 脚本,其基本语法如下:

css 复制代码
EVAL script numkeys key [key ...] arg [arg ...]
  • script:要执行的 Lua 脚本。
  • numkeys:脚本中使用的键名参数的数量。
  • key [key ...]:键名参数列表。
  • arg [arg ...]:其他参数列表。
css 复制代码
redis-cli EVAL "return redis.call('SET', KEYS[1], ARGV[1])" 1 mykey myvalue
  • 脚本return redis.call('SET', KEYS[1], ARGV[1])的作用是调用 Redis 的SET命令,将ARGV[1]的值设置到KEYS[1]对应的键上。
  • 1表示脚本中使用的键名参数的数量为 1。
  • mykey是键名参数。
  • myvalue是其他参数。

使用EVALSHA命令执行预加载的脚本

为了避免每次执行脚本时都传输整个脚本内容,可以使用SCRIPT LOAD命令将脚本加载到 Redis 中,得到一个 SHA1 哈希值,然后使用EVALSHA命令通过哈希值来执行脚本。

bash 复制代码
# 加载脚本并获取SHA1哈希值
redis-cli SCRIPT LOAD "return redis.call('SET', KEYS[1], ARGV[1])"
# 输出示例:"a1b2c3d4e5f6..."

# 使用EVALSHA命令执行脚本
redis-cli EVALSHA "a1b2c3d4e5f6..." 1 mykey myvalue

在 Go 语言中使用 Lua 脚本操作 Redis

以下是一个使用 Go 语言和go-redis库执行 Lua 脚本的示例

go 复制代码
package main

import (
    "context"
    "fmt"
    "github.com/go-redis/redis/v8"
)

func main() {
    rdb := redis.NewClient(&redis.Options{
        Addr:     "localhost:6379",
        Password: "",
        DB:       0,
    })

    ctx := context.Background()

    // 定义Lua脚本
    script := `
    local key = KEYS[1]
    local value = ARGV[1]
    return redis.call('SET', key, value)
    `

    // 执行Lua脚本
    result, err := rdb.Eval(ctx, script, []string{"mykey"}, "myvalue").Result()
    if err != nil {
        fmt.Println("Failed to execute Lua script:", err)
        return
    }
    fmt.Println("Script execution result:", result)
}    

Lua 脚本中的 Redis API

在 Lua 脚本中,可以使用redis.callredis.pcall函数来调用 Redis 命令:

  • redis.call:调用 Redis 命令,如果命令执行出错,脚本会终止并返回错误信息。
  • redis.pcall:调用 Redis 命令,如果命令执行出错,脚本不会终止,而是返回一个包含错误信息的 Lua 表。

call示例:

go 复制代码
package main

import (
    "context"
    "fmt"
    "github.com/go-redis/redis/v8"
)

func main() {
    rdb := redis.NewClient(&redis.Options{
        Addr:     "localhost:6379",
        Password: "",
        DB:       0,
    })

    ctx := context.Background()

    // 定义Lua脚本
    script := `
    redis.call('SET', KEYS[1], ARGV[1])
    local value = redis.call('GET', KEYS[1])
    return value
    `

    // 执行Lua脚本
    result, err := rdb.Eval(ctx, script, []string{"mykey"}, "myvalue").Result()
    if err != nil {
        fmt.Println("Failed to execute Lua script:", err)
        return
    }
    fmt.Println("Script execution result:", result)
}    

pcall示例:

lua 复制代码
package main

import (
    "context"
    "fmt"
    "github.com/go-redis/redis/v8"
)

func main() {
    rdb := redis.NewClient(&redis.Options{
        Addr:     "localhost:6379",
        Password: "",
        DB:       0,
    })

    ctx := context.Background()

    // 定义包含 redis.pcall 的 Lua 脚本
    script := `
    local result = redis.pcall('GET', 'nonexistent_key')
    if type(result) == 'table' and result.err then
        return 'Error: ' .. result.err
    else
        return result
    end
    `

    // 执行 Lua 脚本
    result, err := rdb.Eval(ctx, script, []string{}).Result()
    if err != nil {
        fmt.Println("Failed to execute Lua script:", err)
        return
    }
    fmt.Println("Script execution result:", result)
}    

注意事项

  • 原子性:Lua 脚本在 Redis 中是原子执行的,但要注意脚本的执行时间不宜过长,否则会阻塞其他客户端的请求。
  • 性能:合理使用 Lua 脚本可以减少网络开销和提高执行效率,但如果脚本过于复杂,可能会影响性能。
  • 数据类型:Lua 脚本中的数据类型和 Redis 的数据类型需要进行适当的转换。

Lua 注释

单行注释

在 Lua 里,使用两个连字符 -- 开启单行注释。在 -- 之后直到该行结束的内容都会被视为注释,不会被 Lua 解释器执行。

lua 复制代码
-- 这是一个单行注释
local num = 10 -- 定义一个变量 num 并赋值为 10

多行注释

多行注释以 --[[ 开头,以 ]] 结尾。在这两个标记之间的所有内容都属于注释,无论跨多少行。

lua 复制代码
--[[
这是一个多行注释
可以包含很多行内容
用于对代码块进行详细说明
]]
local str = "Hello, World!"

Lua 基本语法

变量

Lua 是动态类型语言,变量不需要预先声明类型。常见的变量类型有 nilbooleannumberstringtablefunctionthreaduserdata

  • 全局变量 :默认情况下,变量都是全局变量。未赋值的全局变量值为 nil
ini 复制代码
-- 定义一个全局变量
message = "Hello, Lua!"
print(message)
  • 局部变量 :使用 local 关键字声明局部变量,其作用域仅限于声明它的代码块。
dart 复制代码
do
    local num = 20
    print(num) -- 可以在代码块内访问
end
-- print(num) -- 这里会出错,因为 num 是局部变量,超出作用域

数据类型

1. 布尔类型(boolean)

只有两个值:truefalse。在条件判断中,除了 falsenil 被视为假,其他值都被视为真。

lua 复制代码
local isEnabled = true
if isEnabled then
    print("Enabled")
end
2. 数字类型(number)

Lua 中的数字类型默认是双精度浮点数。

ini 复制代码
local num1 = 10
local num2 = 3.14
print(num1 + num2)
3. 字符串类型(string)

可以使用单引号或双引号来表示字符串。

lua 复制代码
local str1 = 'Hello'
local str2 = "World"
print(str1 .. " " .. str2) -- 使用 .. 进行字符串拼接
4. 表类型(table)

Lua 中最强大的数据类型,可以当作数组、字典等使用。表使用花括号 {} 来创建。

lua 复制代码
-- 当作数组使用
local fruits = {"apple", "banana", "cherry"}
print(fruits[1]) -- 索引从 1 开始

-- 当作字典使用
local person = {name = "John", age = 30}
print(person.name)

控制结构

1. if-else 语句
lua 复制代码
local score = 80
if score >= 90 then
    print("A")
elseif score >= 80 then
    print("B")
else
    print("C")
end
2. for 循环
  • 数值型 for 循环:用于遍历一个数值范围。
java 复制代码
for i = 1, 5 do
    print(i)
end
  • 泛型 for 循环:用于遍历迭代器,如数组或表。
lua 复制代码
local fruits = {"apple", "banana", "cherry"}
for index, value in ipairs(fruits) do
    print(index, value)
end
3. while 循环
lua 复制代码
local count = 0
while count < 3 do
    print(count)
    count = count + 1
end

函数

函数使用 function 关键字定义,可以有参数和返回值。

sql 复制代码
function add(a, b)
    return a + b
end

local result = add(3, 5)
print(result)

相关推荐
ShiXZ2134 小时前
Redis docker-compose 部署指南
redis·docker·eureka
ArixBit6 小时前
用 Go 手撸 Agent 框架 #11:工具从哪里来——内置工具、MCP 与 Skills
go·agent
YDS8297 小时前
大营销平台 —— 活动SKU库存扣减业务及其一致性处理
java·spring boot·redis·rabbitmq·ddd
程序员爱钓鱼9 小时前
为什么学习 Go?Go 能做什么?
后端·面试·go
爬也要爬着前进1 天前
redis主从搭建
数据库·redis·缓存
用户095367515831 天前
在系统的学习 redis 前的疑惑
redis·go
吴声子夜歌1 天前
Redis 3.x——集群故障转移
java·数据库·redis·集群
明豆1 天前
Redis 分布式缓存生产实战
redis·分布式·缓存
名字还没想好☜1 天前
Go 用 errgroup 管理并发子任务:错误收敛、取消传播与限流
开发语言·后端·golang·go·并发
海兰1 天前
【高速缓存】RedisVL 指南:从向量搜索到 AI 应用落地
人工智能·redis