go-redis入门操作 | 青训营

494 阅读7分钟

之前Redis的课程中讲述了Redis的原理和应用,但没有涉及Redis在go中的框架go-redis的讲解,我结合官方文档和课程代码,总结出了go-redis的基本知识,希望大家喜欢!

1 安装和连接

1.1 安装

目前版本为go-redis/v9,支持所有的 redis 版本:

go get github.com/redis/go-redis/v9

1.2 连接数据库

1.2.1 简单连接

用NewClient创建简单连接,传入&redis.Options配置连接,以下是单机数据库的连接:

rdb = redis.NewClient(&redis.Options{
    Addr:     "localhost:6379",
    Password: "", // 没有密码,默认值
    DB:       0,  // 默认DB 0
})

也可以使用字符串作为参数连接,字符串的格式为"redis://<user>:<pass>@localhost:6379/<db>"

opt, err := redis.ParseURL("redis://toni:tmdgnnwscjl@localhost:6379/0")

1.2.2 从连接池中取出的单个连接

redis.Conn 从连接池中取出的单个连接,除非有特殊的需要,否则尽量不要使用。可以使用它向 redis 发送任何数据并读取 redis 的响应,当你使用完毕时,应该把它返回给 go-redis,否则连接池会永远丢失一个连接。

可以使用ClientSetName设置连接名,使用ClientGetName获取连接名

cn := rdb.Conn()
defer cn.Close()
cn.ClientSetName(ctx, "myclient")
name, err := cn.ClientGetName(ctx).Result()

1.2.3 上下文关联

go-redis 支持 Context。 在许多方法使用时,都需要传入参数类型:context.Context。 该参数可以控制 超时 或者传递一些数据, 也可以 监控 go-redis 性能。

2 数据读取与写入

2.1 string数据类型

  1. Set
err := rdb.Set(ctx, "key1", "value1", 0)

go-redis的命令中,第一个参数默认是上下文参数,通过.Err()返回错误信息,或通过.Result()返回值和错误信息,这些基本概念此后不再解释。 Set的最后一个参数是是设置超时时间,数据类型为time.Duration,指从当前开始的时间,例如可以如下设置:

rdb.Set(ctx, "key1", "value1", time.Second*2)
  1. Get Get命令只需要传入key值即可
value, err := rdb.Get(ctx, "key1").Result()

注意,当返回值类型为redis.Nil时,是指该key不存在于数据库中,而非命令执行失败

  1. MSet 一次性设置多个值
err = rdb.MSet(ctx, "key2", "value2", "key3", "value3").Err()
  1. Incr 实现自增操作
rdb.Incr(ctx, "key4")

注意,当key值不存在时,Incr新建该值并设置其为1

  1. Del 实现删除操作,会返回删除的元素值
val, _ = rdb.Del(ctx, "key2").Result()
  1. SetNX SetNX操作非常重要,其只会在key不存在时进行设置,其特性可以用作分布式锁。 SetNX返回一个bool类型和错误类型,错误类型指的是该命令执行的错误信息,而bool类型返回的是该值有没有设置成功,以下为一个案例:
res, err := rdb.SetNX(ctx, "key1", 1, 0).Result()
if err != nil {
    fmt.Println("SetNX failed")
}
if res {
    fmt.Println("SetNX success")
} else {
    fmt.Println("SetNX failed because key1 already exists")
}

2.2 List数据类型

  1. LPush、RPush LPush、RPush向列表中插入多个值,L表示从左插入,R表示从右插入
rdb.LPush(ctx, "list1", 1, 2, 3, 4)
	rdb.RPush(ctx, "list1", 5, 6, 7, 8)
  1. LInsert LInsert指定位置处插入值,并可以传入"Before""After设置在该key前或后进行插入
rdb.LInsert(ctx, "list1", "Before", "2", 111)
  1. LIndex LIndex获取指定位置的值
val, _ := rdb.LIndex(ctx, "list1", 2).Result()
  1. BLPop、BRPop BLPop、BRPop分别表示从列表左侧弹出一个值和从列表右侧弹出一个值,相当于删除并返回。 需要传入的第一个参数表示阻塞模式的超时时间。命令会等待的最长时间。如果在超时时间内,列表中有可弹出的元素,则会立即返回该元素,否则在超时后返回空结果。 如果将超时时间设置为 0,则表示不进行超时等待,即如果列表为空,命令会一直阻塞等待直到有元素可以弹出。这种情况下,如果列表一直为空,命令会一直阻塞程序。
value, _ := rdb.BRPop(ctx, 0, "list1").Result()
value, _ = rdb.BLPop(ctx, 0, "list1").Result()
  1. LPopCount、RPopCount LPopCount、RPopCount表示从左侧或右侧弹出多个值,需要传入弹出值的数量,返回值为[]stringerror
items, _ = rdb.RPopCount(ctx, "list1", 2).Result()

2.3 Hash数据类型

  1. HSet HSet插入单个filed,且会自动创建不存在的key,同时也可以传入超时时间
_, err = rdb.HSet(ctx, "testHash1", "time", time.Now().Nanosecond()).Result()
  1. MSet HMSet一次插入多个filed,且会自动创建不存在的key。 其传入的多个数据类型需要用map[string]interface{}包裹。
