57、Go语言基础 - 数据库操作 - Go 操作 Redis

作者: 温新

分类: 【Go基础】

阅读: 309

时间: 2023-12-05 00:50:34

hi,我是温新

在 Go 中操作 Redis,可以使用第三方库如 "github.com/go-redis/redis"。

准备工作

安装第三库

go get github.com/go-redis/redis
go get github.com/go-redis/redis/v8
go get golang.org/x/net/context

连接 Redis

redis.NewClient 函数是 Go Redis 库中用于创建 Redis 客户端连接的函数。

func NewClient(opt *Options) *Client

参数说明

  • opt:一个指向 Options 结构体的指针,包含了 Redis 客户端连接的配置选项。

Options 结构体的定义如下:

type Options struct {
    Addr     string  // Redis 服务器地址 (例如:"localhost:6379")
    Network  string  // 网络协议 ("tcp"、"unix" 等)
    Password string  // Redis 密码,如果没有密码则为空字符串
    DB       int     // Redis 数据库编号
    ...
    // 其他配置选项如 DialTimeout、ReadTimeout、WriteTimeout 等
}

代码案例

package main

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

func main() {
	// 创建一个 Redis 客户端
	client := redis.NewClient(&redis.Options{
		Addr:     "192.168.31.90:6379",
		Password: "",
		DB:       0,
	})

	// 检查连接是否成功
	pong, err := client.Ping().Result()
	if err != nil {
		fmt.Println("Redis连接失败:", err)
		return
	}
	fmt.Println("Redis连接成功:", pong)

	// 关闭 Redis 连接
	err = client.Close()
	if err != nil {
		fmt.Println("关闭Redis连接失败:", err)
		return
	}
	fmt.Println("Redis连接已关闭")
}

String 类型 set & get

set

func (c *Client) Set(key string, value interface{}, expiration time.Duration) *StatusCmd
  • key:要设置的键名。
  • value:要设置的值。
  • expiration:可选参数,设置键的过期时间。如果不需要设置过期时间,可以将其设为0。

get

func (c *Client) Get(key string) *StringCmd

key:要获取值的键名。

代码案例

package main

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

func main() {
	client := redis.NewClient(&redis.Options{
		Addr:     "192.168.31.90:6379",
		Password: "",
		DB:       0,
	})
	// 检查连接是否成功
	_, err := client.Ping().Result()
	if err != nil {
		fmt.Println("Redis连接失败:", err)
		return
	}

	// 使用 set 设置键值对
	err = client.Set("goname", "王美丽", 0).Err()
	if err != nil {
		fmt.Println("设置键值对失败", err)
		return
	}

	// 使用 get 获取取
	value, err := client.Get("goname").Result()
	if err == redis.Nil {
		fmt.Println("key 不存在")
	} else if err != nil {
		fmt.Println("获取键值对的值失败:", err)
	} else {
		fmt.Println("name:", value)
	}

	// 设置过期时间为 10 秒的键值对
	err = client.Set("gocity", "武汉", 10*time.Second).Err()
	if err != nil {
		fmt.Println("设置键值对失败:", err)
		return
	}

	// 获取带过期时间的值
	city, err := client.Get("gocity").Result()
	if err == redis.Nil {
		fmt.Println("键不存在")
	} else if err != nil {
		fmt.Println("获取键值对的值失败:", err)
	} else {
		fmt.Println("city:", city)
	}

	// 关闭Redis连接
	client.Close()
}

这个示例演示了如何使用 SET 操作设置键值对,以及如何使用 GET 操作获取键的值。而且还设置了一个带有过期时间的键值对,并展示了如何获取带有过期时间的值。

redis.Nil

redis.Nil 是 Go Redis 客户端库返回的特殊错误,表示尝试获取的键不存在。

