对redis 命令很熟悉的 可以直接跳过,因为这个库的api 命名基本上都和命令一一对应上了,很容易记
redis 数据库连接
var rdb *redis.Client
var ctx = context.Background()
func initClient() (err error) {
rdb = redis.NewClient(&redis.Options{
Addr: "localhost:6379",
Password: "",
DB: 0,
})
_, err = rdb.Ping(ctx).Result()
if err != nil {
return err
}
return nil
}
初学者要注意 外部声明了一个 rdb, init方法里面 一定得是rdb= 千万别写成rdb:=了 否则rdb就是个局部变量
外部引用就是nil了
另外还有redis的哨兵模式以及集群模式 的两种连接方法 这里就不演示了。有需要的可以自行查询
基本使用
func redisDemo() {
err := rdb.Set(ctx, "score", 100, 0).Err()
if err != nil {
panic(err)
}
val, err := rdb.Get(ctx, "score").Result()
if err != nil {
panic(err)
}
fmt.Println("score:", val)
val2, err := rdb.Get(ctx, "keytest").Result()
if err == redis.Nil {
// 这里主要是看这个key不存在的判定方法就可以了
fmt.Println("keytest does not exist")
} else if err != nil {
fmt.Println("get keytest failed")
} else {
fmt.Println("keytest:", val2)
}
}
总体上 redis的操作 还是挺简单的,可以自行探索,直接在goland中.一下就能看到对应的api 这里就不再一一演示了
redis-类似排行榜的操作
这个例子会比上面的例子稍微复杂一点,很多网站的类似的排行榜的操作 其实就是个zset, 写法就是这:
func redisDemo2() {
zsetkey := "language_rank"
languages := []*redis.Z{
{Score: 90, Member: "java"},
{Score: 80, Member: "go"},
{Score: 70, Member: "js"},
{Score: 60, Member: "rust"},
{Score: 50, Member: "c++"},
}
num, err := rdb.ZAdd(ctx, zsetkey, languages...).Result()
if err != nil {
panic(err)
}
fmt.Println("num:", num)
// 增加值
newScore, err := rdb.ZIncrBy(ctx, zsetkey, 10, "go").Result()
if err != nil {
panic(err)
}
fmt.Println("newScore:", newScore)
// 取分数最高的3个
ret := rdb.ZRevRangeWithScores(ctx, zsetkey, 0, 2).Val()
for _, z := range ret {
fmt.Println("name:", z.Member,
" score:", z.Score)
}
// 取分数在一定范围之内的
op := redis.ZRangeBy{
Min: "80",
Max: "110",
}
ret = rdb.ZRangeByScoreWithScores(ctx, zsetkey, &op).Val()
for _, z := range ret {
fmt.Println(z.Member," ",z.Score)
}
}
pipeline
pipeline 主要就是网络优化,可以节省rtt,并不是事务,千万别搞错了,比如你要执行3个命令,那你正常操作就是需要3个rtt的网络时间,但是你可以把这3个 放到一个pipeline里面执行 那就只需要1个rtt的网络时间即可
同一时间有大量命令要执行的时候 就可以用这个pipeline了
pipeline也不是万能的,比如后面的操作依赖前面的操作的时候 就不适合了,例如我们要取一个值,然后根据这个值 来决定 set一个新的值,这2个操作 set操作 就依赖前面的get操作了
这种场景是没办法用pipeline的
事务 TxPipeline
redis是单线程的,单个命令是原子操作,但是来自不同客户端的命令是可以依次执行的,这个时候 我们就需利用 TxPipeline来确保我们的2个命令之间 不会有来自其他客户端的命令插入进来。
watch
这种场景也是常用的之一,比如我们下单抢购显卡,显卡现在这么少,你怎么确保用户下单的那一刻一定有库存呢? 那其实就是你下单的时候 watch一下库存的这个key,如果发现下单的过程中这个库存被crud了 那就直接返回呗 操作失败
注意key是可以传多个的
举个例子吧:
func watchDemo() {
key := "watch_count"
err := rdb.Watch(ctx, func(tx *redis.Tx) error {
n, err := tx.Get(ctx, key).Int64()
if err != nil && err != redis.Nil {
return err
}
_, err = tx.TxPipelined(ctx, func(pipeliner redis.Pipeliner) error {
pipeliner.Set(ctx, key, n+1, 0)
return nil
})
return err
}, key)
if err != nil {
fmt.Println("tx exec failed:",err)
return
}
fmt.Println("tx exec success")
}
这个函数单独执行是可以的,没问题
那如果我稍微改一下,在这个函数里面 sleep 几秒钟
然后在这个期间 我在redis-cli里面 去修改一下这个值 看看
func watchDemo() {
key := "watch_count"
err := rdb.Watch(ctx, func(tx *redis.Tx) error {
n, err := tx.Get(ctx, key).Int()
if err != nil && err != redis.Nil {
return err
}
_, err = tx.TxPipelined(ctx, func(pipeliner redis.Pipeliner) error {
time.Sleep(5 * time.Second)
pipeliner.Set(ctx, key, n+1, 0)
return nil
})
return err
}, key)
if err != nil {
fmt.Println("tx exec failed:", err)
return
}
fmt.Println("tx exec success")
}
注意在这个时候 我们在事务的执行过程中 加了一个time sleep的操作 以保证我们可以有充足的时间 在redis-cli里面 set一下这个key 结果也是显而易见 这次操作肯定是失败的
注意,这段关于watch的代码 网上很多都是tx.Pipelined 这个是不对的,千万别被这个误导了,tx.Pipelined里面执行的不是事务 所以代码在这里是会失效的,一定得是 tx.TxPipelined 才行