Redis数据结构
Redis 比较常见的数据结构有 string、list、hash、set、sorted set(Zset) 等,但是 Redis 还有比较高级的3种数据结构:HyperLogLog、Geo、BloomFilter。
接下来博主本人就来分享一下自己对Redis数据结构的理解(如果有误或者不懂的地方,欢迎各位小伙伴在评论区发表自己的见解,博主会及时修改并为小伙伴们解答)
传统键值存储是关联字符串值到字符串键,但是 Redis 的值不仅仅局限于简单字符串,还可以持有更复杂的数据结构。下面列的是 Redis 支持常用的数据结构,下面将逐一介绍:
- String(字符串)
- List(列表)
- Set(集合)
- Hash(哈希,键值对集合)
- SortedSet(zset,排序集合)
String(字符串)
字符串类型是Redis最基础的数据结构。首先键都是字符串类型,而且其他几种数据结构都是在字符串类型基础上构建的,所以字符串类型能为其他四种数据结构的学习奠定基础。
Redis中字符串类型可存储的值可以是字符串(简单的字符串、复杂的字符串(例如JSON、XML))、数字(整数、浮点数),甚至是二进制(图片、音频、视频),但是值最大不能超过512MB。
读写操作
- 设置值:
set key value [ex seconds] [px milliseconds] [nx|xx]
- ex seconds:为键设置秒级过期时间。
- px milliseconds:为键设置毫秒级过期时间。
- nx:键必须不存在,才可以设置成功,可用于添加。
- xx:与nx相反,键必须存在,才可以设置成功,可用于更新。
# 省略所有可选参数,不存在则直接添加;已存在则更新
127.0.0.1:6379> set name zhangsan
OK
127.0.0.1:6379> set age 18
OK
# 不存在才添加,可以使用setnx命令
127.0.0.1:6379> set sex 1 nx
OK
127.0.0.1:6379> set age 19 nx
(nil) 失败
#存在才更新,可以使用setxx命令
127.0.0.1:6379> set sex 0 xx
OK
127.0.0.1:6379> set address zhengzhou xx
(nil)
# 添加数据,同时设置过期时间
127.0.0.1:6379> set score 100.0 ex 10
OK
- 获取值
get key
127.0.0.1:6379> get name
"zhangsan"
- 删除:
del key [key ...]
# 可以一次性删除多个key
127.0.0.1:6379> del name age
(integer) 2
127.0.0.1:6379> get name
(nil)
批量操作
- 批量设置值:
mset key value [key value ...]
127.0.0.1:6379> mset name xiaobai age 20 sex 0
OK
- 批量获取值:
mget key [key ...]
127.0.0.1:6379> mget name age sex
1) "xiaobai"
2) "20"
3) "0"
- 批量添加值:
msetnx key value [key value ...]
# 注意,有一个存在则整体失败
127.0.0.1:6379> msetnx name xiaohei salary 10000.0
(integer) 0
说明:批量操作命令可以有效提高开发效率,假如没有mget这样的命令,client和redis交互就需要产生n次的网络开销。而使用mget只会产生一次网络开销。
Redis可以支撑每秒数万的读写操作,但是这指的是Redis服务端的处理能力,对于客户端来说,一次命令指令耗时除了执行时间还是有网络时间,假设网络时间为1毫秒,命令时间为0.1毫秒(按照每秒处理1万条命令算),那么执行1000次get命令和1次mget命令的区别。因为Redis的处理能力已经足够高,对于开发人员来说,网络可能会成为性能的瓶颈。
操作 | 时间 |
---|---|
1000次get | 1000 × 1 + 1000 × 0.1 = 1100 ms = 1.1s |
1次mget(组装1000个键) | 1 × 1 + 1000 × 0.1 = 101ms = 0.101s |
学会使用批量操作,有助于提高业务处理效率,但是要注意的是每次批量操作所发送的命令数不是无节制的,如果数量过多可能造成Redis阻塞或者网络拥塞。
计数操作
- 自增操作:
incr key
- 值不是整数,返回错误。
- 值是整数,返回自增后的结果。
- 键不存在,按照值为0自增,返回结果为1。
127.0.0.1:6379> set abc 20
OK
127.0.0.1:6379> set name a
OK
# 不存在返回1
127.0.0.1:6379> incr def
(integer) 1
# 存在返回自增后的值
127.0.0.1:6379> incr abc
(integer) 21
127.0.0.1:6379> incrby abc 2
(integer) 23
127.0.0.1:6379> incrby abc -10
(integer) 13
# 值不是整数,返回错误
127.0.0.1:6379> incr name
(error) ERR value is not an integer or out of range
总结
List(列表)
列表(list)类型是用来存储多个有序的字符串,如下图所示,msg1、msg2、msg3、msg4、msg5五个元素从左到右组成了一个有序的列表,列表中的每个字符串称为元素(element),一个列表最多可以存储2^{32} -1个元素。
在Redis中,可以对列表两端插入(push)和弹出(pop),还可以获取指定范围的元素列表、获取指定索引下标的元素等。
基础操作
- 插入操作
rpush key value [value ...]
从右边插入元素lpush key value [value ...]
左边插入元素linsert key before|after pivot value
向某个元素前后插入元素
# 从右边插入元素
127.0.0.1:6379> rpush mylist erwa siwa liuwa shejing
(integer) 4
# 查看所有元素
127.0.0.1:6379> lrange mylist 0 -1
1) "erwa"
2) "siwa"
3) "liuwa"
4) "shejing"
127.0.0.1:6379> rpush mylist wugongjing
(integer) 5
127.0.0.1:6379> lrange mylist 0 -1
1) "erwa"
2) "siwa"
3) "liuwa"
4) "shejing"
5) "wugongjing"
# 从左边插入元素
127.0.0.1:6379> lpush mylist dawa
(integer) 6
127.0.0.1:6379> lrange mylist 0 -1
1) "dawa"
2) "erwa"
3) "siwa"
4) "liuwa"
5) "shejing"
6) "wugongjing"
# 向siwa 前添加元素sanwa
127.0.0.1:6379> linsert mylist before siwa sanwa
(integer) 7
127.0.0.1:6379> lrange mylist 0 -1
1) "dawa"
2) "erwa"
3) "sanwa"
4) "siwa"
5) "liuwa"
6) "shejing"
7) "wugongjing"
# 向liuwa 后添加元素qiwa
127.0.0.1:6379> linsert mylist after liuwa qiwa
(integer) 8
127.0.0.1:6379> lrange mylist 0 -1
1) "dawa"
2) "erwa"
3) "sanwa"
4) "siwa"
5) "liuwa"
6) "qiwa"
7) "shejing"
8) "wugongjing"
- 删除操作
lpop key
从列表左侧弹出元素rpop key
从列表右侧弹出元素lrem key count value
删除最多count个等于value的元素。count > 0 从左向右删除;count < 0 从右向左删除; count = 0 删除所有;
# 从左侧弹出
127.0.0.1:6379> lpop mylist
"dawa"
127.0.0.1:6379> lrange mylist 0 -1
1) "erwa"
2) "sanwa"
3) "siwa"
4) "liuwa"
5) "qiwa"
6) "shejing"
7) "wugongjing"
# 从右侧弹出
127.0.0.1:6379> rpop mylist
"wugongjing"
127.0.0.1:6379> lrange mylist 0 -1
1) "erwa"
2) "sanwa"
3) "siwa"
4) "liuwa"
5) "qiwa"
6) "shejing"
127.0.0.1:6379> rpush mylist 0 a 0 b 0 c 0 d 0 e 0 f
(integer) 18
127.0.0.1:6379> lrange mylist 0 -1
1) "erwa"
2) "sanwa"
3) "siwa"
4) "liuwa"
5) "qiwa"
6) "shejing"
7) "0"
8) "a"
9) "0"
10) "b"
11) "0"
12) "c"
13) "0"
14) "d"
15) "0"
16) "e"
17) "0"
18) "f"
# 从左到右删除3个 0
127.0.0.1:6379> lrem mylist 3 0
(integer) 3
127.0.0.1:6379> lrange mylist 0 -1
1) "erwa"
2) "sanwa"
3) "siwa"
4) "liuwa"
5) "qiwa"
6) "shejing"
7) "a"
8) "b"
9) "c"
10) "0"
11) "d"
12) "0"
13) "e"
14) "0"
15) "f"
# 从右到左删除3个 0
127.0.0.1:6379> lrem mylist -3 0
(integer) 3
127.0.0.1:6379> lrange mylist 0 -1
1) "erwa"
2) "sanwa"
3) "siwa"
4) "liuwa"
5) "qiwa"
6) "shejing"
7) "a"
8) "b"
9) "c"
10) "d"
11) "e"
12) "f"
下标操作
- 根据下标检索元素:
lindex key index
127.0.0.1:6379> lrange mylist 0 -1
1) "erwa"
2) "sanwa"
3) "siwa"
4) "liuwa"
5) "qiwa"
6) "shejing"
7) "a"
8) "b"
9) "c"
10) "d"
11) "e"
12) "f"
127.0.0.1:6379> lindex mylist 4
"qiwa"
- 获取列表长度:
llen key
127.0.0.1:6379> llen mylist
(integer) 12
- 截取列表子集:
ltrim key start end
127.0.0.1:6379> lrange mylist 0 -1
1) "erwa"
2) "sanwa"
3) "siwa"
4) "liuwa"
5) "qiwa"
6) "shejing"
7) "a"
8) "b"
9) "c"
10) "d"
11) "e"
12) "f"
127.0.0.1:6379> ltrim mylist 6 11
OK
127.0.0.1:6379> lrange mylist 0 -1
1) "a"
2) "b"
3) "c"
4) "d"
5) "e"
6) "f"
- 修改指定索引元素:
lset key index newValue
127.0.0.1:6379> lrange mylist 0 -1
1) "a"
2) "b"
3) "c"
4) "d"
5) "e"
6) "f"
127.0.0.1:6379> lset mylist 5 lisi
OK
127.0.0.1:6379> lrange mylist 0 -1
1) "a"
2) "b"
3) "c"
4) "d"
5) "e"
6) "lisi"
阻塞操作
前面介绍的 LPOP 和 RPOP 命令,在 List 为空的时候会返回 nil,但是有的场景中,需要在 List 没有数据的时候,阻塞等待新元素。
brpop key [key ...] timeout
阻塞式的从列表右侧弹出数据,直到返回数据或者等待超时(timeout单位s,为0时一直阻塞)blpop key [key ...] timeout
阻塞式的从列表右侧弹出数据,直到返回数据或者等待超时(timeout单位s,为0时一直阻塞)
127.0.0.1:6379> blpop mylist 1
1) "mylist"
2) "a"
127.0.0.1:6379> blpop mylist 1
1) "mylist"
2) "b"
127.0.0.1:6379> blpop mylist 1
1) "mylist"
2) "c"
127.0.0.1:6379> blpop mylist 1
1) "mylist"
2) "d"
127.0.0.1:6379> blpop mylist 1
1) "mylist"
2) "e"
127.0.0.1:6379> blpop mylist 1
1) "mylist"
2) "lisi"
127.0.0.1:6379> blpop mylist 2
(nil)
(2.06s)
# 现在这个mylist队列为空,此时再打开一个客户端,向mylist队列添加元素
127.0.0.1:6379> rpush mylist 12 16
(integer) 2
127.0.0.1:6379> brpop mylist 15
1) "mylist"
2) "16"
(9.28s)
说明:可以利用Redis的lpush+brpop命令组合即可实现阻塞队列,生产者客户端使用lrpush从列表左侧插入元素,多个消费者客户端使用brpop命令阻塞式的“抢”列表尾部的元素,多个客户端保证了消费的负载均衡和高可用性。
总结
Set(集合)
集合(set)类型也是用来保存多个的字符串元素,但和列表类型不一样的是,集合中不允许有重复元素,并且集合中的元素是无序的,不能通过索引下标获取元素。如图下图所示,集合user:1:follow
包含着"it"、“music”、“his”、"sports"四个元素,一个集合最多可以存储 2^{32}-1个元素。
Redis除了支持集合内的增删改查,同时还支持多个集合取交集、并集、差集,合理地使用好集合类型,能在实际开发中解决很多实际问题。
集合内操作
- 添加元素:
sadd key member[member ...]
# 元素不可以重复
127.0.0.1:6379> sadd myset a b c d a b
(integer) 4
- 获取所有元素:
smembers key
127.0.0.1:6379> smembers myset
1) "b"
2) "a"
3) "d"
4) "c"
- 删除元素:
srem key member [member ...]
127.0.0.1:6379> srem myset d
(integer) 1
127.0.0.1:6379> smembers myset
1) "b"
2) "a"
3) "c"
- 计算元素个数:
scard key
127.0.0.1:6379> smembers myset
1) "b"
2) "a"
3) "c"
127.0.0.1:6379> scard myset
(integer) 3
- 判断元素是否在集合中:
sismember key member
#存在返回 1 , 不存在返回 0
127.0.0.1:6379> sismember myset a
(integer) 1
127.0.0.1:6379> sismember myset d
(integer) 0
- 随机从集合中返回指定个元素:
srandmember key count
127.0.0.1:6379> smembers myset
1) "d"
2) "c"
3) "f"
4) "e"
5) "a"
6) "b"
127.0.0.1:6379> srandmember myset 2
1) "b"
2) "d"
127.0.0.1:6379> srandmember myset 3
1) "c"
2) "f"
3) "e"
127.0.0.1:6379> srandmember myset 3
1) "c"
2) "f"
3) "b"
127.0.0.1:6379> smembers myset
1) "c"
2) "f"
3) "e"
4) "a"
5) "b"
6) "d"
- 从集合中随机弹出元素:
spop key [count]
127.0.0.1:6379> smembers myset
1) "c"
2) "f"
3) "e"
4) "a"
5) "b"
6) "d"
127.0.0.1:6379> spop myset 2
1) "b"
2) "a"
127.0.0.1:6379> smembers myset
1) "c"
2) "f"
3) "e"
4) "d"
127.0.0.1:6379> spop myset 1
1) "d"
127.0.0.1:6379> smembers myset
1) "c"
2) "f"
3) "e"
集合间操作
Set 结构除了能支持单个集合内元素的增、删、改、查
之外,还提供了一些数学上的集合操作
,例如,求两个 Set 的并集、差集或者交集等。
- 交集
sinter key [key ...]
: 求多个集合的交集(共性部分)sinterstore destination key [key ...]
: 求多个集合的交集,并保存到一个新的集合中
127.0.0.1:6379> sadd myset1 a b c
(integer) 3
127.0.0.1:6379> sadd myset2 a b d
(integer) 3
127.0.0.1:6379> sadd myset3 a e f
(integer) 3
127.0.0.1:6379> sinter myset1 myset2
1) "b"
2) "a"
127.0.0.1:6379> sinterstore myset4 myset1 myset2
(integer) 2
127.0.0.1:6379> smembers myset4
1) "a"
2) "b"
- 并集
sunion key [key ...]
: 求多个集合的交集(合并起来)sunionstore destination key [key ...]
: 求多个集合的交集(合并起来),并保存到一个新的集合中
127.0.0.1:6379> sunion myset1 myset3
1) "a"
2) "b"
3) "f"
4) "c"
5) "e"
127.0.0.1:6379> sunionstore myset5 myset1 myset3
(integer) 5
127.0.0.1:6379> smembers myset5
1) "a"
2) "b"
3) "f"
4) "c"
5) "e"
- 差集
sdiff key1 [key2 ...]
: 求多个集合的差集(key1中有key2中没有的部分)sdiffstore destination key1 [key2 ...]
: 求多个集合的差集(key1中有key2中没有的部分),并保存到一个新的集合中
127.0.0.1:6379> sdiff myset1 myset3
1) "b"
2) "c"
127.0.0.1:6379> sdiff myset1 myset2
1) "c"
127.0.0.1:6379> sdiffstore myset6 myset1 myset2
(integer) 1
127.0.0.1:6379> smembers myset6
1) "c"
总结
Hash(键值对集合)
几乎所有的编程语言都提供了哈希(hash)类型,它们的叫法可能是Map、字典、关联数组。在Redis中,哈希类型是指键值本身又是一个键值对结构,形如value={{field1,value1},…{fieldN,valueN}},Redis键值对和哈希类型二者的关系可以用下图表示。
读写操作
- 设置值:
hset key field value
127.0.0.1:6379> hset myhash name lisi
(integer) 1
127.0.0.1:6379> hset myhash age 18
(integer) 1
- 获取值:
hget key field
127.0.0.1:6379> hget myhash name
"lisi"
127.0.0.1:6379> hget myhash age
"18"
- 添加值:
hsetnx key field value
# sex不存在,添加成功返回1
127.0.0.1:6379> hsetnx myhash sex 1
(integer) 1
# sex已经存在,添加失败返回0
127.0.0.1:6379> hsetnx myhash sex 2
(integer) 0
- 删除:
hdel key field [field ...]
127.0.0.1:6379> hget myhash sex
"1"
127.0.0.1:6379> hdel myhash sex
(integer) 1
127.0.0.1:6379> hget myhash sex
(nil)
- 获取field个数:
hlen key
127.0.0.1:6379> hlen myhash
(integer) 2
- 判断field是否存在:
hexists key field
127.0.0.1:6379> hget myhash name
"lisi"
127.0.0.1:6379> hexists myhash name
(integer) 1
127.0.0.1:6379> hget myhash sex
(nil)
127.0.0.1:6379> hexists myhash sex
(integer) 0
批量操作
- 批量设置值:
hmset key field value [field value ...]
127.0.0.1:6379> hmset myhash name zhangsan age 38 sex M
OK
- 批量获取值:
hmget key field [field ...]
127.0.0.1:6379> hmget myhash name sex age
1) "zhangsan"
2) "M"
3) "38"
- 获取所有field:
hkeys key
127.0.0.1:6379> hkeys myhash
1) "name"
2) "age"
3) "sex"
- 获取所有子value:
hvals key
127.0.0.1:6379> hvals myhash
1) "zhangsan"
2) "38"
3) "M"
- 获取所有field-value:
hgetall key
127.0.0.1:6379> hgetall myhash
1) "name"
2) "zhangsan"
3) "age"
4) "38"
5) "sex"
6) "M"
计数操作
- 自增指定的数:
hincrby key field increment
127.0.0.1:6379> hincrby myhash age 2
(integer) 40
127.0.0.1:6379> hincrby myhash age -5
(integer) 35
- 自增指定的浮点数:
hincrbyfloat key field increment
127.0.0.1:6379> hincrbyfloat myhash age -0.5
"34.5"
127.0.0.1:6379> hincrbyfloat myhash age +2.5
"37"
总结
SortedSet(ZSet排序集合)
有序集合保留了集合不能有重复成员的特性,但不同的是,有序集合中的元素可以排序。但是它和列表使用索引下标作为排序依据不同的是,它给每个元素设置一个分数(score)作为排序的依据。
基础操作
- 添加成员:
zadd key [NX|XX] [CH] [INCR] score member [score member ...]
- NX: member必须不存在,才可以设置成功,用于添加。
- XX: member必须存在,才可以设置成功,用于更新。
- CH: 返回此次操作后,有序集合元素和分数发生变化的个数
- INCR: 对score做增加,相当于后面介绍的zincrby。
# mike 获得了3个赞
127.0.0.1:6379> zadd myzset NX 3 mike
(integer) 1
# mike 新增1个赞
127.0.0.1:6379> zadd myzset XX INCR 1 mike
"4"
# 查看所有成员,并查看分数
127.0.0.1:6379> zrange myzset 0 -1 withscores
1) "dawa"
2) "1"
3) "erwa"
4) "3"
5) "wuwa"
6) "5"
7) "qiwa"
8) "21"
127.0.0.1:6379> zadd myzset incr 10 qiwa
"31"
127.0.0.1:6379> zrange myzset 0 -1 withscores
1) "dawa"
2) "1"
3) "erwa"
4) "3"
5) "wuwa"
6) "5"
7) "qiwa"
8) "31"
# 如果更新已有的成员的分数,使用CH返回更新的元素数目
127.0.0.1:6379> zadd myzset 7 mike
(integer) 0
127.0.0.1:6379> zadd myzset CH 8 mike
(integer) 1
- 删除成员:
zrem key member [member ...]
# 查看所有成员
127.0.0.1:6379> zrange myzset 0 -1
1) "dawa"
2) "erwa"
3) "wuwa"
4) "qiwa"
127.0.0.1:6379> zrem myzset qiwa
(integer) 1
127.0.0.1:6379> zrange myzset 0 -1
1) "dawa"
2) "erwa"
3) "wuwa"
- 修改分数:
zincrby key score member
127.0.0.1:6379> zrange myzset 0 -1 withscores
1) "dawa"
2) "1"
3) "erwa"
4) "3"
5) "wuwa"
6) "5"
7) "qiwa"
8) "31"
127.0.0.1:6379> zincrby myzset 5 dawa
"6"
127.0.0.1:6379> zincrby myzset -6 dawa
"0"
- 获取成员个数:
zcard key
127.0.0.1:6379> zcard myzset
(integer) 4
127.0.0.1:6379> zrange myzset 0 -1
1) "dawa"
2) "erwa"
3) "wuwa"
4) "qiwa"
- 获取某个成员的分数:
zscore key member
127.0.0.1:6379> zscore myzset qiwa
"31"
127.0.0.1:6379> zscore myzset erwa
"3"
- 计算成员的排名:
zrank key member
127.0.0.1:6379> zrank myzset wuwa
(integer) 2
127.0.0.1:6379> zrank myzset dawa
(integer) 0
127.0.0.1:6379> zrange myzset 0 -1
1) "dawa"
2) "erwa"
3) "wuwa"
4) "qiwa"
范围操作
- 返回指定下标范围的成员:
zrange key start stop [withscores]
# 查看所有成员
127.0.0.1:6379> zrange myzset 0 -1
1) "dawa"
2) "erwa"
3) "wuwa"
4) "qiwa"
# 查看所有成员,同时查看分数
127.0.0.1:6379> zrange myzset 0 -1 withscores
1) "dawa"
2) "0"
3) "erwa"
4) "3"
5) "wuwa"
6) "5"
7) "qiwa"
8) "31"
- 删除指定下标范围的成员:
zremrangebyrank key start stop
127.0.0.1:6379> zrange myzset 0 -1
1) "dawa"
2) "shihao"
3) "xiaoyan"
4) "erwa"
5) "sanwa"
6) "wuwa"
7) "liuwa"
8) "qiwa"
127.0.0.1:6379> zremrangebyrank myzset 1 3
(integer) 3
127.0.0.1:6379> zrange myzset 0 -1
1) "dawa"
2) "sanwa"
3) "wuwa"
4) "liuwa"
5) "qiwa"
# 删除全部
127.0.0.1:6379> zremrangebyrank myzset 0 -1
(integer) 5
127.0.0.1:6379> zrange myzset 0 -1
(empty list or set)
- 返回指定分数范围的成员:
zrangebyscore key min max [withscores] [limit offset count]
127.0.0.1:6379> zrangebyscore myzset 2 8
1) "erwa"
2) "siwa"
3) "wuwa"
127.0.0.1:6379> zrangebyscore myzset (2 8
1) "siwa"
2) "wuwa"
127.0.0.1:6379> zrangebyscore myzset (2 (10
1) "siwa"
2) "wuwa"
127.0.0.1:6379> zrangebyscore myzset 2 10
1) "erwa"
2) "siwa"
3) "wuwa"
4) "qiwa"
127.0.0.1:6379> zrange myzset 0 -1 withscores
1) "dawa"
2) "1"
3) "erwa"
4) "2"
5) "siwa"
6) "3"
7) "wuwa"
8) "4"
9) "qiwa"
10) "10"
11) "ajie"
12) "20"
- 返回指定分数范围的成员个数:
zcount key min max
127.0.0.1:6379> zcount myzset (2 (10
(integer) 2
127.0.0.1:6379> zcount myzset 2 10
(integer) 4
- 删除指定分数范围的成员:
zremrangebyscore key min max
127.0.0.1:6379> zrange myzset 0 -1 withscores
1) "dawa"
2) "1"
3) "erwa"
4) "2"
5) "siwa"
6) "3"
7) "wuwa"
8) "4"
9) "qiwa"
10) "10"
11) "ajie"
12) "20"
127.0.0.1:6379> zremrangebyscore myzset 4 10
(integer) 2
127.0.0.1:6379> zrange myzset 0 -1 withscores
1) "dawa"
2) "1"
3) "erwa"
4) "2"
5) "siwa"
6) "3"
7) "ajie"
8) "20"