Golang 操作 Redis:eval/functions 执行 lua script 脚本操作用法 - go-redis 使用指南

文章目录

在上一篇文章中,我们探讨了 Redis 的 Pipeline 操作及其在高效处理大量命令时的应用。今天,我们将深入了解 Redis 的脚本功能,包括如何在 Go 语言中使用 go-redis 库执行脚本。我们将介绍 Redis 的脚本功能、eval 和 function 操作的常见场景,并详细讲解 go-redis 中相关的方法及示例代码。

👉 点击查看《go-redis使用指南》系列文章目录

在《go-redis使用指南》系列文章中,我们将详细介绍如何在 Golang 项目中使用 redis/go-redis 库与 Redis 进行交互。以下是该系列文章的全部内容:

  1. Golang 操作 Redis:快速上手 - go-redis 使用指南
  2. Golang 操作 Redis:连接设置与参数详解 - go-redis 使用指南
  3. Golang 操作 Redis:基础的字符串键值操作 - go-redis 使用指南
  4. Golang 操作 Redis:如何设置 key 的过期时间 - go-redis 使用指南
  5. Golang 操作 Redis:Hash 哈希数据类型操作用法 - go-redis 使用指南
  6. Golang 操作 Redis:Set 集合数据类型操作用法 - go-redis 使用指南
  7. Golang 操作 Redis:为 Hash 中的字段设置过期时间 - go-redis 使用指南
  8. Golang 操作 Redis:List 列表数据类型操作用法 - go-redis 使用指南
  9. Golang 操作 Redis:SortedSet 有序集合数据类型操作用法 - go-redis 使用指南
  10. Golang 操作 Redis:bitmap 数据类型操作用法 - go-redis 使用指南
  11. Golang 操作 Redis:事务处理操作用法 - go-redis 使用指南
  12. Golang 操作 Redis:地理空间数据类型操作用法 - go-redis 使用指南
  13. Golang 操作 Redis:HyperLogLog 操作用法 - go-redis 使用指南
  14. Golang 操作 Redis:Pipeline 操作用法 - go-redis 使用指南
  15. Golang 操作 Redis:PubSub发布订阅用法 - go-redis 使用指南
  16. Golang 操作 Redis:布隆过滤器(Bloom Filter)操作用法 - go-redis 使用指南
  17. Golang 操作 Redis:Cuckoo Filter操作用法 - go-redis 使用指南
  18. Golang 操作 Redis:Stream操作用法 - go-redis 使用指南
golang redis 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 使用指南 可查看更多相关教程!


也可以看看