介绍

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

Redis支持的数据结构

Redis支持诸如字符串(strings)、哈希(hashes)、列表(lists)、集合(sets)、带范围查询的排序集合(sortedsets)、位图(bitmaps)、hyperloglogs、带半径查询和流的地理空间索引等数据结构(geospatial indexes)。

Redis应用场景

  • 1、缓存系统,减轻主数据库(MySql)的压力。
  • 2、计数场景,如微博、抖音中的关注数和粉丝数。
  • 3、热门排行榜,需要排序的场景特别适合使用ZSET。
  • 4、利用LIST可以实现队列的功能。

准备Redis环境

使用Docker启动一个Redis环境。

1
$ docker pull redis:latest

运行容器:

1
$ docker run -itd --name redis-test -p 6379:6379 redis

连接测试:

1
$ docker exec -it redis-test /bin/bash

go-redis库

安装

1
go get -u github.com/go-redis/redis

可能会有部分库下载不下来,需手动下载:

1
git clone https://github.com/open-telemetry/opentelemetry-go
1
git clone https://github.com/golang/exp.git

连接:

普通连接:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//声明一个全局的redisDb变量
var redisDb *redis.Client
var ctx = context.Background()

//初始化连接
func initRedisDb() (err error){
redisDb = redis.NewClient(&redis.Options{
Addr: "47.106.172.60:6379",
Password: "",
DB: 0,
})

_, err = redisDb.Ping(context.Background()).Result()
if err != nil {
return err
}
fmt.Println("init RedisDb success")
return nil
}

连接Redis哨兵模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//声明一个全局的redisDb变量
var redisDb *redis.Client
var ctx = context.Background()

//初始化连接 - 哨兵模式
func initRedisDbWithSentinel() (err error){
redisDb = redis.NewFailoverClient(&redis.FailoverOptions{
MasterName: "master",
SentinelAddrs: []string{"x.x.x.x:26379", "xx.xx.xx.xx:26379", "xxxx.xxx.xxx.xxx:26379"},
DB: 0,
})

_, err = redisDb.Ping(context.Background()).Result()
if err != nil {
return err
}
fmt.Println("init RedisDbWithSentinel success")
return nil
}

连接Redis集群

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//声明一个全局的redisClusterDb变量 用于Redis集群Cluster
var redisClusterDb *redis.ClusterClient
var ctx = context.Background()

//初始化连接 - Redis集群
func initRedisDbWithCluster() (err error){
redisClusterDb = redis.NewClusterClient(&redis.ClusterOptions{
Addrs: []string{":7000", "7001", "7002", "7003", "7004", "7005"},
})

_, err = redisClusterDb.Ping(context.Background()).Result()
if err != nil {
return err
}
fmt.Println("init RedisDbWithCluster success")
return nil
}

基本使用

set/get

get

1
2
3
4
5
func RedisSet(){
set := redisDb.Set(ctx, "score", 100, 0)
fmt.Printf("set: %#v\n", set)
fmt.Printf("stautes: %#v\n", set.Val())
}

set

1
2
3
4
5
func RedisGet(){
get := redisDb.Get(ctx, "score")
fmt.Printf("get: %#v\n", get)
fmt.Printf("value: %v\n", get.Val())
}

zset

