Go操作Redis
Redis是一个开源的内存数据库,Redis提供了多种不同类型的数据结构,很多业务场景下的问题都可以很自然地映射到这些数据结构上。除此之外,通过复制、持久化和客户端分片等特性,我们可以很方便地将Redis扩展成一个能够包含数百GB数据、每秒处理上百万次请求的系统。
Redis支持的数据结构:
- 字符串(string)
- 哈希(hash)
- 列表(list)
- 集合(set)
- 有序集合(sorted set)
- 位图 ( Bitmaps )
- 基数统计 ( HyperLogLogs )
Redis应用场景:
- 缓存系统,减轻主数据库(MySQL)的压力
- 计数场景,比如微博、抖音中的关注数和粉丝数
- 热门排行榜,需要排序的场景特别适合使用ZSET
- 利用LIST可以实现队列的功能
关于Redis的介绍这里不再多说。可以参考Redis入门 笔记。
关于Redis的安装也不再多说,本地安装和docker安装(Docker安装Redis)。
区别于另一个比较常用的Go语言redis client库:redigo,我们这里采用https://github.com/go-redis/redis连接Redis数据库并进行操作,因为go-redis
支持连接哨兵及集群模式的Redis。(See Comparing go-redis vs redigo.)
golang redis快速入门教程(老版本,没有升级到V8)
Go操作Redis实战(go-redis v8)
Github:go -redis
go-redis V8新版本相关
Go Redis官网(建议多看官网!)
go-redis v8 Documentation
If you are using Redis 6, install go-redis/v8:
1
| go get github.com/go-redis/redis/v8
|
If you are using Redis 7, install go-redis/v9 (currently in beta):
1
| go get github.com/go-redis/redis/v9
|
客户端连接
代码如下:
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
| package main
import ( "context" "fmt" "log"
"github.com/go-redis/redis/v8" )
var ctx = context.Background() var rdb *redis.Client
func main() { result, err := rdb.Ping(ctx).Result() if err != nil { log.Fatalln("连接redis出错,错误信息:", err) } fmt.Println("成功连接redis") fmt.Println(result) }
func init() { rdb = redis.NewClient(&redis.Options{ Addr: "127.0.0.1:6379", Password: "", DB: 10, }) }
|
关于Context,官网是这样说的:
Every Redis command accepts a context that you can use to set timeouts or propagate some information, for example, tracing context.
1
| ctx := context.Background()
|
Background返回一个非空的Context。 它永远不会被取消,没有值,也没有期限。 它通常在main函数,初始化和测试时使用,并用作传入请求的顶级上下文。
基本指令
基本指令
Keys():根据正则获取keys
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
| package main
import ( "context" "fmt" "log"
"github.com/go-redis/redis/v8" )
var ctx = context.Background() var rdb *redis.Client
func main() { keys, err := rdb.Keys(ctx, "*").Result() if err != nil { log.Fatalln(err) } fmt.Println(keys) fmt.Printf("%T", keys) }
func init() { rdb = redis.NewClient(&redis.Options{ Addr: "127.0.0.1:6379", Password: "", DB: 10, }) }
|
Type():获取key对应值得类型
Type()
:获取一个key对应值的类型。
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
| package main
import ( "context" "fmt" "log"
"github.com/go-redis/redis/v8" )
var ctx = context.Background() var rdb *redis.Client
func main() { vType, err := rdb.Type(ctx, "hash1").Result() if err != nil { log.Fatalln(err) } fmt.Println(vType) }
func init() { rdb = redis.NewClient(&redis.Options{ Addr: "127.0.0.1:6379", Password: "", DB: 10, }) }
|
Del():删除缓存项
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
| package main
import ( "context" "fmt" "log"
"github.com/go-redis/redis/v8" )
var ctx = context.Background() var rdb *redis.Client
func main() { n, err := rdb.Del(ctx, "test", "hello").Result() if err != nil { log.Fatalln(err) } fmt.Println("成功删除了", n, "个key") }
func init() { rdb = redis.NewClient(&redis.Options{ Addr: "127.0.0.1:6379", Password: "", DB: 10, }) }
|
Exists():检测缓存项是否存在
Exists()
:用于检测某个key是否存在。
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 ( "context" "fmt" "log"
"github.com/go-redis/redis/v8" )
var ctx = context.Background() var rdb *redis.Client
func main() { n, err := rdb.Exists(ctx, "test").Result() if err != nil { log.Fatalln(err) } if n > 0 { fmt.Println("存在") } else { fmt.Println("不存在") } }
func init() { rdb = redis.NewClient(&redis.Options{ Addr: "127.0.0.1:6379", Password: "", DB: 10, }) }
|
注:Exists()
方法可以传入多个key,返回的第一个结果表示存在的key的数量,不过工作中我们一般不同时判断多个key是否存在,一般就判断一个key,所以判断是否大于0即可;如果判断判断传入的多个key是否都存在,则返回的结果的值要和传入的key的数量相等。
1
| func (c cmdable) Exists(ctx context.Context, keys ...string) *IntCmd {...}
|
Expire()&ExpireAt():设置有效期
使用场景:设置好key后,再设置key的有效期。
Expire()
:设置某个时间段(time.Duration
)后过期
ExpireAt()
:在某个时间点(time.Time
)过期失效
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
| package main
import ( "context" "fmt" "log" "time"
"github.com/go-redis/redis/v8" )
var ctx = context.Background() var rdb *redis.Client
func main() { result, err := rdb.Expire(ctx, "test1", time.Minute*3).Result() if err != nil { log.Fatalln(err) } if result { fmt.Println("设置成功! test1将于3分钟后过期") } else { fmt.Println("设置失败!") }
str := "2022-07-14T12:52:28Z" t, err := time.Parse("2006-01-02T15:04:05Z", str) if err != nil { log.Fatalln(err) } b, err := rdb.ExpireAt(ctx, "test2", t).Result() if b { fmt.Println("设置成功! test2将于2022-07-14 12:52:28过期") } else { fmt.Println("设置失败!") }
}
func init() { rdb = redis.NewClient(&redis.Options{ Addr: "127.0.0.1:6379", Password: "", DB: 10, }) }
|
TTL(),PTTL():获取有效期
TTL()
:获取某个键的剩余有效期(单位:秒(s))
PTTL()
:获取某个键的剩余有效期(单位:毫秒(ms))
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
| package main
import ( "context" "fmt" "log" "time"
"github.com/go-redis/redis/v8" )
var ctx = context.Background() var rdb *redis.Client
func main() { rdb.Expire(ctx, "test", time.Minute*1)
ttl, err := rdb.TTL(ctx, "test").Result() if err != nil { log.Fatalln(err) } fmt.Println(ttl) pttl, err := rdb.PTTL(ctx, "test").Result() if err != nil { log.Fatalln(err) } fmt.Println(pttl) }
func init() { rdb = redis.NewClient(&redis.Options{ Addr: "127.0.0.1:6379", Password: "", DB: 10, }) }
|
DBSize():查看当前数据库key的数量
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
| package main
import ( "context" "fmt" "log"
"github.com/go-redis/redis/v8" )
var ctx = context.Background() var rdb *redis.Client
func main() { num, err := rdb.DBSize(ctx).Result() if err != nil { log.Fatalln(err) } fmt.Println(num) }
func init() { rdb = redis.NewClient(&redis.Options{ Addr: "127.0.0.1:6379", Password: "", DB: 10, }) }
|
FlushDB():清空当前数据
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
| package main
import ( "context" "fmt" "log"
"github.com/go-redis/redis/v8" )
var ctx = context.Background() var rdb *redis.Client
func main() { result, err := rdb.FlushDB(ctx).Result() if err != nil { log.Fatalln(err) } fmt.Println(result) }
func init() { rdb = redis.NewClient(&redis.Options{ Addr: "127.0.0.1:6379", Password: "", DB: 10, }) }
|
FlushAll():清空所有数据库
慎用!
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
| package main
import ( "context" "fmt" "log"
"github.com/go-redis/redis/v8" )
var ctx = context.Background() var rdb *redis.Client
func main() { result, err := rdb.FlushAll(ctx).Result() if err != nil { log.Fatalln(err) } fmt.Println(result) }
func init() { rdb = redis.NewClient(&redis.Options{ Addr: "192.168.219.101:6379", Password: "1234", DB: 10, }) }
|
字符串(string)类型
这里先介绍常用的字符串操作,此外,字符串(string)类型还有MGet()
、Mset()
、MSetNX()
等同时操作多个key的方法,详见:https://pkg.go.dev/github.com/go-redis/redis/v8
Set() 设置
仅仅支持字符串(包含数字)操作,不支持内置数据编码功能。如果需要存储Go的非字符串类型,需要提前手动序列化,获取时再反序列化。
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
| package main
import ( "context" "log" "time"
"github.com/go-redis/redis/v8" )
var ctx = context.Background() var rdb *redis.Client
func main() { err := rdb.Set(ctx, "hello", "world", 0).Err() if err != nil { log.Fatalln(err) } err = rdb.Set(ctx, "test", "即将过期", time.Minute*1).Err() if err != nil { log.Fatalln(err) } }
func init() { rdb = redis.NewClient(&redis.Options{ Addr: "127.0.0.1:6379", Password: "", DB: 10, }) }
|
SetEX() 设置并指定过期时间
设置键的同时,设置过期时间。
当某个key已经存在时,可以用SetEX()
来对这个key设置过期时间。SetEX()
不管该key是否已经存在缓存中,直接覆盖。
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
| package main
import ( "context" "log" "time"
"github.com/go-redis/redis/v8" )
var ctx = context.Background() var rdb *redis.Client
func main() { err := rdb.SetEX(ctx, "hello", "world", time.Minute*3).Err() if err != nil { log.Fatalln(err) } }
func init() { rdb = redis.NewClient(&redis.Options{ Addr: "127.0.0.1:6379", Password: "", DB: 10, }) }
|
SetNX() 设置并指定过期时间
SetEX()
与SetNX()
的区别:
还有一点需要注意:如果我们想知道我们调用SetNX()
是否设置成功了,可以接着调用Result()
方法,返回的第一个值表示是否设置成功了:
- 如果返回false,说明缓存Key已经存在,此次操作虽然没有错误,但是是没有起任何效果的
- 如果返回true,表示在此之前key是不存在缓存中的,操作是成功的
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
| package main
import ( "context" "fmt" "log" "time"
"github.com/go-redis/redis/v8" )
var ctx = context.Background() var rdb *redis.Client
func main() { ok, err := rdb.SetNX(ctx, "hello", "world", time.Minute*5).Result() if err != nil { log.Fatalln(err) } if ok { fmt.Println("key不存在,操作成功") } else { fmt.Println("key存在,操作不成功") } }
func init() { rdb = redis.NewClient(&redis.Options{ Addr: "127.0.0.1:6379", Password: "", DB: 10, }) }
|
Get() 获取
如果要获取的key在缓存中并不存在,Get()
方法将会返回redis.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
| package main
import ( "context" "fmt" "log"
"github.com/go-redis/redis/v8" )
var ctx = context.Background() var rdb *redis.Client
func main() { val, err := rdb.Get(ctx, "hello").Result() if err != nil { log.Fatalln(err) } fmt.Println(val)
val2, err := rdb.Get(ctx, "test1").Result() if err != nil { if err == redis.Nil { fmt.Println("key不存在") } else { log.Fatalln(err) } return } fmt.Println(val2) }
func init() { rdb = redis.NewClient(&redis.Options{ Addr: "127.0.0.1:6379", Password: "", DB: 10, }) }
|
GetRange() 字符串截取
GetRange()
:用来截取字符串的部分内容,最后两个参数分别是索引的开始位置和结束位置(左闭右闭,[start,end]
)。
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
| package main
import ( "context" "fmt" "log"
"github.com/go-redis/redis/v8" )
var ctx = context.Background() var rdb *redis.Client
func main() { val, err := rdb.GetRange(ctx, "test", 0, 3).Result() if err != nil { log.Fatalln(err) } fmt.Println(val) }
func init() { rdb = redis.NewClient(&redis.Options{ Addr: "127.0.0.1:6379", Password: "", DB: 10, }) }
|
注:即使key不存在,调用GetRange()
也不会报错,只是返回的截取结果是空""
,可以使用fmt.Printf("%q\n", val)
来打印测试
Incr() 增加(+1)
Incr()
、IncrBy()
都是操作数字,对数字进行增加的操作,incr()
是执行原子加1操作,incrBy()
是增加指定的数。
所谓原子操作是指不会被线程调度机制打断的操作:这种操作一旦开始,就一直运行到结束,中间不会有任何context witch(切换到另一个线程)。
- 在单线程中,能够在单条指令中完成的操作都可以认为是“原子操作”,因为中断只能发生于指令之间
- 在多线程中,不能被其它进程(线程)打断的操作叫原子操作
Redis单命令的原子性主要得益于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 33 34
| package main
import ( "context" "fmt" "log"
"github.com/go-redis/redis/v8" )
var ctx = context.Background() var rdb *redis.Client
func main() { val, err := rdb.Get(ctx, "number").Result() if err != nil { log.Fatalln(err) } fmt.Println("val为:", val) val2, err := rdb.Incr(ctx, "number").Result() if err != nil { log.Fatalln(err) } fmt.Println("Incr后的val为:", val2) }
func init() { rdb = redis.NewClient(&redis.Options{ Addr: "127.0.0.1:6379", Password: "", DB: 10, }) }
|
IncrBy() 增加(按指定步长)
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
| package main
import ( "context" "fmt" "log"
"github.com/go-redis/redis/v8" )
var ctx = context.Background() var rdb *redis.Client
func main() { val, err := rdb.Get(ctx, "number").Result() if err != nil { log.Fatalln(err) } fmt.Println("val为:", val) val2, err := rdb.IncrBy(ctx, "number", 10).Result() if err != nil { log.Fatalln(err) } fmt.Println("IncrBy后的val为:", val2) }
func init() { rdb = redis.NewClient(&redis.Options{ Addr: "127.0.0.1:6379", Password: "", DB: 10, }) }
|
Decr() 减少(-1)
Decr()
和DecrBy()
方法是对数字进行减的操作,和Incr()
和IncrBy()
正好相反。
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
| package main
import ( "context" "fmt" "log"
"github.com/go-redis/redis/v8" )
var ctx = context.Background() var rdb *redis.Client
func main() { val, err := rdb.Get(ctx, "number").Result() if err != nil { log.Fatalln(err) } fmt.Println("val为:", val) val2, err := rdb.Decr(ctx, "number").Result() if err != nil { log.Fatalln(err) } fmt.Println("Decr后的val为:", val2) }
func init() { rdb = redis.NewClient(&redis.Options{ Addr: "127.0.0.1:6379", Password: "", DB: 10, }) }
|
DecrBy() 减少(按指定步长)
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
| package main
import ( "context" "fmt" "log"
"github.com/go-redis/redis/v8" )
var ctx = context.Background() var rdb *redis.Client
func main() { val, err := rdb.Get(ctx, "number").Result() if err != nil { log.Fatalln(err) } fmt.Println("val为:", val) val2, err := rdb.DecrBy(ctx, "number", 10).Result() if err != nil { log.Fatalln(err) } fmt.Println("DecrBy后的val为:", val2) }
func init() { rdb = redis.NewClient(&redis.Options{ Addr: "127.0.0.1:6379", Password: "", DB: 10, }) }
|
Append() 追加
Append()
:往字符串后面追加元素,返回值是字符串的总长度。
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
| package main
import ( "context" "fmt" "log"
"github.com/go-redis/redis/v8" )
var ctx = context.Background() var rdb *redis.Client
func main() { str, err := rdb.Get(ctx, "test1").Result() if err != nil { log.Fatalln(err) } fmt.Println(str) length, err := rdb.Append(ctx, "test1", "world!").Result() if err != nil { log.Fatalln(err) } fmt.Println(length)
str, err = rdb.Get(ctx, "test1").Result() if err != nil { log.Fatalln(err) } fmt.Println(str) }
func init() { rdb = redis.NewClient(&redis.Options{ Addr: "127.0.0.1:6379", Password: "", DB: 10, }) }
|
StrLen() 获取长度
StrLen()
:获取字符串的长度。
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
| package main
import ( "context" "fmt" "log"
"github.com/go-redis/redis/v8" )
var ctx = context.Background() var rdb *redis.Client
func main() { length, err := rdb.StrLen(ctx, "test").Result() if err != nil { log.Fatalln(err) } fmt.Println(length) }
func init() { rdb = redis.NewClient(&redis.Options{ Addr: "127.0.0.1:6379", Password: "", DB: 10, }) }
|
哈希(hash)类型
Redis hash
是一个string
类型的 field(字段) 和 any
类型的value(值) 的映射表,hash特别适合用于存储对象。(之前在gobang项目中用hash开存储go的struct)。Redis 中每个hash
可以存储2^32 - 1键值对(40多亿)。
当前服务器一般都是将用户登录信息保存到Redis中,这里存储用户登录信息就比较适合用hash表。hash表比string更合适。
如果我们选择使用string类型来存储用户的信息的时候,我们每次存储的时候就得先序列化成字符串才能存储到redis;从redis拿到用户信息后又得反序列化成数组或对象,这样开销比较大。如果使用hash的话我们通过key(用户ID)+field(属性标签)就可以操作对应属性数据了,既不需要重复存储数据,也不会带来序列化和并发修改控制的问题。
HSet() 设置
HSet()
支持如下格式:
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
| package main
import ( "context"
"github.com/go-redis/redis/v8" )
var ctx = context.Background() var rdb *redis.Client
func main() { rdb.HSet(ctx, "user", "key1", "value1", "key2", "value2")
s := []string{"id", "123", "name", "张三"} rdb.HSet(ctx, "user", s)
m := make(map[string]any, 0) m["sex"] = "男" m["age"] = 20 rdb.HSet(ctx, "user", m) }
func init() { rdb = redis.NewClient(&redis.Options{ Addr: "127.0.0.1:6379", Password: "", DB: 10, }) }
|
redis中的hash值:
HMset() 批量设置
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
| package main
import ( "context"
"github.com/go-redis/redis/v8" )
var ctx = context.Background() var rdb *redis.Client
func main() { m := map[string]any{ "name": "李四", "id": 123, } rdb.HMSet(ctx, "student", m) }
func init() { rdb = redis.NewClient(&redis.Options{ Addr: "127.0.0.1:6379", Password: "", DB: 10, }) }
|
redis成功存储hash值:
HGet() 获取某个元素
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
| package main
import ( "context" "fmt" "log"
"github.com/go-redis/redis/v8" )
var ctx = context.Background() var rdb *redis.Client
func main() { val, err := rdb.HGet(ctx, "student", "name").Result() if err != nil { log.Fatalln(err) } fmt.Println(val) }
func init() { rdb = redis.NewClient(&redis.Options{ Addr: "127.0.0.1:6379", Password: "", DB: 10, }) }
|
HGetAll() 获取全部元素
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
| package main
import ( "context" "fmt" "log"
"github.com/go-redis/redis/v8" )
var ctx = context.Background() var rdb *redis.Client
func main() { user, err := rdb.HGetAll(ctx, "user").Result() if err != nil { log.Fatalln(err) } fmt.Println(user) }
func init() { rdb = redis.NewClient(&redis.Options{ Addr: "127.0.0.1:6379", Password: "", DB: 10, }) }
|
HDel() 删除某个元素
HDel()
支持一次删除多个元素。在参数中写入要删除的hash类型的key即可。
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
| package main
import ( "context" "fmt" "log"
"github.com/go-redis/redis/v8" )
var ctx = context.Background() var rdb *redis.Client
func main() { result, err := rdb.HDel(ctx, "user", "key1", "key2").Result() if err != nil { log.Fatalln(err) } fmt.Println(result) }
func init() { rdb = redis.NewClient(&redis.Options{ Addr: "127.0.0.1:6379", Password: "", DB: 10, }) }
|
删除后的user:
HExists() 判断元素是否存在
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
| package main
import ( "context" "fmt" "log"
"github.com/go-redis/redis/v8" )
var ctx = context.Background() var rdb *redis.Client
func main() { exist, err := rdb.HExists(ctx, "student", "id").Result() exist2, err := rdb.HExists(ctx, "student", "age").Result() if err != nil { log.Fatalln(err) } fmt.Println(exist) fmt.Println(exist2) }
func init() { rdb = redis.NewClient(&redis.Options{ Addr: "127.0.0.1:6379", Password: "", DB: 10, }) }
|
HLen() 获取长度
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
| package main
import ( "context" "fmt" "log"
"github.com/go-redis/redis/v8" )
var ctx = context.Background() var rdb *redis.Client
func main() { length, err := rdb.HLen(ctx, "user").Result() if err != nil { log.Fatalln(err) } fmt.Println(length) }
func init() { rdb = redis.NewClient(&redis.Options{ Addr: "127.0.0.1:6379", Password: "", DB: 10, }) }
|
此时的user:
列表(list)类型
Redis list
是按插入顺序排序的string
列表。可以在列表的**头部(左边)或尾部(右边)**添加元素。列表可以包含2^32 - 1个元素(40多亿)。
LPush() 将元素从左侧压入链表
LPush()
:将数据从左侧压入链表。当指定的key不存在时,会自动创建key。
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
| package main
import ( "context" "fmt" "log"
"github.com/go-redis/redis/v8" )
var ctx = context.Background() var rdb *redis.Client
func main() { n, err := rdb.LPush(ctx, "list", 1, 2, "hello", "world").Result() if err != nil { log.Fatalln(err) } fmt.Println(n) }
func init() { rdb = redis.NewClient(&redis.Options{ Addr: "127.0.0.1:6379", Password: "", DB: 10, }) }
|
注意LPush()
是从左侧压入数据的,所以list如下:
RPush() 将元素从右侧压入链表
RPush()
:将数据从右侧压入链表。当指定的key不存在时,会自动创建key。
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
| package main
import ( "context" "fmt" "log"
"github.com/go-redis/redis/v8" )
var ctx = context.Background() var rdb *redis.Client
func main() { n, err := rdb.RPush(ctx, "list2", 1, 2, "hello", "world").Result() if err != nil { log.Fatalln(err) } fmt.Println(n) }
func init() { rdb = redis.NewClient(&redis.Options{ Addr: "127.0.0.1:6379", Password: "", DB: 10, }) }
|
RPush()
是从右侧压入数据的,所以list2如下:
LInsert() 在某个位置插入新元素
函数签名:
1
| func (c cmdable) LInsert(ctx context.Context, key, op string, pivot, value interface{}) *IntCmd
|
具体点说,是在某个元素的前面或后面插入新元素。
- op指定为
before
:在pivot前面插入新元素
- op指定为
after
:在pivot后面插入新元素
注意:即使key列表里有多个值为hello的元素,也只会在第一个值为hello的元素前面插入3,并不会在所有值为hello的前面插入3。
客户端还提供了从前面插入和从后面插入的LInsertBefore()
和LInsertAfer()
方法。
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
| package main
import ( "context" "fmt" "log"
"github.com/go-redis/redis/v8" )
var ctx = context.Background() var rdb *redis.Client
func main() { n, err := rdb.LInsert(ctx, "list2", "before", "hello", "3").Result() if err != nil { log.Fatalln(err) } fmt.Println(n) }
func init() { rdb = redis.NewClient(&redis.Options{ Addr: "127.0.0.1:6379", Password: "", DB: 10, }) }
|
LSet() 设置某个元素的值
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
| package main
import ( "context" "log"
"github.com/go-redis/redis/v8" )
var ctx = context.Background() var rdb *redis.Client
func main() { err := rdb.LSet(ctx, "list", 1, 100).Err() if err != nil { log.Fatalln(err) } }
func init() { rdb = redis.NewClient(&redis.Options{ Addr: "127.0.0.1:6379", Password: "", DB: 10, }) }
|
LLen() 获取列表元素个数
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
| package main
import ( "context" "fmt" "log"
"github.com/go-redis/redis/v8" )
var ctx = context.Background() var rdb *redis.Client
func main() { length, err := rdb.LLen(ctx, "list").Result() if err != nil { log.Fatalln(err) } fmt.Println(length) }
func init() { rdb = redis.NewClient(&redis.Options{ Addr: "127.0.0.1:6379", Password: "", DB: 10, }) }
|
LIndex() 根据索引获取列表元素
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
| package main
import ( "context" "fmt" "log"
"github.com/go-redis/redis/v8" )
var ctx = context.Background() var rdb *redis.Client
func main() { val, err := rdb.LIndex(ctx, "list", 2).Result() if err != nil { log.Fatalln(err) } fmt.Println(val) }
func init() { rdb = redis.NewClient(&redis.Options{ Addr: "127.0.0.1:6379", Password: "", DB: 10, }) }
|
LRange() 获取某个选定范围的元素集
范围是左闭右闭([start,end]
)
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
| package main
import ( "context" "fmt" "log"
"github.com/go-redis/redis/v8" )
var ctx = context.Background() var rdb *redis.Client
func main() { list, err := rdb.LRange(ctx, "list", 0, 2).Result() if err != nil { log.Fatalln(err) } fmt.Println(list) fmt.Printf("%T\n", list) }
func init() { rdb = redis.NewClient(&redis.Options{ Addr: "127.0.0.1:6379", Password: "", DB: 10, }) }
|
LPop() 从列表左侧弹出数据
Push和Pop操作,跟数据结构中的类似。
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
| package main
import ( "context" "fmt" "log"
"github.com/go-redis/redis/v8" )
var ctx = context.Background() var rdb *redis.Client
func main() { val, err := rdb.LPop(ctx, "list").Result() if err != nil { log.Fatalln(err) } fmt.Println(val) }
func init() { rdb = redis.NewClient(&redis.Options{ Addr: "127.0.0.1:6379", Password: "", DB: 10, }) }
|
RPop() 从列表右侧弹出数据
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
| package main
import ( "context" "fmt" "log"
"github.com/go-redis/redis/v8" )
var ctx = context.Background() var rdb *redis.Client
func main() { val, err := rdb.RPop(ctx, "list").Result() if err != nil { log.Fatalln(err) } fmt.Println(val) }
func init() { rdb = redis.NewClient(&redis.Options{ Addr: "127.0.0.1:6379", Password: "", DB: 10, }) }
|
LRem() 根据值移除元素
函数签名:
1
| func (c cmdable) LRem(ctx context.Context, key string, count int64, value interface{}) *IntCmd
|
count
:要删除元素的个数
value
:根据指定值删除该元素
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
| package main
import ( "context" "fmt" "log"
"github.com/go-redis/redis/v8" )
var ctx = context.Background() var rdb *redis.Client
func main() { val, err := rdb.LRem(ctx, "list", 2, "hello").Result() if err != nil { log.Fatalln(err) } fmt.Println("删除了", val, "个元素") }
func init() { rdb = redis.NewClient(&redis.Options{ Addr: "127.0.0.1:6379", Password: "", DB: 10, }) }
|
删除前:
删除后:
集合(set)类型
Redis set
对外提供的功能与list
类似是一个列表的功能,特殊之处在于set
是可以自动去重的,当你需要存储一个列表数据,又不希望出现重复数据,set
是一个很好的选择,并且set
提供了判断某个成员是否在一个set
集合内的接口,这是list
所不能提供的。
Redis的set
是string
类型的无序集合。它底层其实是一个value为null的hash表,所以添加、删除、查找的复杂度都是O(1)。
集合数据的特征:
- 元素不能重复,保持唯一性
- 元素无序,不能使用索引(下标)操作
SAdd() 添加元素
当指定的key不存在时,会自动创建key。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| package main
import ( "context"
"github.com/go-redis/redis/v8" )
var ctx = context.Background() var rdb *redis.Client
func main() { rdb.SAdd(ctx, "team", "张三", "lisi", "tom") rdb.SAdd(ctx, "team", "王五") rdb.SAdd(ctx, "team", "张三") }
func init() { rdb = redis.NewClient(&redis.Options{ Addr: "127.0.0.1:6379", Password: "", DB: 10, }) }
|
SPop() 随机弹出一个元素
SPop()
:从集合中随机弹出元素的,如果想一次弹出多个元素,可以使用SPopN()
方法。(set
的无序性,所以是随机的)
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
| package main
import ( "context" "fmt" "log"
"github.com/go-redis/redis/v8" )
var ctx = context.Background() var rdb *redis.Client
func main() { val, err := rdb.SPop(ctx, "team").Result() if err != nil { log.Fatalln(err) } fmt.Println(val) }
func init() { rdb = redis.NewClient(&redis.Options{ Addr: "127.0.0.1:6379", Password: "", DB: 10, }) }
|
SPopN() 随机弹出多个元素
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
| package main
import ( "context" "fmt" "log"
"github.com/go-redis/redis/v8" )
var ctx = context.Background() var rdb *redis.Client
func main() { val, err := rdb.SPopN(ctx, "team", 2).Result() if err != nil { log.Fatalln(err) } fmt.Println(val) fmt.Printf("%T", val) }
func init() { rdb = redis.NewClient(&redis.Options{ Addr: "127.0.0.1:6379", Password: "", DB: 10, }) }
|
SRem() 删除集合里指定的值
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
| package main
import ( "context" "fmt" "log"
"github.com/go-redis/redis/v8" )
var ctx = context.Background() var rdb *redis.Client
func main() { n, err := rdb.SRem(ctx, "team", "张三").Result() if err != nil { log.Fatalln(err) } fmt.Println(n) }
func init() { rdb = redis.NewClient(&redis.Options{ Addr: "127.0.0.1:6379", Password: "", DB: 10, }) }
|
SMembers() 获取所有成员
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
| package main
import ( "context" "fmt" "log"
"github.com/go-redis/redis/v8" )
var ctx = context.Background() var rdb *redis.Client
func main() { result, err := rdb.SMembers(ctx, "team").Result() if err != nil { log.Fatalln(err) } fmt.Println(result) fmt.Printf("%T", result) }
func init() { rdb = redis.NewClient(&redis.Options{ Addr: "127.0.0.1:6379", Password: "", DB: 10, }) }
|
SIsMember() 判断元素是否在集合中
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
| package main
import ( "context" "fmt" "log"
"github.com/go-redis/redis/v8" )
var ctx = context.Background() var rdb *redis.Client
func main() { exists, err := rdb.SIsMember(ctx, "team", "张三").Result() if err != nil { log.Fatalln(err) } fmt.Println(exists) }
func init() { rdb = redis.NewClient(&redis.Options{ Addr: "127.0.0.1:6379", Password: "", DB: 10, }) }
|
SCard() 获取集合元素个数
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
| package main
import ( "context" "fmt" "log"
"github.com/go-redis/redis/v8" )
var ctx = context.Background() var rdb *redis.Client
func main() { length, err := rdb.SCard(ctx, "team").Result() if err != nil { log.Fatalln(err) } fmt.Println(length) }
func init() { rdb = redis.NewClient(&redis.Options{ Addr: "127.0.0.1:6379", Password: "", DB: 10, }) }
|
SUnion() 并集;SDiff() 差集;SInter() 交集
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
| package main
import ( "context" "fmt" "log"
"github.com/go-redis/redis/v8" )
var ctx = context.Background() var rdb *redis.Client
func main() { rdb.SAdd(ctx, "setA", "a", "b", "c", "d") rdb.SAdd(ctx, "setB", "a", "d", "e", "f")
union, err := rdb.SUnion(ctx, "setA", "setB").Result() if err != nil { log.Fatalln(err) } fmt.Println(union)
diff, err := rdb.SDiff(ctx, "setA", "setB").Result() if err != nil { log.Fatalln(err) } fmt.Println(diff)
inter, err := rdb.SInter(ctx, "setA", "setB").Result() if err != nil { log.Fatalln(err) } fmt.Println(inter) }
func init() { rdb = redis.NewClient(&redis.Options{ Addr: "127.0.0.1:6379", Password: "", DB: 10, }) }
|
有序集合(zset)类型
Redis 有序集合zset
和集合set
一样也是string
类型元素的集合,且不允许重复的成员。
不同的是每个元素都会关联一个double
类型的分数score。redis正是通过分数来为集合中的成员进行从小到大的排序。
有序集合的成员是唯一的,但**分数(score)**却可以重复。
集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1)。 集合中最大的成员数为 2^32 - 1 (4294967295, 每个集合可存储40多亿个成员)。
ZAdd() 添加元素
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
| package main
import ( "context"
"github.com/go-redis/redis/v8" )
var ctx = context.Background() var rdb *redis.Client
func main() { rdb.ZAdd(ctx, "zSet", &redis.Z{ Score: 0, Member: "hello", }) rdb.ZAdd(ctx, "zSet", &redis.Z{ Score: 2.3, Member: "world", }) rdb.ZAdd(ctx, "zSet", &redis.Z{ Score: 3.14, Member: "pai", }) rdb.ZAdd(ctx, "zSet", &redis.Z{ Score: -12.3, Member: "张三", }) rdb.ZAdd(ctx, "zSet", &redis.Z{ Score: 10, Member: "tom", }) }
func init() { rdb = redis.NewClient(&redis.Options{ Addr: "127.0.0.1:6379", Password: "", DB: 10, }) }
|
添加后结果如下:
ZIncrBy() 增加元素分值
函数签名:
1
| func (c cmdable) ZIncrBy(ctx context.Context, key string, increment float64, member string) *FloatCmd
|
increment
参数可以为负数,表示减少score
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| package main
import ( "context"
"github.com/go-redis/redis/v8" )
var ctx = context.Background() var rdb *redis.Client
func main() { rdb.ZIncrBy(ctx, "zSet", -3.14, "pai") rdb.ZIncrBy(ctx, "zSet", 123, "hello") }
func init() { rdb = redis.NewClient(&redis.Options{ Addr: "127.0.0.1:6379", Password: "", DB: 10, }) }
|
修改前后:
ZRange()&ZRevRange() 获取根据score排序后的数据段
ZRange()
&ZRevRange()
:返回根据分值过滤之后的列表,需要提供分值区间。
ZRange()
:结果按score升序排序(自然顺序)
ZRevRangeByScore()
:结果按score降序排序
使用方法是完全一样的。
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
| package main
import ( "context" "fmt" "log"
"github.com/go-redis/redis/v8" )
var ctx = context.Background() var rdb *redis.Client
func main() { result, err := rdb.ZRangeByScore(ctx, "zSet", &redis.ZRangeBy{ Min: "2", Max: "20", Offset: 0, Count: 0, }).Result() if err != nil { log.Fatalln(err) } fmt.Println(result) }
func init() { rdb = redis.NewClient(&redis.Options{ Addr: "127.0.0.1:6379", Password: "", DB: 10, }) }
|
关于Offset
和Count
,这是分页查询常见的了。当Count
为0时,表示查询全部。
ZCard() 获取元素个数
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
| package main
import ( "context" "fmt" "log"
"github.com/go-redis/redis/v8" )
var ctx = context.Background() var rdb *redis.Client
func main() { count, err := rdb.ZCard(ctx, "zSet").Result() if err != nil { log.Fatalln(err) } fmt.Println(count) }
func init() { rdb = redis.NewClient(&redis.Options{ Addr: "127.0.0.1:6379", Password: "", DB: 10, }) }
|
ZCount() 获取区间内元素个数
函数签名:
1
| func (c cmdable) ZCount(ctx context.Context, key, min, max string) *IntCmd
|
ZCount()
:获取分值在[mim, max]
的元素的数量(左闭右闭)。
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
| package main
import ( "context" "fmt" "log"
"github.com/go-redis/redis/v8" )
var ctx = context.Background() var rdb *redis.Client
func main() { count, err := rdb.ZCount(ctx, "zSet", "2", "20").Result() if err != nil { log.Fatalln(err) } fmt.Println(count) }
func init() { rdb = redis.NewClient(&redis.Options{ Addr: "127.0.0.1:6379", Password: "", DB: 10, }) }
|
ZScore() 获取元素的score
ZScore()
:获取元素的分值score。
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
| package main
import ( "context" "fmt" "log"
"github.com/go-redis/redis/v8" )
var ctx = context.Background() var rdb *redis.Client
func main() { score, err := rdb.ZScore(ctx, "zSet", "hello").Result() if err != nil { log.Fatalln(err) } fmt.Println(score) }
func init() { rdb = redis.NewClient(&redis.Options{ Addr: "127.0.0.1:6379", Password: "", DB: 10, }) }
|
ZRank()&ZRevRank() 获取某个元素在集合中的排名
ZRank()
&ZRevRank()
:获取某个元素在集合中的排名。
ZRank()
:返回元素在集合中的升序排名情况,从0开始
ZRevRank()
:返回元素在集合中的降序排名情况,从0开始(与ZRank()
正好相反,类似倒数第几,因为是从0开始所以最末尾的元素是倒数第0)
使用方法是完全一样的。效果很简单自己玩一玩就明白函数是什么意思了。
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
| package main
import ( "context" "fmt" "log"
"github.com/go-redis/redis/v8" )
var ctx = context.Background() var rdb *redis.Client
func main() { result, err := rdb.ZRank(ctx, "zSet", "tom").Result() if err != nil { log.Fatalln(err) } fmt.Println(result) }
func init() { rdb = redis.NewClient(&redis.Options{ Addr: "127.0.0.1:6379", Password: "", DB: 10, }) }
|
ZRem() 删除元素
ZRem()
:通过元素的值来删除元素。(注意是元素值而不是score)
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
| package main
import ( "context" "fmt" "log"
"github.com/go-redis/redis/v8" )
var ctx = context.Background() var rdb *redis.Client
func main() { val, err := rdb.ZRem(ctx, "zSet", "张三").Result() if err != nil { log.Fatalln(err) } fmt.Println(val) }
func init() { rdb = redis.NewClient(&redis.Options{ Addr: "127.0.0.1:6379", Password: "", DB: 10, }) }
|
ZRemRangeByRank() 根据排名删除
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
| package main
import ( "context" "fmt" "log"
"github.com/go-redis/redis/v8" )
var ctx = context.Background() var rdb *redis.Client
func main() { val, err := rdb.ZRemRangeByRank(ctx, "zSet", 0, 1).Result() if err != nil { log.Fatalln(err) } fmt.Println(val) }
func init() { rdb = redis.NewClient(&redis.Options{ Addr: "127.0.0.1:6379", Password: "", DB: 10, }) }
|
ZRemRangeByScore() 根据分值区间删除
函数签名:
1
| func (c cmdable) ZRemRangeByScore(ctx context.Context, key, min, max string) *IntCmd
|
ZRemRangeByScore()
:删除分值区间在[min,max]
的所有元素(左闭右闭)
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
| package main
import ( "context" "fmt" "log"
"github.com/go-redis/redis/v8" )
var ctx = context.Background() var rdb *redis.Client
func main() { val, err := rdb.ZRemRangeByScore(ctx, "zSet", "100", "123").Result() if err != nil { log.Fatalln(err) } fmt.Println(val) }
func init() { rdb = redis.NewClient(&redis.Options{ Addr: "127.0.0.1:6379", Password: "", DB: 10, }) }
|