item := map[string]interface{}{"user_id": "2222", "got_digg_count": 1238, "follower_count": 379}
_, err := rdb.HMSet(ctx, "testHash1", item).Result()
  1. HGet HGet获取单个filed
val, _ := rdb.HGet(ctx, "testHash1", "user_id").Result()
  1. HGetAll HGetAll获取所有的key-vlue,Result()返回值类型为map[string]string
items, _ := rdb.HGetAll(ctx, "testHash1").Result()
  1. HIncrBy HIncrBy实现哈希filed自增,将返回自增后的值,需要注意的是返回的值类型为int64
val64, _ := rdb.HIncrBy(ctx, "testHash1", "user_id", 10).Result()

2.4 Zset数据类型

  1. ZAdd ZAdd可以创建多条有序列表项。 通常借助redis.Z创建数据,redis.Z结构体包含Score和Member字段,Score用于排序。
initList := []redis.Z{
		{Member: "user1", Score: 10},
		{Member: "user2", Score: 232},
		{Member: "user3", Score: 129},
		{Member: "user4", Score: 149},
		{Member: "user5", Score: 122},
		{Member: "user6", Score: 18},
		{Member: "user7", Score: 12},
	}
num, err := rdb.ZAdd(ctx, "zrank", initList...).Result()

上述代码中,initList...表示可变参数的展开,它的作用是将一个切片(或数组)的元素逐个展开,作为函数参数传递。

  1. ZRangeWithScores、ZRevRangeWithScores ZRangeWithScores、ZRevRangeWithScores 根据Score进行排序(正序或倒序)并返回指定范围内的项。Result()返回值类型为 []redis.Z
resList0, err := rdb.ZRevRangeWithScores(ctx, "zrank", 0, -1).Result()
  1. ZRangeArgsWithScores ZRangeArgsWithScores可以自定义排序规则和处理规则,需要借助redis.ZRangeArgs这个结构体定义这些规则:
zRangeArgs := redis.ZRangeArgs{
		Key:     "zrank",
		ByScore: true,
		Rev:     true,
		Start:   "-inf",
		Stop:    "+inf",
		Offset:  offset,
		Count:   pageSize,
	}
resList1, err := rdb.ZRangeArgsWithScores(ctx, zRangeArgs).Result()

上述代码中,zRangeArgs结构体定义了offset和pageSize,offset表示数据排序的偏移量,pageSize表示获取的的数量最大值。Result()返回值类型为 []redis.Z

  1. ZRank、ZRevRank ZRank、ZRevRank 分别以正序和逆序返回指定field的排名。
rank, err := rdb.ZRevRank(ctx, "zrank", "user1").Result()
  1. ZScore ZScore获取指定field的分值。
score, err := rdb.ZScore(ctx, "zrank", "user2").Result()

2.5 设置过期时间

可以在key创建后为其设置过期时间

2.5.1 设置相对过期时间

相对过期时间指的是从当前时间往后指定一段时间,接受的数据类型为time.Duration,使用Expire指定相对过期时间:

rdb.Expire(ctx, "key3", time.Hour*2)

2.5.2 指定绝对过期时间

可以用ExpireAt指定具体的过期日期,传入类型为time.Time

rdb.ExpireAt(ctx, "key3", time.Now().Add(time.Second*2))

3 发布与订阅

Subscribe方法可以简单地进行订阅,需要注意的是,该方法需要调用Unsubscribe()和Close()以进行资源清理。

pubSub := rdb.Subscribe(ctx, "my_channel")
defer func(mPubSub *redis.PubSub) {
    mPubSub.Unsubscribe(ctx, "my_channel")
    mPubSub.Close()
}(pubSub)

.Channel()返回一个可迭代对象,其接受的结构体为*redis.Message,用Channel字段获取其频道名,用Payload获取内容。

for msg := range pubSub.Channel() {
		// 打印收到的消息
		fmt.Println(msg.Channel + ":" + msg.Payload)
	}

4 pipeline

Pipeline 是一种用于提高批量操作性能的技术。通过将多个命令一次性发送给 Redis 服务器并一起执行,可以减少网络延迟和通信开销,从而提高操作效率。

Pipeline()方法可以创建了一个 Pipeline 对象。使用 Exec 方法执行 Pipeline 并获取结果。

需要注意,Pipeline 并不会在 Redis 服务器端实际开启一个事务,因此在出现错误时需要自行处理。同时,Pipeline 中的操作不能跨越多个事务(例如 MULTI 和 EXEC)。

pipe := rdb.Pipeline()

incr := pipe.Incr(ctx, "counter")
get := pipe.Get(ctx, "counter")

_, err := pipe.Exec(ctx)
if err != nil {
    panic(err)
}

5 事务

使用 TxPipeline 方法创建一个事务的 pipeline(流水线)对象,将多个命令放入该 pipeline 中,然后一次性执行这些命令以实现事务操作。

需要注意的是,如果事务中的任何一个命令执行失败,整个事务都会回滚。

这种方式实现的事务具有基本的原子性,但不支持复杂的事务控制,如条件判断等。

// 创建事务
txn := rdb.TxPipeline()

// 将命令添加到 txn
txn.Set(ctx, "txn1", "txnValue1", 0)
txn.Set(ctx, "txn2", "txnValue2", 0)

// 执行事务
_, err := txn.Exec(ctx)
assert.Nil(t, err)