Command 描述
ZADD 向有序集合添加一个或多个成员,或者更新已存在成员的分数
ZCARD 获取有序集合的成员数
ZCOUNT 计算有序集合中指定区间分数的成员
ZINCRBY 有序集合中对指定成员的分数加上增量 incremen
ZINTERSTORE 计算一个或给定多个有序集合的交集并将结果储存在新的有序集合key中, 结果集中某个成员的分数值是给定集合下该成员的分数之和
ZLEXCOUNT 计算有序集合中指定字典区间的成员
ZRANGE 通过索引区间返回有序集合指定区间内的成员
ZRANGEBYLEX 通过字典区间返回有序集合的成员
ZRANGEBYSCORE 通过分数返回有序集合指定区间的成员
ZRANK 返回有序集合指定成员的索引
ZREM 移除有序集合中的一个或多个成员
ZREMRANGEBYLEX 移除有序集合中给定的字典区间的所有成员
ZREMRANGEBYRANK 移除有序集合中给定的排名区间的所有成员
ZREMRANGEBYSCORE 移除有序集合中给定分数区间的成员
ZREVRANGE 返回有序集合中指定区间的成员,通过索引, 分数从高到低
ZREVRANGEBYSCORE 返回有序集合指定分数区间的成员,分数从高到低
ZREVRANK 返回有序集合中指定成员的排名, 有序集合成员按分数值递减
ZSCORE 返回有序集合中成员的分数值
ZUNIONSTORE 计算给定的一个或多个有序集合的并集,并储存在新的key中
ZSCAN 迭代有序集合中的元素

1、ZADD

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//1、向有序集合添加一个或多个成员,或者更新已存在成员的分数
func RedisZAdd() {
languages := []*redis.Z{
{Score: 90, Member: "Golang"},
{Score: 98, Member: "Java"},
{Score: 95, Member: "Python"},
{Score: 97, Member: "JavaScript"},
{Score: 99, Member: "C/C++"},
}
//ZAdd
zAdd := redisDb.ZAdd(ctx, zSetKey, languages...)
fmt.Printf("ZAdd: %#v\n", zAdd)
fmt.Printf("ZAdd value: %#v\n", zAdd.Val())
}

2、ZCARD

1
2
3
4
5
//2、获取有序集合的成员数
func Zcard() {
zCard := redisDb.ZCard(ctx, zSetKey)
fmt.Printf("zCard is %#v now.\n", zCard)
}

3、ZCOUNT

1
2
3
4
5
//3、计算有序集合中指定区间分数的成员
func ZCount() {
zCount := redisDb.ZCount(ctx, zSetKey, "90", "100")
fmt.Printf("zCount is %#v now.\n", zCount)
}

4、ZINCRBY

1
2
3
4
5
//4、有序集合中对指定成员的分数加上增量 increment
func RedisZIncrBy() {
zIncrBy := redisDb.ZIncrBy(ctx, zSetKey, 10.0, "Golang")
fmt.Printf("Golang score is %v now.\n", zIncrBy)
}

5、ZINTERSTORE

1
2
3
4
5
6
7
//5、计算一个或给定多个有序集合的交集并将结果储存在新的有序集合key中, 结果集中某个成员的分数值是给定集合下该成员的分数之和
func RsdisZInterStore() {
zInterStore := redisDb.ZInterStore(ctx, zSetKey_New, &redis.ZStore{
Keys: []string{zSetKey, zSetKey1},
})
fmt.Printf("zInterStore is %#v now.\n", zInterStore)
}

6、ZLEXCOUNT

1
2
3
4
5
//6、计算有序集合中指定字典区间的成员
func RedisZLexCount(){
zLexCount := redisDb.ZLexCount(ctx, zSetKey, "[C/C++", "[Java")
fmt.Printf("zLexCount is %#v now.\n", zLexCount)
}

7、ZRANGE

1
2
3
4
5
//7、通过索引区间返回有序集合指定区间内的成员
func RedisZRange(){
zRange := redisDb.ZRange(ctx, zSetKey, 0, 100)
fmt.Printf("zRange is %#v now.\n", zRange)
}

8、ZRANGEBYLEX

1
2
3
4
5
6
7
8
//8、通过字典区间返回有序集合的成员
func RedisZRangeByLex(){
zRangeByLex := redisDb.ZRangeByLex(ctx, zSetKey, &redis.ZRangeBy{
Min: "[C/C++",
Max: "[Java",
})
fmt.Printf("zRangeByLex is %#v now.\n", zRangeByLex)
}

9、ZRANGEBYSCORE

1
2
3
4
5
6
7
8
//9、通过分数返回有序集合指定区间的成员
func RedisZRangeByScore(){
zRangeByScore := redisDb.ZRangeByScore(ctx, zSetKey, &redis.ZRangeBy{
Min: "0",
Max: "95",
})
fmt.Printf("zRangeByScore is %#v now.\n", zRangeByScore)
}