package main

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

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

	// 检查连接是否成功
	_, err := client.Ping().Result()
	if err != nil {
		fmt.Println("Redis连接失败:", err)
		return
	}

	// 尝试获取一个不存在的键
	key := "nonexistent_key"
	val, err := client.Get(key).Result()
	// 检查错误是否为 redis.Nil
	if errors.Is(err, redis.Nil) {
		fmt.Printf("键 %s 不存在\n", key)
	} else if err != nil {
		fmt.Printf("获取键 %s 失败: %s\n", key, err)
	} else {
		fmt.Printf("%s: %s\n", key, val)
	}

	// 关闭Redis连接
	client.Close()
}

尝试获取一个不存在的键("nonexistent_key"),这将导致 client.Get(key).Result() 返回 redis.Nil 错误。

Pipeline

Pipeline 是 Redis 客户端库中的一种机制,用于执行多个Redis操作,尤其是批量操作,以提高效率。

在 Go 的github.com/go-redis/redis库中,Pipeline 是一个用于批量执行 Redis 命令的机制。它的工作方式类似于在管道中排队 Redis 操作,然后一次性将它们发送到Redis服务器,减少了往返时间,从而提高了性能。

Pipeline 具有以下主要特点和使用方式:

  • 创建 Pipeline:使用 client.Pipeline() 方法创建一个 Pipeline 对象。该方法返回一个新的 Pipeline 实例。
  • Queue 操作:使用 Pipeline 对象的方法(例如 SetGet)来排队要执行的 Redis 操作。这些操作不会立即执行,而是排队在 Pipeline 中。
  • 执行操作:使用 pipeline.Exec() 方法来一次性执行排队的所有 Redis 操作。这将减少与 Redis 服务器的往返次数,提高性能。
  • 处理结果Exec() 方法返回每个操作的结果,你可以检查这些结果以确定操作是否成功。
  • 原子性Pipeline 中的操作是原子的,它们将在单个 Redis 事务中执行,因此要么都成功,要么都失败。
  • 适用范围Pipeline 特别适用于需要执行大量 Redis 操作的情况,如批量设置、批量获取等。

代码案例

package main

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

func main() {
	client := redis.NewClient(&redis.Options{
		Addr:     "192.168.31.90:6379",
		Password: "",
		DB:       0,
	})
	// 检查连接是否成功
	_, err := client.Ping().Result()
	if err != nil {
		fmt.Println("Redis连接失败:", err)
		return
	}

	// 使用批量操作进行设置多个键值对
	pipline := client.Pipeline() // 创建批量操作的管道
	// 使用 SET 操作设置多个键值对
	pipline.Set("name1", "王美丽", 0)
	pipline.Set("name2", "王大丽", 0)
	pipline.Set("name3", "王小丽", 0)

	// 执行批量操作
	_, err = pipline.Exec()
	if err != nil {
		fmt.Println("批量设置键值对失败:", err)
		return
	}

	// 使用批量操作获取多个键的值
	keys := []string{"name1", "name2", "name3"}
	pipline.MGet(keys...)
	// 执行批量操作
	_, err = pipline.Exec()
	if err != nil {
		fmt.Println("批量获取键值对失败:", err)
		return
	}
	// 处理批量获取的结果
	for _, key := range keys {
		val, err := client.Get(key).Result()
		if err == redis.Nil {
			fmt.Printf("键 %s 不存在\n", key)
		} else if err != nil {
			fmt.Printf("获取键 %s 失败: %s\n", key, err)
		} else {
			fmt.Printf("%s: %s\n", key, val)
		}
	}

	// 关闭Redis连接
	client.Close()
}

这个示例演示了如何使用 Pipeline 执行批量操作,从而提高了Redis操作的效率。

代码解释

  1. 首先创建一个 Redis 客户端连接并检查连接是否成功。
  2. 然后,创建一个 Pipeline 对象,它用于执行批量操作。
  3. 使用 pipeline.Set 在批量操作中设置多个键值对的值,但这些操作尚未执行。它们在管道中排队等待执行。
  4. 使用 pipeline.MGet 来批量获取多个键的值。同样,这些操作也排队在管道中。
  5. 最后,使用 pipeline.Exec() 来一次性执行管道中的所有操作,确保它们在同一个 Redis 事务中执行。
  6. 我们循环遍历要获取的键,并获取它们的值,然后根据结果进行处理。
  7. 最后,我们关闭了 Redis 连接。

