Go语言 go-redis与watch redis 数据库连

对redis 命令很熟悉的 可以直接跳过,因为这个库的api 命名基本上都和命令一一对应上了,很容易记

redis 数据库连接

github.com/go-redis/re…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
go复制代码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的哨兵模式以及集群模式 的两种连接方法 这里就不演示了。有需要的可以自行查询

基本使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
go复制代码
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, 写法就是这:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
go复制代码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是可以传多个的

举个例子吧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
go复制代码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")
}

这个函数单独执行是可以的,没问题

image.png

那如果我稍微改一下,在这个函数里面 sleep 几秒钟

然后在这个期间 我在redis-cli里面 去修改一下这个值 看看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
go复制代码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 才行

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%