10、ZRANK

1
2
3
4
5
//10、返回有序集合指定成员的索引
func RedisZRank(){
zRank := redisDb.ZRank(ctx, zSetKey, "Golang")
fmt.Printf("zRank is %#v now.\n", zRank)
}

11、ZREM

1
2
3
4
5
//11、移除有序集合中的一个或多个成员
func RedisZRem(){
zRem := redisDb.ZRem(ctx, zSetKey, "C/C++")
fmt.Printf("zRem is %#v now.\n", zRem)
}

12、ZREMRANGEBYLEX

1
2
3
4
5
//12、移除有序集合中给定的字典区间的所有成员
func RedisZRemRangeByLex(){
zRemRangeByLex := redisDb.ZRemRangeByLex(ctx, zSetKey, "[C/C++", "[Java")
fmt.Printf("zRemRangeByLex is %#v now.\n", zRemRangeByLex)
}

13、ZREMRANGEBYRANK

1
2
3
4
5
//13、移除有序集合中给定的排名区间的所有成员
func RedisZRemRangeByRank(){
zRemRangeByRank := redisDb.ZRemRangeByRank(ctx, zSetKey, 0, 1)
fmt.Printf("zRemRangeByRank is %#v now.\n", zRemRangeByRank)
}

14、ZREMRANGEBYSCORE

1
2
3
4
5
//14、移除有序集合中给定分数区间的成员
func RedisZRemRangeByScore(){
zRemRangeByScore := redisDb.ZRemRangeByScore(ctx, zSetKey, "0", "100")
fmt.Printf("zRemRangeByScore is %#v now.\n", zRemRangeByScore)
}

15、ZREVRANGE

1
2
3
4
5
//15、返回有序集合中指定区间的成员,通过索引, 分数从高到低
func RedisZRevRange(){
zRevRange := redisDb.ZRevRange(ctx, zSetKey, 0, 1)
fmt.Printf("zRevRange is %#v now.\n", zRevRange)
}

16、ZREVRANGEBYSCORE

1
2
3
4
5
//16、返回有序集合指定分数区间的成员,分数从高到低
func RedisZRevRangeByScore() {
zRevRangeByScore := redisDb.ZRevRangeByScore(ctx, zSetKey, &redis.ZRangeBy{Min: "0", Max: "95"})
fmt.Printf("zRevRangeByScore: %#v\n", zRevRangeByScore)
}

17、ZREVRANK

1
2
3
4
5
//17、返回有序集合中指定成员的排名, 有序集合成员按分数值递减
func RedisZRevRank(){
zRevRank := redisDb.ZRevRank(ctx, zSetKey, "Java")
fmt.Printf("zRevRank is %#v\n", zRevRank)
}

18、ZSCORE

1
2
3
4
5
//18、返回有序集合中成员的分数值
func RedisZScore(){
zScore := redisDb.ZScore(ctx, zSetKey, "Java")
fmt.Printf("zScore is %#v now.\n", zScore)
}

19、ZUNIONSTORE

1
2
3
4
5
6
7
//19、计算给定的一个或多个有序集合的并集,并储存在新的key中
func RedisZUnIonStore(){
zUnionStore := redisDb.ZUnionStore(ctx, zSetKey_New1, &redis.ZStore{
Keys: []string{zSetKey, zSetKey1},
})
fmt.Printf("zUnionStore is %#v now.\n", zUnionStore)
}

20、ZSCAN

1
2
3
4
5
//20、迭代有序集合中的元素
func RedisZScan(){
zScan := redisDb.ZScan(ctx, zSetKey, 0, "J*", 1)
fmt.Printf("zScan is %#v now.\n", zScan)
}

Pipeline

Pipeline主要是一种网络优化,它本质上意味着客户端缓冲一堆命令并一次性将他们发送到服务器。这些命令不保证在事务中执行。这样做的好处是节省了每一个命令的网络往返时间(RTT)。