scan 遍历 key

在 Go 的 Redis 客户端库中,通常使用 client.Scan 方法执行 SCAN 命令。

package main

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

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

	// 检查连接是否成功
	_, err := client.Ping().Result()
	if err != nil {
		fmt.Println("Redis连接失败:", err)
		return
	}

	// 初始化一个 SCAN 游标,初始值为0
	cursor := uint64(0)
	// 使用 SCAN 遍历所有键
	for {
		// 执行 SCAN 命令,获取一批键和下一个游标
		keys, newCursor, err := client.Scan(cursor, "laravel*", 10).Result()
		if err != nil {
			fmt.Printf("SCAN 执行失败: %s\n", err)
			break
		}
		// 遍历当前批次
		for _, key := range keys {
			fmt.Printf("键:%s\n", key)
		}
		// 更新游标
		cursor = newCursor
		if cursor == 0 {
			break
		}
	}

	client.Close()
}

事务

package main

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

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

	// 检查连接是否成功
	_, err := client.Ping().Result()
	if err != nil {
		fmt.Println("Redis连接失败:", err)
		return
	}

	// 设置一个键值对
	client.Set("balance", 100, 0)

	// 开启一个 Redis 事务
	txPipeline := client.TxPipeline()
	txPipeline.Set("balance", 200, 0) // 在事务中设置新的值
	txPipeline.Get("balance")         // 在事务中获取值

	// 执行事务
	_, err = txPipeline.Exec()
	if err != nil {
		fmt.Println("事务执行失败:", err)
		return
	}

	// 获取事务后的值
	newBalance, err := client.Get("balance").Result()
	if err != nil {
		fmt.Println("获取新值失败:", err)
		return
	}

	fmt.Printf("新的余额:%s\n", newBalance)

	client.Close()
}

这个示例演示了如何使用 Go 的 Redis 客户端库执行一个简单的事务操作,确保一系列 Redis 命令以原子方式执行。

代码解释

  1. 首先创建了一个 Redis 客户端连接,并检查连接是否成功。
  2. 使用 client.Set 设置一个键值对("balance":100)。
  3. 创建一个 Redis 事务(txPipeline)并在其中排队了两个操作:
    • Set:在事务中设置新的值("balance":200)。
    • Get:在事务中获取键 "balance" 的值。
  4. 使用 txPipeline.Exec() 执行事务中的所有操作,确保它们以原子方式执行。
  5. 使用 client.Get("balance").Result() 获取事务后的新值,并打印出来。
  6. 最后,我们关闭了 Redis 连接。

List 队列

package main

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

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

	// 检查连接是否成功
	_, err := client.Ping().Result()
	if err != nil {
		fmt.Println("Redis连接失败:", err)
		return
	}

	// 左侧推入元素到队列
	err = client.LPush("myQueue", "item1", "item2", "item3").Err()
	if err != nil {
		fmt.Println("左侧推入元素失败:", err)
		return
	}

	// 弹出右侧的元素
	popItem, err := client.RPop("myQueue").Result()
	if err != nil {
		fmt.Println("右侧弹出元素失败:", err)
		return
	}

	fmt.Printf("弹出的元素:%s\n", popItem)

	client.Close()
}

这个示例使用了 Redis 的 List 数据结构,通过 LPush 方法将元素推入队列的左侧,然后使用 RPop 方法从队列的右侧弹出元素。

Hash 表

package main

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

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

	// 检查连接是否成功
	_, err := client.Ping().Result()
	if err != nil {
		fmt.Println("Redis连接失败:", err)
		return
	}

	// 哈希表名称
	hashName := "myHash"
	// 添加字段和值到哈希表
	err = client.HSet(hashName, "field1", "value1").Err()
	if err != nil {
		fmt.Println("添加字段和值失败:", err)
		return
	}

	// 获取哈希表字段的值
	field1Value, err := client.HGet(hashName, "field1").Result()
	if err == redis.Nil {
		fmt.Println("字段不存在")
	} else if err != nil {
		fmt.Println("获取字段值失败:", err)
	} else {
		fmt.Printf("字段值:%s\n", field1Value)
	}

	// 删除哈希表中的字段
	err = client.HDel(hashName, "field1").Err()
	if err != nil {
		fmt.Println("删除字段失败:", err)
		return
	}

	fmt.Println("字段删除成功")

	client.Close()
}

