在上一篇文章中,我们探讨了 Redis 的 Pipeline 操作及其在高效处理大量命令时的应用。今天,我们将深入了解 Redis 的脚本功能,包括如何在 Go 语言中使用 go-redis 库执行脚本。我们将介绍 Redis 的脚本功能、eval 和 function 操作的常见场景,并详细讲解 go-redis 中相关的方法及示例代码。
👉 点击查看《go-redis使用指南》系列文章目录
在《go-redis使用指南》系列文章中,我们将详细介绍如何在 Golang 项目中使用 redis/go-redis 库与 Redis 进行交互。以下是该系列文章的全部内容:
- Golang 操作 Redis:快速上手 - go-redis 使用指南
- Golang 操作 Redis:连接设置与参数详解 - go-redis 使用指南
- Golang 操作 Redis:基础的字符串键值操作 - go-redis 使用指南
- Golang 操作 Redis:如何设置 key 的过期时间 - go-redis 使用指南
- Golang 操作 Redis:Hash 哈希数据类型操作用法 - go-redis 使用指南
- Golang 操作 Redis:Set 集合数据类型操作用法 - go-redis 使用指南
- Golang 操作 Redis:为 Hash 中的字段设置过期时间 - go-redis 使用指南
- Golang 操作 Redis:List 列表数据类型操作用法 - go-redis 使用指南
- Golang 操作 Redis:SortedSet 有序集合数据类型操作用法 - go-redis 使用指南
- Golang 操作 Redis:bitmap 数据类型操作用法 - go-redis 使用指南
- Golang 操作 Redis:事务处理操作用法 - go-redis 使用指南
- Golang 操作 Redis:地理空间数据类型操作用法 - go-redis 使用指南
- Golang 操作 Redis:HyperLogLog 操作用法 - go-redis 使用指南
- Golang 操作 Redis:Pipeline 操作用法 - go-redis 使用指南
- Golang 操作 Redis:PubSub发布订阅用法 - go-redis 使用指南
- Golang 操作 Redis:布隆过滤器(Bloom Filter)操作用法 - go-redis 使用指南
- Golang 操作 Redis:Cuckoo Filter操作用法 - go-redis 使用指南
- Golang 操作 Redis:Stream操作用法 - go-redis 使用指南
Redis script 脚本操作:eval 和 function 简介
Redis 提供了强大的脚本功能,允许用户使用 Lua 脚本在 Redis 服务器端执行复杂的操作。这些脚本可以大大简化客户端与 Redis 之间的数据交换,并提升操作效率。Redis 的脚本功能主要包括两大类:eval 和 function。
- eval: 允许用户直接在 Redis 服务器上执行 Lua 脚本。它支持多种操作,如对数据进行计算、更新或条件判断。
- function: 从 Redis 7.0 开始引入的功能,允许用户定义和使用更复杂的 Lua 函数。这些函数可以被加载到 Redis 服务器中,并用于执行复杂的操作。
Redis script 脚本使用场景
- eval:
- 执行复杂的原子操作,避免多次网络往返。
- 处理需要服务器端逻辑的数据更新和计算。
- 快速实现特定逻辑而无需在客户端实现。
- function:
- 定义可重用的服务器端函数,提高脚本执行的灵活性。
- 支持脚本的版本管理和异步处理。
go-redis 中 script 操作的方法
以下是 go-redis 提供的与 script 相关的操作方法:
- Eval - 执行 Lua 脚本
- EvalSha - 根据脚本的 SHA1 值执行 Lua 脚本
- EvalRO - 执行只读的 Lua 脚本(需要 Redis 7.0 以上版本)
- EvalShaRO - 根据 SHA1 值执行只读的 Lua 脚本(需要 Redis 7.0 以上版本)
- ScriptExists - 检查脚本是否已经存在于 Redis 中
- ScriptFlush - 清除所有脚本缓存
- ScriptKill - 杀死当前正在执行的脚本
- ScriptLoad - 将脚本加载到 Redis 并返回 SHA1 值
- FunctionLoad - 加载 Redis Function
- FunctionLoadReplace - 替换已存在的 Redis Function
- FunctionDelete - 删除指定的 Redis Function
- FunctionFlush - 清除所有已加载的 Redis Functions
- FunctionKill - 杀死当前正在执行的 Function
- FunctionFlushAsync - 异步清除 Redis Functions
- FunctionList - 列出当前所有加载的 Redis Functions
- FunctionDump - 导出 Redis Functions
- FunctionRestore - 恢复导出的 Redis Functions
- FunctionStats - 获取 Redis Functions 的统计信息
- FCall - 执行指定的 Redis Function
- FCallRo - 执行只读的 Redis Function
go-redis script 操作方法详细讲解及示例代码
go-redis Eval 脚本操作示例代码
package main
import (
"context"
"fmt"
"log"
"github.com/redis/go-redis/v9"
)
func main() {
ctx := context.Background()
rdb := redis.NewClient(&redis.Options{
Addr: "localhost:6380",
})
// 清除所有脚本缓存
if _, err := rdb.ScriptFlush(ctx).Result(); err != nil {
log.Fatalf("Error flushing scripts: %v", err)
}
// 设置初始键值
rdb.Set(ctx, "key1", 10, 0)
rdb.Set(ctx, "key2", 20, 0)
// Lua 脚本,计算两个键值的和并返回
script := `
local val1 = redis.call("GET", KEYS[1])
local val2 = redis.call("GET", KEYS[2])
val1 = tonumber(val1) or 0
val2 = tonumber(val2) or 0
return val1 + val2
`
keys := []string{"key1", "key2"}
args := []interface{}{}
// 将脚本加载到 Redis 并获取 SHA1 值
sha1, err := rdb.ScriptLoad(ctx, script).Result()
if err != nil {
log.Fatalf("Error loading script: %v", err)
}
fmt.Printf("Loaded script SHA1: %s\n", sha1)
// 检查脚本是否已经存在
exists, err := rdb.ScriptExists(ctx, sha1).Result()
if err != nil || !exists[0] {
log.Fatalf("Script does not exist: %v", err)
}
fmt.Println("Script exists in Redis")
// 执行脚本
result, err := rdb.Eval(ctx, script, keys, args...).Result()
if err != nil {
log.Fatalf("Error executing script: %v", err)
}
fmt.Printf("Result of Eval: %v\n", result)
// 根据 SHA1 值执行脚本
resultSha, err := rdb.EvalSha(ctx, sha1, keys, args...).Result()
if err != nil {
log.Fatalf("Error executing script by SHA1: %v", err)
}
fmt.Printf("Result of EvalSha: %v\n", resultSha)
// 执行只读的 Lua 脚本 (需要 Redis 7.0 以上)
resultRO, err := rdb.EvalRO(ctx, script, keys, args...).Result()
if err != nil {
log.Fatalf("Error executing read-only script: %v", err)
}
fmt.Printf("Result of EvalRO: %v\n", resultRO)
// 根据 SHA1 值执行只读的 Lua 脚本 (需要 Redis 7.0 以上)
resultShaRO, err := rdb.EvalShaRO(ctx, sha1, keys, args...).Result()
if err != nil {
log.Fatalf("Error executing read-only script by SHA1: %v", err)
}
fmt.Printf("Result of EvalShaRO: %v\n", resultShaRO)
// 杀死当前正在执行的脚本(若有)
if err := rdb.ScriptKill(ctx).Err(); err != nil && err != redis.Nil {
log.Fatalf("Error killing script: %v", err)
}
fmt.Println("Killed running script (if any)")
}
执行代码,输出:
Loaded script SHA1: 4f542ba45ede2410735a8a396120ade717f1a142
Script exists in Redis
Result of Eval: 30
Result of EvalSha: 30
Result of EvalRO: 30
Result of EvalShaRO: 30
2024/08/26 18:27:49 Error killing script: NOTBUSY No scripts in execution right now.
exit status 1
go-redis Function 脚本操作示例代码
package main
import (
"context"
"fmt"
"log"
"github.com/redis/go-redis/v9"
)
func main() {
ctx := context.Background()
rdb := redis.NewClient(&redis.Options{
Addr: "localhost:6380",
})
// 清除所有已加载的 Redis Functions
if _, err := rdb.FunctionFlush(ctx).Result(); err != nil {
log.Fatalf("Error flushing functions: %v", err)
}
// 加载一个纯读取操作的 Redis Function
functionCode := `#!lua name=mylib
redis.register_function('get_sum', function(keys, args)
local sum = 0
for i, key in ipairs(keys) do
sum = sum + tonumber(redis.call("GET", key))
end
return sum
end)
`
if _, err := rdb.FunctionLoad(ctx, functionCode).Result(); err != nil {
log.Fatalf("Error loading function: %v", err)
}
fmt.Println("Function 'get_sum' loaded successfully")
// 列出所有已加载的 Redis Functions
functions, err := rdb.FunctionList(ctx, redis.FunctionListQuery{}).Result()
if err != nil {
log.Fatalf("Error listing functions: %v", err)
}
fmt.Printf("Loaded functions: %v\n", functions)
// 执行加载的 Redis Function
result, err := rdb.FCall(ctx, "get_sum", []string{"key1", "key2"}).Result()
if err != nil {
log.Fatalf("Error executing function: %v", err)
}
fmt.Printf("Result of FCall 'get_sum': %v\n", result)
// 替换已存在的 Redis Function
newFunctionCode := `#!lua name=mylib
redis.register_function('get_sum', function(keys, args)
return "xxx"
end)
`
if _, err := rdb.FunctionLoadReplace(ctx, newFunctionCode).Result(); err != nil {
log.Fatalf("Error replacing function: %v", err)
}
fmt.Println("Function 'get_sum' replaced successfully")
// 导出 Redis Functions
dump, err := rdb.FunctionDump(ctx).Result()
if err != nil {
log.Fatalf("Error dumping functions: %v", err)
}
fmt.Println("Functions dumped successfully")
// 删除 Redis Function
if _, err := rdb.FunctionDelete(ctx, "mylib").Result(); err != nil {
log.Fatalf("Error deleting function: %v", err)
}
fmt.Println("Function 'get_sum' deleted successfully")
// 异步清除 Redis Functions
if _, err := rdb.FunctionFlushAsync(ctx).Result(); err != nil {
log.Fatalf("Error flushing functions asynchronously: %v", err)
}
fmt.Println("Functions flushed asynchronously")
// 恢复导出的 Redis Functions
if _, err := rdb.FunctionRestore(ctx, dump).Result(); err != nil {
log.Fatalf("Error restoring functions: %v", err)
}
fmt.Println("Functions restored successfully")
// 获取 Redis Functions 的统计信息
stats, err := rdb.FunctionStats(ctx).Result()
if err != nil {
log.Fatalf("Error getting function stats: %v", err)
}
fmt.Printf("Function stats: %v\n", stats)
// 杀死当前正在执行的 Function(若有)
if err := rdb.FunctionKill(ctx).Err(); err != nil && err != redis.Nil {
log.Fatalf("Error killing function: %v", err)
}
fmt.Println("Killed running function (if any)")
}
执行代码,输出结果:
Function 'get_sum' loaded successfully
Loaded functions: [{mylib LUA [{get_sum []}] }]
Result of FCall 'get_sum': 30
Function 'get_sum' replaced successfully
Functions dumped successfully
Function 'get_sum' deleted successfully
Functions flushed asynchronously
Functions restored successfully
Function stats: {[{LUA 1 1}] false { [] 0} []}
2024/08/26 20:46:27 Error killing function: NOTBUSY No scripts in execution right now.
exit status 1
结语
本文详细介绍了在 Golang 中使用 go-redis 执行 Redis script 脚本的操作方法,并提供了相关示例代码。通过这些方法,你可以更加灵活高效地在 Redis 中执行复杂操作。
希望这篇文章能帮助你更好地理解和使用 go-redis,点击 go-redis 使用指南 可查看更多相关教程!