Pipeline

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func Pipeline(){
pipeline := redisDb.Pipeline()
pipeline.ZAdd(ctx, zSetKey, []*redis.Z{
{Member: "C/C++", Score: 90},
{Member: "Python", Score: 95},
{Member: "Golang", Score: 93},
{Member: "Java", Score: 99},
}...)
pipeline.ZRevRangeByScore(ctx, zSetKey, &redis.ZRangeBy{
Min: "93",
Max: "100",
})
cmders, err := pipeline.Exec(ctx)
if err != nil {
fmt.Printf("exec pipeline failed, err:%#v\n", err)
}
fmt.Printf("cmders is:%#v\n", cmders)
}

Pipelined

1
2
3
4
5
6
7
8
9
10
func Pipelined(){
var cmd *redis.StringSliceCmd
redisDb.Pipelined(ctx, func(pipeliner redis.Pipeliner) error {
pipeliner.ZAdd(ctx, zSetKey, &redis.Z{Score: 96, Member: "JavaScript"})
pipeliner.ZRemRangeByLex(ctx, zSetKey, "Python", "Python")
cmd = pipeliner.ZRange(ctx, zSetKey, 0, 100)
return nil
})
fmt.Println(cmd.Val())
}

将两次命令一次发送给Redis Server端执行,只与服务端交互一次,减少减少交互的时间损耗。

事务

Redis是单线程的,因此单个命令始终是原子的,但是来自不同客户端的两个给定命令可以依次执行,例如他们在交替执行。但是,Multi/exex能够确保在mulit/exec两个语句之间的命令之间没有其他客户端正在执行命令。

TxPipeline:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func TxPipeline() {
txPipeline := redisDb.TxPipeline()
txPipeline.ZAdd(ctx, zSetKey, []*redis.Z{
{Member: "C/C++", Score: 90},
{Member: "Python", Score: 95},
{Member: "Golang", Score: 93},
{Member: "Java", Score: 99},
}...)
txPipeline.ZRevRangeByScore(ctx, zSetKey, &redis.ZRangeBy{
Min: "93",
Max: "100",
})
cmders, err := txPipeline.Exec(ctx)
if err != nil {
fmt.Printf("exec txPipeline failed, err:%#v\n", err)
}
fmt.Printf("cmders is:%#v\n", cmders)
}

TxPipelined:

1
2
3
4
5
6
7
8
9
10
func TxPipelined(){
var cmd *redis.StringSliceCmd
redisDb.TxPipelined(ctx, func(pipeliner redis.Pipeliner) error {
pipeliner.ZAdd(ctx, zSetKey, &redis.Z{Score: 96, Member: "JavaScript"})
pipeliner.ZRemRangeByLex(ctx, zSetKey, "Python", "Python")
cmd = pipeliner.ZRange(ctx, zSetKey, 0, 100)
return nil
})
fmt.Println(cmd.Val())
}

上面的代码类似执行了下面的redis命令:

1
2
3
4
5
MULIT
MYREDISCOMMAND1
MYREDISCOMMAND2
MYREDISCOMMAND3
EXEX

Watch

在某些场景下,我们除了要使用MULTI/EXEC命令外,还需要配合使用WATCH命令。在用户使用WATCH命令监视某个键之后,直到该用户执行EXEC命令的这段时间里,如果有其他用户抢先对被监视的键值进行了替换、更新、删除等操作,那么用户尝试执行EXEC的时候,事务将失败并返回一个错误,用户可以根据这个错误选择重试或者放弃事务。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func Watch(){
redisDb.Watch(ctx, func(tx *redis.Tx) error {
n, err := tx.Get(ctx, key).Int()
if err != nil && err != redis.Nil {
return err
}
tx.Pipelined(ctx, func(pipeliner redis.Pipeliner) error {
cmd := pipeliner.Set(ctx, key, n+1, 0)
fmt.Println(cmd.Val())
return nil
})
return nil
})
}