这个示例使用了 Redis 的哈希表(Hash),通过 HSet 方法添加字段和值到哈希表,使用 HGet 方法获取哈希表字段的值,然后使用 HDel 方法删除哈希表中的字段。

watch 监听

在 Redis 中,WATCH 是一种用于实现乐观锁的机制,通常与事务(Transaction)一起使用。乐观锁是一种并发控制机制,允许多个客户端同时读取数据,但只有一个客户端能够成功写入数据。

package main

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

func main() {
	ctx := context.Background()
	client := redis.NewClient(&redis.Options{
		Addr:     "192.168.31.90:6379",
		Password: "",
		DB:       0,
	})

	// 检查连接是否成功
	_, err := client.Ping(ctx).Result()
	if err != nil {
		fmt.Println("Redis连接失败:", err)
		return
	}

	key := "myKey"
	initialValue := "1"
	client.Set(ctx, key, initialValue, 0)

	// 开启 Watch 事务
	tx := client.Watch(ctx, func(tx *redis.Tx) error {
		// 获取当前值
		currentValue, err := tx.Get(ctx, key).Result()
		if err != nil && err != redis.Nil {
			return err
		}

		// 检查值是否被修改
		if currentValue != initialValue {
			return fmt.Errorf("值已被修改")
		}

		// 开始事务
		_, err = tx.TxPipelined(ctx, func(pipe redis.Pipeliner) error {
			// 修改值
			pipe.Set(ctx, key, "2", 0)
			return nil
		})

		if err != nil {
			return err
		}

		return nil
	}, key)

	fmt.Println(tx)
	fmt.Println("事务执行成功", client.Get(ctx, key))
}

这个示例中,使用 Watch 函数创建一个事务(Transaction),首先获取 Redis 键 myKey 的当前值,并检查它是否与 initialValue 相同。如果值没有被修改,修改它为新的值,然后提交事务。

Redis 连接池

连接池是一种重要的资源管理机制,用于有效管理和重用与外部资源(如数据库、Redis等)的连接。

连接池的作用:

  • 连接复用和重用: 连接池允许应用程序创建一次连接,然后将这些连接放入池中,以便在需要时重复使用。这避免了在每个操作中都创建和关闭连接的开销,提高了性能。
  • 限制并发连接数: 连接池可以设置最大连接数,防止过多的并发连接对外部资源产生负担。
  • 连接生命周期管理: 连接池负责管理连接的生命周期,包括创建、复用和关闭。这确保了连接在适当的时候被释放和关闭,而不会被忘记或泄漏。
  • 性能和资源利用率提高: 连接池通过减少连接的创建和关闭开销,提高了应用程序的性能。它还可以避免频繁的连接建立和断开对外部资源的资源浪费。
  • 优化连接状态: 连接池可以维护连接的健康状态。如果连接出现问题,池可以重新创建连接,确保连接的可用性。
package main

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

func main() {
	// 创建连接池
	client := redis.NewClient(&redis.Options{
		Addr:     "192.168.31.90:6379",
		Password: "",
		DB:       0,
		PoolSize: 10,
	})
	// 检查连接是否成功
	ctx := context.Background()
	_, err := client.Ping(ctx).Result()
	if err != nil {
		fmt.Println("Redis连接失败:", err)
		return
	}

	// 设置一个键值对
	err = client.Set(ctx, "name5", "wangmeili", 0).Err()
	if err != nil {
		fmt.Println("设置键值对失败:", err)
		return
	}

	// 获取键的值
	value, err := client.Get(ctx, "name5").Result()
	if err != nil {
		fmt.Println("获取键值失败:", err)
		return
	}

	fmt.Printf("键的值:%s\n", value)
}
请登录后再评论