Redis

Redis介绍

Redis是一个开源的内存数据库,Redis提供了多种不同类型的数据结构,很多业务场景下的问题都可以很自然地映射到这些数据结构上。除此之外,通过复制、持久化和客户端分片等特性,我们可以很方便地将Redis扩展成一个能够包含数百GB数据、每秒处理上百万次请求的系统。

Redis支持的数据结构

Redis支持诸如字符串(string)、哈希(hashe)、列表(list)、集合(set)、带范围查询的排序集合(sorted set)、bitmap、hyperloglog、带半径查询的地理空间索引(geospatial index)和流(stream)等数据结构。

Redis应用场景

  • 缓存系统,减轻主数据库(MySQL)的压力。
  • 计数场景,比如微博、抖音中的关注数和粉丝数。
  • 热门排行榜,需要排序的场景特别适合使用ZSET。
  • 利用 LIST 可以实现队列的功能。
  • 利用 HyperLogLog 统计UV、PV等数据。
  • 使用 geospatial index 进行地理位置相关查询。
1
2
redis-server.exe redis.windows.conf
redis-cli.exe -h 127.0.0.1 -p 6379

go-redis

连接

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
package main

import (
"fmt"
"github.com/go-redis/redis"
)

// 声明一个全局的rdb变量
var rdb *redis.Client

// 初始化连接
func initClient() (err error) {
rdb = redis.NewClient(&redis.Options{
Addr: "localhost:6379",
Password: "",
DB: 0, // 使用默认DB
PoolSize: 100, // 连接池大小
})

_, err = rdb.Ping().Result()
return err

}

func main() {
if err := initClient(); err != nil {
fmt.Printf("init redis client failed, err:%v\n", err)
return
}
fmt.Println("connect redis success~~~")
defer rdb.Close()
}

哨兵模式

1
2
3
4
rdb := redis.NewFailoverClient(&redis.FailoverOptions{
MasterName: "master-name",
SentinelAddrs: []string{":9126", ":9127", ":9128"},
})

集群模式

1
2
3
4
5
6
7
rdb := redis.NewClusterClient(&redis.ClusterOptions{
Addrs: []string{":7000", ":7001", ":7002", ":7003", ":7004", ":7005"},

// 若要根据延迟或随机路由命令,请启用以下命令之一
// RouteByLatency: true,
// RouteRandomly: true,
})

使用

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
package main

import (
"fmt"
"github.com/go-redis/redis"
)

// 声明一个全局的rdb变量
var rdb *redis.Client

// 初始化连接
func initClient() (err error) {
rdb = redis.NewClient(&redis.Options{
Addr: "localhost:6379",
Password: "",
DB: 0, // 使用默认DB
PoolSize: 100, // 连接池大小
})

_, err = rdb.Ping().Result()
return err

}

//set/get的例子
func redisExample() {
err := rdb.Set("age", 21, 0).Err()
if err != nil {
fmt.Printf("set age failed, err%v\n", err)
return
}
val, err := rdb.Get("age").Result()
if err != nil {
fmt.Printf("get age failed, err%v\n", err)
return
}
fmt.Println("age", val)
val2, err := rdb.Get("name").Result()
if err == redis.Nil { // go-redis 库提供了一个 redis.Nil 错误来表示 Key 不存在的错误。
fmt.Printf("name does not exist")
} else if err != nil {
fmt.Printf("get name failed, err%v\n", err)
return
} else {
fmt.Println("name", val2)
}
}

func main() {
if err := initClient(); err != nil {
fmt.Printf("init redis client failed, err:%v\n", err)
return
}
fmt.Println("connect redis success~~~")
redisExample()
defer rdb.Close()
}

go-redis 库提供了一个 redis.Nil 错误来表示 Key 不存在的错误。因此在使用 go-redis 时需要注意对返回错误的判断。在某些场景下我们应该区别处理 redis.Nil 和其他不为 nil 的错误。

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
package main

import (
"context"
"fmt"
"github.com/go-redis/redis/v8"
"time"
)

// 声明一个全局的rdb变量
var rdb *redis.Client

// 初始化连接
func initClient() (err error) {
rdb = redis.NewClient(&redis.Options{
Addr: "localhost:6379",
Password: "",
DB: 0, // 使用默认DB
PoolSize: 100, // 连接池大小
})
return err

}

// zsetDemo 操作zset示例
func zsetDemo() {
// key
zsetKey := "language_rank"
// value
languages := []*redis.Z{
{Score: 90.0, Member: "Golang"},
{Score: 98.0, Member: "Java"},
{Score: 95.0, Member: "Python"},
{Score: 97.0, Member: "JavaScript"},
{Score: 99.0, Member: "C/C++"},
}
ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
defer cancel()

// ZADD
err := rdb.ZAdd(ctx, zsetKey, languages...).Err()
if err != nil {
fmt.Printf("zadd failed, err:%v\n", err)
return
}
fmt.Println("zadd success")

// 把Golang的分数加10
newScore, err := rdb.ZIncrBy(ctx, zsetKey, 10.0, "Golang").Result()
if err != nil {
fmt.Printf("zincrby failed, err:%v\n", err)
return
}
fmt.Printf("Golang's score is %f now.\n", newScore)

// 取分数最高的3个
ret := rdb.ZRevRangeWithScores(ctx, zsetKey, 0, 2).Val()
for _, z := range ret {
fmt.Println(z.Member, z.Score)
}

// 取95~100分的
op := &redis.ZRangeBy{
Min: "95",
Max: "100",
}
ret, err = rdb.ZRangeByScoreWithScores(ctx, zsetKey, op).Result()
if err != nil {
fmt.Printf("zrangebyscore failed, err:%v\n", err)
return
}
for _, z := range ret {
fmt.Println(z.Member, z.Score)
}
}
func main() {
if err := initClient(); err != nil {
fmt.Printf("init redis client failed, err:%v\n", err)
return
}
fmt.Println("connect redis success~~~")

zsetDemo()
defer rdb.Close()
}