介绍
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
| 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
| 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
| var redisClusterDb *redis.ClusterClient var ctx = context.Background()
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
| 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 := 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
| func Zcard() { zCard := redisDb.ZCard(ctx, zSetKey) fmt.Printf("zCard is %#v now.\n", zCard) }
|
3、ZCOUNT
1 2 3 4 5
| func ZCount() { zCount := redisDb.ZCount(ctx, zSetKey, "90", "100") fmt.Printf("zCount is %#v now.\n", zCount) }
|
4、ZINCRBY
1 2 3 4 5
| 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
| 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
| 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
| 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
| 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
| 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
| func RedisZRank(){ zRank := redisDb.ZRank(ctx, zSetKey, "Golang") fmt.Printf("zRank is %#v now.\n", zRank) }
|
11、ZREM
1 2 3 4 5
| func RedisZRem(){ zRem := redisDb.ZRem(ctx, zSetKey, "C/C++") fmt.Printf("zRem is %#v now.\n", zRem) }
|
12、ZREMRANGEBYLEX
1 2 3 4 5
| 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
| func RedisZRemRangeByRank(){ zRemRangeByRank := redisDb.ZRemRangeByRank(ctx, zSetKey, 0, 1) fmt.Printf("zRemRangeByRank is %#v now.\n", zRemRangeByRank) }
|
14、ZREMRANGEBYSCORE
1 2 3 4 5
| func RedisZRemRangeByScore(){ zRemRangeByScore := redisDb.ZRemRangeByScore(ctx, zSetKey, "0", "100") fmt.Printf("zRemRangeByScore is %#v now.\n", zRemRangeByScore) }
|
15、ZREVRANGE
1 2 3 4 5
| func RedisZRevRange(){ zRevRange := redisDb.ZRevRange(ctx, zSetKey, 0, 1) fmt.Printf("zRevRange is %#v now.\n", zRevRange) }
|
16、ZREVRANGEBYSCORE
1 2 3 4 5
| 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
| func RedisZRevRank(){ zRevRank := redisDb.ZRevRank(ctx, zSetKey, "Java") fmt.Printf("zRevRank is %#v\n", zRevRank) }
|
18、ZSCORE
1 2 3 4 5
| 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
| 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
| 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 }) }
|