之前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数据类型
- Set
err := rdb.Set(ctx, "key1", "value1", 0)
go-redis的命令中,第一个参数默认是上下文参数,通过.Err()返回错误信息,或通过.Result()返回值和错误信息,这些基本概念此后不再解释。
Set的最后一个参数是是设置超时时间,数据类型为time.Duration,指从当前开始的时间,例如可以如下设置:
rdb.Set(ctx, "key1", "value1", time.Second*2)
- Get Get命令只需要传入key值即可
value, err := rdb.Get(ctx, "key1").Result()
注意,当返回值类型为redis.Nil时,是指该key不存在于数据库中,而非命令执行失败
- MSet 一次性设置多个值
err = rdb.MSet(ctx, "key2", "value2", "key3", "value3").Err()
- Incr 实现自增操作
rdb.Incr(ctx, "key4")
注意,当key值不存在时,Incr新建该值并设置其为1
- Del 实现删除操作,会返回删除的元素值
val, _ = rdb.Del(ctx, "key2").Result()
- 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数据类型
- LPush、RPush LPush、RPush向列表中插入多个值,L表示从左插入,R表示从右插入
rdb.LPush(ctx, "list1", 1, 2, 3, 4)
rdb.RPush(ctx, "list1", 5, 6, 7, 8)
- LInsert
LInsert指定位置处插入值,并可以传入
"Before"或"After设置在该key前或后进行插入
rdb.LInsert(ctx, "list1", "Before", "2", 111)
- LIndex LIndex获取指定位置的值
val, _ := rdb.LIndex(ctx, "list1", 2).Result()
- BLPop、BRPop BLPop、BRPop分别表示从列表左侧弹出一个值和从列表右侧弹出一个值,相当于删除并返回。 需要传入的第一个参数表示阻塞模式的超时时间。命令会等待的最长时间。如果在超时时间内,列表中有可弹出的元素,则会立即返回该元素,否则在超时后返回空结果。 如果将超时时间设置为 0,则表示不进行超时等待,即如果列表为空,命令会一直阻塞等待直到有元素可以弹出。这种情况下,如果列表一直为空,命令会一直阻塞程序。
value, _ := rdb.BRPop(ctx, 0, "list1").Result()
value, _ = rdb.BLPop(ctx, 0, "list1").Result()
- LPopCount、RPopCount
LPopCount、RPopCount表示从左侧或右侧弹出多个值,需要传入弹出值的数量,返回值为
[]string和error
items, _ = rdb.RPopCount(ctx, "list1", 2).Result()
2.3 Hash数据类型
- HSet HSet插入单个filed,且会自动创建不存在的key,同时也可以传入超时时间
_, err = rdb.HSet(ctx, "testHash1", "time", time.Now().Nanosecond()).Result()
- 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()
- HGet HGet获取单个filed
val, _ := rdb.HGet(ctx, "testHash1", "user_id").Result()
- HGetAll
HGetAll获取所有的key-vlue,Result()返回值类型为
map[string]string
items, _ := rdb.HGetAll(ctx, "testHash1").Result()
- HIncrBy
HIncrBy实现哈希filed自增,将返回自增后的值,需要注意的是返回的值类型为
int64
val64, _ := rdb.HIncrBy(ctx, "testHash1", "user_id", 10).Result()
2.4 Zset数据类型
- 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...表示可变参数的展开,它的作用是将一个切片(或数组)的元素逐个展开,作为函数参数传递。
- ZRangeWithScores、ZRevRangeWithScores
ZRangeWithScores、ZRevRangeWithScores 根据Score进行排序(正序或倒序)并返回指定范围内的项。Result()返回值类型为
[]redis.Z。
resList0, err := rdb.ZRevRangeWithScores(ctx, "zrank", 0, -1).Result()
- 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。
- ZRank、ZRevRank ZRank、ZRevRank 分别以正序和逆序返回指定field的排名。
rank, err := rdb.ZRevRank(ctx, "zrank", "user1").Result()
- 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)