57、Go语言基础 - 数据库操作 - Go 操作 Redis
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对象的方法(例如Set、Get)来排队要执行的 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操作的效率。
代码解释
- 首先创建一个 Redis 客户端连接并检查连接是否成功。
- 然后,创建一个 Pipeline对象,它用于执行批量操作。
- 使用 pipeline.Set在批量操作中设置多个键值对的值,但这些操作尚未执行。它们在管道中排队等待执行。
- 使用 pipeline.MGet来批量获取多个键的值。同样,这些操作也排队在管道中。
- 最后,使用 pipeline.Exec()来一次性执行管道中的所有操作,确保它们在同一个 Redis 事务中执行。
- 我们循环遍历要获取的键,并获取它们的值,然后根据结果进行处理。
- 最后,我们关闭了 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 命令以原子方式执行。
代码解释
- 首先创建了一个 Redis 客户端连接,并检查连接是否成功。
- 使用 client.Set设置一个键值对("balance":100)。
- 创建一个 Redis 事务(txPipeline)并在其中排队了两个操作:- 
Set:在事务中设置新的值("balance":200)。
- 
Get:在事务中获取键 "balance" 的值。
 
- 
- 使用 txPipeline.Exec()执行事务中的所有操作,确保它们以原子方式执行。
- 使用 client.Get("balance").Result()获取事务后的新值,并打印出来。
- 最后,我们关闭了 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)
}