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)
}