Java 转 go 学习 - Redis(1)

文章目录

  • [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. 小结)

本系列文章:


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 和事务...

如有错误,欢迎指出!!!

相关推荐
JWASX1 小时前
Java 转 go 学习 - Redis(2)
学习·golang
Misnearch2 小时前
agent架构学习
学习·架构
li星野2 小时前
【学习记录】USB连接与枚举全解析:从D+/D-上拉到pyOCD烧录
学习
我命由我123452 小时前
Photoshop - Photoshop 使用魔棒工具选择单独的区域
学习·ui·职场和发展·求职招聘·职场发展·学习方法·photoshop
JWASX2 小时前
Java 转 go 学习 - kitex(1)
学习·golang
li星野2 小时前
HAVE — have / has / had (One-Page Notes)
学习
传奇开心果编程2 小时前
【声明式UI开发实用技术学与练】第8课 状态提升与下放
学习·flutter·react native·ui·swiftui·composer
谢亮_vipxieliang2 小时前
Go select 多路复用:从语法到实战的完整指南
开发语言·后端·golang
阳光九叶草LXGZXJ11 小时前
达梦数据库-报错-15-列【XXX】长度超出定义
linux·运维·数据库·sql·学习