文章目录
- [1. 项目初始化](#1. 项目初始化)
-
- [1.1 简单版本](#1.1 简单版本)
- [1.2 redis 连接参数](#1.2 redis 连接参数)
- [1.3 封装成工具类](#1.3 封装成工具类)
- [1.4 关闭连接,优雅退出](#1.4 关闭连接,优雅退出)
- [2. 基本数据结构和操作](#2. 基本数据结构和操作)
-
- [2.1 String](#2.1 String)
- [2.2 Hash 哈希](#2.2 Hash 哈希)
- [2.3 List 列表(有序,可以重复)](#2.3 List 列表(有序,可以重复))
- [2.4 Set 集合](#2.4 Set 集合)
- [2.5 ZSet 有序集合](#2.5 ZSet 有序集合)
- [2.6 常用命令](#2.6 常用命令)
- [2.7 Context](#2.7 Context)
- [3. 小结](#3. 小结)
本系列文章:
- Java 转 go 学习 - 项目管理
- Java 转 go 学习 - 基本语法
- Java 转 go 学习 - 类型转换
- Java 转 go 学习 - 流程控制结构
- Java 转 go 学习 - 数组和切片
- Java 转 go 学习 - map
- Java 转 go 学习 - 函数(1)
- Java 转 go 学习 - 函数(2)
- Java 转 go 学习 - 结构体
- Java 转 go 学习 - 接口
- Java 转 go 学习 - 并发编程(1)
- Java 转 go 学习 - 并发编程(2)
- Java 转 go 学习 - 并发编程(3)
- Java 转 go 学习 - web 编程
- Java 转 go 学习 - Hertz 学习(1)
- Java 转 go 学习 - Hertz 学习(2)
1. 项目初始化
1.1 简单版本
首先 go get github.com/redis/go-redis/v9 下载 v9 版本的依赖,支持 Redis 7.0+ 的客户端,下面编写代码。
go
package main
import (
"context"
"fmt"
"github.com/redis/go-redis/v9"
)
func main() {
// 1. 创建 Redis 客户端
rdb := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
Password: "123456",
DB: 0,
})
// 2. 创建上下文
ctx := context.Background()
// 3. 测试连接
pong, err := rdb.Ping(ctx).Result()
if err != nil {
fmt.Println("Redis 连接失败:", err)
return
}
fmt.Println("Redis 连接成功:", pong)
// 4. 简单操作
err = rdb.Set(ctx, "name", "go-redis-test", 0).Err()
if err != nil {
panic(err)
}
val, err := rdb.Get(ctx, "name").Result()
if err != nil {
panic(err)
}
fmt.Println("name = ", val)
// Redis 连接成功: PONG
// name = go-redis-test
}
首先使用 NewClient 创建 redis 客户端,然后用 redis.Options 构建 redis 配置传进去,最后获取到连接,调用 Ping 方法测试连接是否成功,如果连接成功 redis 会返回 PONG。后面我们就可以调用 redis 的命令了。
1.2 redis 连接参数
go
// 1. 创建 Redis 客户端
rdb := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
Password: "123456",
DB: 0,
// 连接超时
DialTimeout: 5 * time.Second, // 连接建立超时时间
ReadTimeout: 3 * time.Second, // 读超时
WriteTimeout: 3 * time.Second, // 写超时
// 连接池配置
PoolSize: 40, // 连接池大小, 最大连接数
MinIdleConns: 10, // 能够保持的最小空闲连接
MaxActiveConns: 40, // 最大活跃连接数
MaxIdleConns: 20, // 能够保持的最大空闲连接
PoolTimeout: 4 * time.Second, // 获取连接超时时间
ConnMaxIdleTime: 5 * time.Second, // 空闲连接多久关闭
})
上面是一些 redis 的配置项。
DialTimeout: 连接建立超时时间,第一次连接如果 5s 内连接不上就直接放弃并且报错。ReadTimeout:读命令的超时时间,设置为 3s 如果读不到数据就报错。WriteTimeout:写命令的超时时间,设置为 3s 如果写入数据失败就报错。PoolSize:连接池大小,也就是说程序中最多只有 40 个连接能够连在 Redis 上。MinIdleConns:连接池的最小空闲连接数,连接池启动的时候会预先创建好 10 个连接,永远不会销毁。MaxIdleConns:连接池的最大空闲连接数,连接池启动的时候会预先创建好 MinIdleConns 个连接,但是连接池的上限是 PoolSize,随着连接越创建越多,如果没有请求打到,这时候一些空闲连接就会被回收掉,最多保留MaxIdleConns个连接。PoolTimeout:获取连接的超时时间,当连接池满了就需要等连接释放,拿到之后才能去读写 redis,所以这个值应该要比ReadTimeout和WriteTimeout要大,起码要等到读写完成之后,给业务逻辑留出缓冲空间。
注意这里的连接是当前服务和 redis 建立的连接,正常来说如果线上 QPS 比较高,一般一个服务几百台机器都是正常的,所以这种情况下就需要控制下 redis 连接数,一般就是设置这三个参数 MinIdleConns、MaxActiveConns、MaxIdleConns,正常来说设置成 20 就够用了,如果 QPS 高点可以把 MaxActiveConns 和 MinIdleConns 改成 40,但是如果改成 40,这样机器多的情况下 redis 的连接消耗会比较高。
QPS 比较高的情况下 MinIdleConns 建议和 MaxActiveConns 设置成一样的,避免每次请求都创建连接,减少这部分开销,毕竟线上一旦有一个请求阻塞也会导致后面的请求阻塞住。
1.3 封装成工具类
正常来说这些配置是存到一个文件里面,然后程序启动的时候读到配置中,这种也是比较常见的写法,下面我们可以看下这种写法。
首先我们引入 yaml 文件的解析依赖 gopkg.in/yaml.v3 v3.0.1,然后我们写一个 yaml 文件,将 redis 相关的配置写到文件中。
go
redis:
addr: "localhost:6379"
password: "123456"
db: 0
dial_timeout_sec: 5
read_timeout_sec: 3
write_timeout_sec: 3
pool_size: 40
min_idle_conns: 10
max_active_conns: 40
max_idle_conns: 20
pool_timeout_sec: 4
conn_max_idle_sec: 5
接下来编写 Redis.go,下面是配置相关的结构体,使用 yaml 标签将结构体的字段和文件里面的字段对应上。
go
type RedisConfig struct {
Addr string `yaml:"addr"`
Password string `yaml:"password"`
DB int `yaml:"db"`
DialTimeoutSec int `yaml:"dial_timeout_sec"`
ReadTimeoutSec int `yaml:"read_timeout_sec"`
WriteTimeoutSec int `yaml:"write_timeout_sec"`
PoolSize int `yaml:"pool_size"`
MinIdleConns int `yaml:"min_idle_conns"`
MaxActiveConns int `yaml:"max_active_conns"`
MaxIdleConns int `yaml:"max_idle_conns"`
PoolTimeoutSec int `yaml:"pool_timeout_sec"`
ConnMaxIdleSec int `yaml:"conn_max_idle_sec"`
}
type Config struct {
Redis RedisConfig `yaml:"redis"`
}
然后我们写一个方法,将 yaml 文件绑定到结构体中,同时提供 ToOptions 方法,创建结构体指针返回,注意别写成返回结构体值类型了,返回指针减少复制开销。
go
func LoadRedisConfig(path string) (*RedisConfig, error) {
// 读取文件
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("读取配置文件失败(%s): %w", path, err)
}
var cfg Config
// 解析文件里面的配置, 绑定到结构体 Config 中
if err := yaml.Unmarshal(data, &cfg); err != nil {
return nil, fmt.Errorf("解析配置文件失败(%s): %w", path, err)
}
if cfg.Redis.Addr == "" {
return nil, fmt.Errorf("配置无效(%s): redis.addr 为空", path)
}
return &cfg.Redis, nil
}
func (c *RedisConfig) ToOptions() *redis.Options {
return &redis.Options{
Addr: c.Addr,
Password: c.Password,
DB: c.DB,
DialTimeout: time.Duration(c.DialTimeoutSec) * time.Second,
ReadTimeout: time.Duration(c.ReadTimeoutSec) * time.Second,
WriteTimeout: time.Duration(c.WriteTimeoutSec) * time.Second,
PoolSize: c.PoolSize,
MinIdleConns: c.MinIdleConns,
MaxActiveConns: c.MaxActiveConns,
MaxIdleConns: c.MaxIdleConns,
PoolTimeout: time.Duration(c.PoolTimeoutSec) * time.Second,
ConnMaxIdleTime: time.Duration(c.ConnMaxIdleSec) * time.Second,
}
}
最后在 main 方法中我们可以通过 LoadRedisConfig 加载并获取配置文件结构体。
go
package main
import (
"context"
"fmt"
"go-learn-1/learn-5/conf"
"github.com/redis/go-redis/v9"
)
func main() {
redisCfg, err := conf.LoadRedisConfig("D:\\go-code\\go-learn-1\\learn-5\\redis-conf.yaml")
if err != nil {
fmt.Println("加载配置失败:", err)
return
}
rdb := redis.NewClient(redisCfg.ToOptions())
ctx := context.Background()
pong, err := rdb.Ping(ctx).Result()
if err != nil {
fmt.Println("Redis 连接失败:", err)
return
}
fmt.Println("Redis 连接成功:", pong)
err = rdb.Set(ctx, "name", "go-redis-test", 0).Err()
if err != nil {
panic(err)
}
val, err := rdb.Get(ctx, "name").Result()
if err != nil {
panic(err)
}
fmt.Println("name =", val)
}
1.4 关闭连接,优雅退出
在 main 方法后面加上退出信号监听。
go
fmt.Println("程序运行中,按 Ctrl+C 优雅退出...")
sigCh := make(chan os.Signal, 1)
// 监听操作系统的退出信号
signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM)
// 通道阻塞直到有信号
<-sigCh
fmt.Println("收到退出信号,正在关闭资源...")
然后在创建出 redis 之后使用 defer 关闭 redis。
go
defer func() {
if err := rdb.Close(); err != nil {
fmt.Println("关闭 Redis 连接失败:", err)
return
}
fmt.Println("Redis 连接已关闭")
}()
2. 基本数据结构和操作
2.1 String
常用命令
- Set:存值
- Get:取值
- Del:删除
- Expire:设置过期时间
- SetEX:存值 + 过期
为了方便操作,我们先定义一个 Service 结构体,在上面的 main 方法中将 client 传入 NewRedisService 中。
go
type RedisService struct {
rdb *redis.Client
}
func NewRedisService(rdb *redis.Client) *RedisService {
return &RedisService{rdb: rdb}
}
然后给这个结构体绑定方法,在方法里面调用 rbd 去调用 redis 命令,我们在 main 方法调用 RunDemo,在里面去学相关的命令就行。
go
func (s *RedisService) RunDemo(ctx context.Context) error {
}
下面话不多说先来看下 String 的相关方法。
go
// Set, 不设置过期时间
func (s *RedisService) SetString(ctx context.Context, key, value string) error {
return s.rdb.Set(ctx, key, value, 0).Err()
}
// Set 方法, 带过期时间
func (s *RedisService) SetStringWithDuration(ctx context.Context, key, value string, expire time.Duration) error {
return s.rdb.Set(ctx, key, value, expire).Err()
}
// get 方法获取 key
func (s *RedisService) GetString(ctx context.Context, key string) (string, error) {
return s.rdb.Get(ctx, key).Result()
}
2.2 Hash 哈希
Hash 特别适合存 用户信息、商品信息、配置项 等结构化数据。
常用命令
- HSet:设置字段
- HGet:取单个字段
- HGetAll:取所有字段
- HDel:删除字段
- HExists:判断字段是否存在
下面是相关的代码。
go
// HSet
func (s *RedisService) SetUserAll(ctx context.Context, key string, profile map[string]interface{}) error {
return s.rdb.HSet(ctx, key, profile).Err()
}
// HGet
func (s *RedisService) GetUserField(ctx context.Context, key, field string) (string, error) {
return s.rdb.HGet(ctx, key, field).Result()
}
// HGetAll
func (s *RedisService) GetUserAll(ctx context.Context, key string) (map[string]string, error) {
return s.rdb.HGetAll(ctx, key).Result()
}
// HDel
func (s *RedisService) HDelUser(ctx context.Context, key, field string) error {
return s.rdb.HDel(ctx, key, field).Err()
}
// HExists
func (s *RedisService) UserExists(ctx context.Context, key, field string) (bool, error) {
return s.rdb.HExists(ctx, key, field).Result()
}
这里注意下 HSet 可以直接传入一个 map 来设置单个或者多个字段。
下面是方法调用和结果的示例。
go
func (s *RedisService) RunDemo(ctx context.Context) error {
err := s.SetUserAll(ctx, "user:001", map[string]interface{}{
"name": "John",
"age": 123,
"phone": "123456789",
})
if err != nil {
fmt.Println("set user:001 error")
} else {
fmt.Println("set user:001 success")
}
fmt.Println(s.GetUserField(ctx, "user:001", "name"))
fmt.Println(s.GetUserField(ctx, "user:001", "age"))
fmt.Println(s.GetUserField(ctx, "user:001", "phone"))
fmt.Println(s.GetUserAll(ctx, "user:001"))
// John <nil>
// 123 <nil>
// 123456789 <nil>
// map[age:123 name:John phone:123456789] <nil>
err = s.SetUserAll(ctx, "user:002", map[string]interface{}{
"name": "John",
})
if err != nil {
fmt.Println("set user:002 error")
} else {
fmt.Println("set user:002 success")
}
fmt.Println(s.UserExists(ctx, "user:002", "name"))
// true
fmt.Println(s.HDelUser(ctx, "user:002", "name"))
fmt.Println(s.UserExists(ctx, "user:002", "name"))
// false
return err
}
下面我们也可以传入结构体去设置,但是要将结构体里面的值读出来设置到 map[string]interface{} 集合中,然后再通过 HSet 方法设置到 redis,但是这种方式太麻烦了,看一下就行,下面是用 Codex 帮我写的方法。
go
func (s *RedisService) SetUserAllByStruct(ctx context.Context, key string, data interface{}) error {
// 支持传入结构体或结构体指针;先统一拿到结构体值
v := reflect.ValueOf(data)
if !v.IsValid() {
return fmt.Errorf("data is nil")
}
if v.Kind() == reflect.Ptr {
if v.IsNil() {
return fmt.Errorf("data is nil pointer")
}
v = v.Elem()
}
if v.Kind() != reflect.Struct {
return fmt.Errorf("data must be struct or *struct")
}
// 遍历结构体导出字段,根据 redis tag(或默认字段名)组装为 Hash 字段
t := v.Type()
values := make(map[string]interface{})
// 遍历所有字段
for i := 0; i < t.NumField(); i++ {
// 获取字段
f := t.Field(i)
if f.PkgPath != "" {
continue
}
// 获取对应的 field Key
fieldKey := redisFieldName(f)
if fieldKey == "" {
continue
}
// 获取 value, 弄成 interface{} 的格式
values[fieldKey] = v.Field(i).Interface()
}
// 一次性写入 Redis Hash
return s.rdb.HSet(ctx, key, values).Err()
}
func (s *RedisService) GetUserAllToStruct(ctx context.Context, key string, dst interface{}) error {
// dst 必须是可写的结构体指针,用于接收查询结果
v := reflect.ValueOf(dst)
if !v.IsValid() || v.Kind() != reflect.Ptr || v.IsNil() {
return fmt.Errorf("dst must be non-nil pointer to struct")
}
v = v.Elem()
if v.Kind() != reflect.Struct {
return fmt.Errorf("dst must be pointer to struct")
}
// 从 Hash 读取所有字段,返回 map[string]string
data, err := s.rdb.HGetAll(ctx, key).Result()
if err != nil {
return err
}
// 按字段映射关系把字符串值转换并写回目标结构体
t := v.Type()
for i := 0; i < t.NumField(); i++ {
f := t.Field(i)
if f.PkgPath != "" {
continue
}
fieldKey := redisFieldName(f)
if fieldKey == "" {
continue
}
raw, ok := data[fieldKey]
if !ok {
continue
}
if err := setFieldFromString(v.Field(i), raw); err != nil {
return fmt.Errorf("field %s parse failed: %w", f.Name, err)
}
}
return nil
}
func redisFieldName(f reflect.StructField) string {
// 获取 tag 值, 比如 redis:"name", 这里获取到的就是 name
tag := f.Tag.Get("redis")
if tag == "-" {
return ""
}
if tag != "" {
// 返回 tag
return tag
}
// 如果没有设置 tag 就将结构体字段第一个字符变成小写
return strings.ToLower(f.Name[:1]) + f.Name[1:]
}
func setFieldFromString(field reflect.Value, raw string) error {
if !field.CanSet() {
return nil
}
switch field.Kind() {
case reflect.String:
field.SetString(raw)
return nil
case reflect.Bool:
v, err := strconv.ParseBool(raw)
if err != nil {
return err
}
field.SetBool(v)
return nil
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
v, err := strconv.ParseInt(raw, 10, field.Type().Bits())
if err != nil {
return err
}
field.SetInt(v)
return nil
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
v, err := strconv.ParseUint(raw, 10, field.Type().Bits())
if err != nil {
return err
}
field.SetUint(v)
return nil
case reflect.Float32, reflect.Float64:
v, err := strconv.ParseFloat(raw, field.Type().Bits())
if err != nil {
return err
}
field.SetFloat(v)
return nil
default:
return fmt.Errorf("unsupported kind: %s", field.Kind())
}
}
2.3 List 列表(有序,可以重复)
List 是 有序字符串 列表,常用作:队列、栈、消息流,不过实际上还真没见过有多少用的,反正到现在没怎么用。
- LPush:左边插入
- RPush:右边插入
- LRange:获取区间
- LPop:左边弹出
- RPop:右边弹出
下面是几个方法。
go
func (s *RedisService) LPush(ctx context.Context, key string, values ...string) error {
items := make([]interface{}, 0, len(values))
for _, v := range values {
items = append(items, v)
}
return s.rdb.LPush(ctx, key, items...).Err()
}
func (s *RedisService) RPush(ctx context.Context, key string, values ...string) error {
items := make([]interface{}, 0, len(values))
for _, v := range values {
items = append(items, v)
}
return s.rdb.RPush(ctx, key, items...).Err()
}
func (s *RedisService) LRange(ctx context.Context, key string, start, stop int64) ([]string, error) {
return s.rdb.LRange(ctx, key, start, stop).Result()
}
func (s *RedisService) LPop(ctx context.Context, key string) (string, error) {
return s.rdb.LPop(ctx, key).Result()
}
func (s *RedisService) RPop(ctx context.Context, key string) (string, error) {
return s.rdb.RPop(ctx, key).Result()
}
然后就是调用的方法和调用结果。
go
func (s *RedisService) RunDemo(ctx context.Context) error {
listKey := "task:list:001"
if err := s.LPush(ctx, listKey, "task-c", "task-b"); err != nil {
return err
}
if err := s.RPush(ctx, listKey, "task-d"); err != nil {
return err
}
fmt.Println(s.LRange(ctx, listKey, 0, -1))
fmt.Println(s.LPop(ctx, listKey))
fmt.Println(s.RPop(ctx, listKey))
fmt.Println(s.LRange(ctx, listKey, 0, -1))
// [task-b task-c task-d] <nil>
// task-b <nil>
// task-d <nil>
// [task-c] <nil>
return nil
}
2.4 Set 集合
Set 元素不可重复,适合做下面几个操作:点赞、签到、去重、共同关注...
常用命令
- SAdd:添加
- SMembers:获取所有
- SIsMember:判断是否存在
- SRem:删除
- SCard:统计数量
下面是几个方法。
go
func (s *RedisService) SAdd(ctx context.Context, key string, members ...string) (int64, error) {
values := make([]interface{}, 0, len(members))
for _, m := range members {
values = append(values, m)
}
return s.rdb.SAdd(ctx, key, values...).Result()
}
func (s *RedisService) SMembers(ctx context.Context, key string) ([]string, error) {
return s.rdb.SMembers(ctx, key).Result()
}
func (s *RedisService) SIsMember(ctx context.Context, key, member string) (bool, error) {
return s.rdb.SIsMember(ctx, key, member).Result()
}
func (s *RedisService) SRem(ctx context.Context, key string, members ...string) (int64, error) {
values := make([]interface{}, 0, len(members))
for _, m := range members {
values = append(values, m)
}
return s.rdb.SRem(ctx, key, values...).Result()
}
func (s *RedisService) SCard(ctx context.Context, key string) (int64, error) {
return s.rdb.SCard(ctx, key).Result()
}
示例如下。
go
func (s *RedisService) RunDemo(ctx context.Context) error {
setKey := "demo:set:tags"
added, err := s.SAdd(ctx, setKey, "redis", "go", "cache", "go")
if err != nil {
return err
}
fmt.Println("SAdd added:", added)
// SAdd added: 3
members, err := s.SMembers(ctx, setKey)
if err != nil {
return err
}
fmt.Println("SMembers:", members)
// SMembers: [redis go cache]
exists, err := s.SIsMember(ctx, setKey, "go")
if err != nil {
return err
}
fmt.Println("SIsMember(go):", exists)
// SIsMember(go): true
removed, err := s.SRem(ctx, setKey, "cache")
if err != nil {
return err
}
fmt.Println("SRem removed:", removed)
// SRem removed: 1
count, err := s.SCard(ctx, setKey)
if err != nil {
return err
}
fmt.Println("SCard:", count)
// SCard: 2
members, err = s.SMembers(ctx, setKey)
if err != nil {
return err
}
fmt.Println("SMembers after SRem:", members)
// SMembers after SRem: [redis go]
return nil
}
2.5 ZSet 有序集合
Zset 就是有序 set 集合,带分数,可以用作排行版。
常用命令:
- ZAdd:添加(带分数)
- ZRange:正序排行
- ZRevRange:倒序排行
- ZScore:查分数
- ZRank:查排名
下面是调用的例子。
go
func (s *RedisService) SCard(ctx context.Context, key string) (int64, error) {
return s.rdb.SCard(ctx, key).Result()
}
func (s *RedisService) ZAdd(ctx context.Context, key string, members ...redis.Z) (int64, error) {
return s.rdb.ZAdd(ctx, key, members...).Result()
}
func (s *RedisService) ZRange(ctx context.Context, key string, start, stop int64) ([]string, error) {
return s.rdb.ZRange(ctx, key, start, stop).Result()
}
func (s *RedisService) ZRevRange(ctx context.Context, key string, start, stop int64) ([]string, error) {
return s.rdb.ZRevRange(ctx, key, start, stop).Result()
}
func (s *RedisService) ZScore(ctx context.Context, key, member string) (float64, error) {
return s.rdb.ZScore(ctx, key, member).Result()
}
func (s *RedisService) ZRank(ctx context.Context, key, member string) (int64, error) {
return s.rdb.ZRank(ctx, key, member).Result()
}
下面是示例。
go
func (s *RedisService) RunDemo(ctx context.Context) error {
zsetKey := "demo:zset:rank"
if err := s.rdb.Del(ctx, zsetKey).Err(); err != nil {
return err
}
added, err := s.ZAdd(ctx, zsetKey,
redis.Z{Score: 90, Member: "tom"},
redis.Z{Score: 85, Member: "jerry"},
redis.Z{Score: 98, Member: "alice"},
)
if err != nil {
return err
}
fmt.Println("ZAdd added:", added)
// ZAdd added: 3
asc, err := s.ZRange(ctx, zsetKey, 0, -1)
if err != nil {
return err
}
fmt.Println("ZRange:", asc)
// ZRange: [jerry tom alice]
desc, err := s.ZRevRange(ctx, zsetKey, 0, -1)
if err != nil {
return err
}
fmt.Println("ZRevRange:", desc)
// ZRevRange: [alice tom jerry]
score, err := s.ZScore(ctx, zsetKey, "alice")
if err != nil {
return err
}
fmt.Println("ZScore(alice):", score)
// ZScore(alice): 98
rank, err := s.ZRank(ctx, zsetKey, "alice")
if err != nil {
return err
}
fmt.Println("ZRank(alice):", rank)
// ZRank(alice): 2
return nil
}
2.6 常用命令
下面再来看下一些常用命令的测试:
- Exists 判断是否存在某个 key。
- Expire 给 key 设置过期时间,可以用来给 Hash 结构设置。
- TTL 查看当前 key 的剩余时间。
- Type 查看 key 的类型。
- Del 删除某个 key。
- keys 模糊查找 key,生产环境禁用,避免 Redis 阻塞。
下面是调用方法的例子。
go
func (s *RedisService) Exists(ctx context.Context, keys ...string) (int64, error) {
return s.rdb.Exists(ctx, keys...).Result()
}
func (s *RedisService) TTL(ctx context.Context, key string) (time.Duration, error) {
return s.rdb.TTL(ctx, key).Result()
}
func (s *RedisService) Expire(ctx context.Context, key string, expiration time.Duration) (bool, error) {
return s.rdb.Expire(ctx, key, expiration).Result()
}
func (s *RedisService) Type(ctx context.Context, key string) (string, error) {
return s.rdb.Type(ctx, key).Result()
}
func (s *RedisService) Del(ctx context.Context, keys ...string) (int64, error) {
return s.rdb.Del(ctx, keys...).Result()
}
func (s *RedisService) Keys(ctx context.Context, pattern string) ([]string, error) {
return s.rdb.Keys(ctx, pattern).Result()
}
然后就是测试的例子。
go
func (s *RedisService) RunDemo(ctx context.Context) error {
commonKey := "demo:common:key"
if err := s.SetString(ctx, commonKey, "hello"); err != nil {
return err
}
existsCount, err := s.Exists(ctx, commonKey)
if err != nil {
return err
}
fmt.Println("Exists:", existsCount)
// Exists: 1
keyType, err := s.Type(ctx, commonKey)
if err != nil {
return err
}
fmt.Println("Type:", keyType)
// Type: string
ok, err := s.Expire(ctx, commonKey, 30*time.Second)
if err != nil {
return err
}
fmt.Println("Expire:", ok)
// Expire: true
ttl, err := s.TTL(ctx, commonKey)
if err != nil {
return err
}
fmt.Println("TTL:", ttl)
// TTL: 30s
keyList, err := s.Keys(ctx, "demo:*")
if err != nil {
return err
}
fmt.Println("Keys demo:*:", keyList)
// Keys demo:*: [demo:zset:rank demo:common:key demo:set:tags]
deleted, err := s.Del(ctx, commonKey)
if err != nil {
return err
}
fmt.Println("Del:", deleted)
// Del: 1
return nil
}
2.7 Context
go-redis/v9 所有命令调用方法都必须传入 context,作用就是:超时控制 、取消请求,这样能够防止调用 Redis 卡死导致 Go 协程堵住。
下面我们看一个超时控制的例子,首先通过 context.WithTimeout 创建出一个子 context,设置超时时间,然后调用 redis 命令,将子 context 传进去,最后用 defer cancel 用来释放 WithTimeout 创建出来的上下文资源,因为 WithTimeout 内部会启动定时器,请求提前结束时,如果不 cancel,定时器会一直等到超时才释放,同时 cancel() 会立刻关闭 ctx.Done(),依赖这个 context 的操作能尽快停止。这样写能避免内存泄露。
go
func (s *RedisService) GetStringWithTimeout(parent context.Context, key string, timeout time.Duration) (string, error) {
ctx, cancel := context.WithTimeout(parent, timeout)
defer cancel()
return s.rdb.Get(ctx, key).Result()
}
至于调用也非常简单。
go
value, err := s.GetStringWithTimeout(ctx, commonKey, 2*time.Second)
if err != nil {
return err
}
fmt.Println("GetStringWithTimeout:", value)
// GetStringWithTimeout: hello
3. 小结
这篇文章先到这里,我们学习了 Redis 一些基本命令的操作,下一篇文章来看下 Redis 的一些高阶用法,如 pipeline 和事务...
如有错误,欢迎指出!!!