数据结构
redis有五种基础数据结构,分别是:
string(字符串)
、list(列表)
、hash(字典)
、set(集合)
和zset(有序集合)
string(字符串)
redis所有数据结构都是以唯一的key字符串作为名称,通过这个key我们可以获取相应的value。
不同类型的数据结构的差异就在于value的结构不一样。
常见用途
可以通过字符串结构来缓存一些用户信息,减少mysql的一些频繁查询压力。我们可以将用户信息结构体使用json序列化成字符串,然后将序列化后的字符串放进redis来缓存,不过取出来的时候记得也得经过一次反序列化过程。
字符串扩容策略
redis字符串是动态字符串,是可以进行修改的字符串。采用预分配冗余空间的方式来减少内存的频繁分配。所以实际分配的空间要高于实际字符串长度的。
当字符串长度小于1MB时,扩容都是加倍现有的空间(原有的空间上翻倍)。如果字符串长度超过1MB,扩容时一次只会多扩容1MB的空间(意思是如果还采用翻倍策略将会很大意义上浪费空间),
字符串最大长度为512MB
string使用
键值对
1
# 返回OK表示设置成功,如果继续set则会动态修改value的值
2
127.0.0.1:6379> set name pocket
3
OK
4
127.0.0.1:6379> get name
5
"pocket"
6
# 检测是否存在key,存在则会返回1
7
127.0.0.1:6379> EXISTS name
8
(integer) 1
9
# 如果被删除的key返回1,表明成功,否则表示试图删除不存在的key,会返回0
10
127.0.0.1:6379> del name
11
(integer) 1
12
# 获取一个不存在的key的值,会返回nil
13
127.0.0.1:6379> get name
14
(nil)
15
127.0.0.1:6379> del name1
16
(integer) 0
批量键值对
有时候我们可能需要一次性设置多个值进行读写操作,如果每次都一个一个设置的话,将会增加网络耗时的开销。
1
# 一次性设置多个键值
2
127.0.0.1:6379> mset name1 pocket name2 pang name3 test
3
OK
4
# 一次性取多个,对于没有的key,返回值是nil
5
127.0.0.1:6379> mget name1 name2 name4
6
1) "pocket"
7
2) "pang"
8
3) (nil)
过期设置和set扩展
我们可以在设置key的时候为它设置过期时间,到期会被自动删除。这样就能用来控制缓存失效的时间。
比如登录的token过期时间1
127.0.0.1:6379> get name1
2
"pocket"
3
# 给name1键值设置3秒后过期
4
127.0.0.1:6379> EXPIRE name1 3
5
(integer) 1
6
# 3秒后尝试获取发现已经为nil了
7
127.0.0.1:6379> get name1
8
(nil)
9
10
# 也可以在设置的同时赋予过期时间(eg:8秒后过期)
11
127.0.0.1:6379> setex age 8 25
12
OK
13
127.0.0.1:6379> get age
14
"25"
15
# 等候8秒
16
127.0.0.1:6379> get age
17
(nil)
18
19
# 如果name1不存在就执行set创建,成功会返回1,存在的话set会失败返回0
20
127.0.0.1:6379> setnx name1 pang
21
(integer) 1
22
127.0.0.1:6379> setnx name2 pang
23
(integer) 0
24
127.0.0.1:6379> get name2
25
"test"
计数
当value是一个数值的时候,我们可以对他进行自增操作。不过这个自增是有范围的,在signed long的最大值和最小值之间(2^63-1),超出这个范围继续自增操作就会报错了
1
# 当没有key的时候,直接自增则会直接赋予1的值
2
127.0.0.1:6379> get name
3
(nil)
4
127.0.0.1:6379> incr name
5
(integer) 1
6
127.0.0.1:6379> get name
7
"1"
8
9
# 也可以直接指定需要自增的大小(用incrby指定)
10
127.0.0.1:6379> get age
11
"27"
12
127.0.0.1:6379> incrby age 5
13
(integer) 32
14
15
# 非数值的不能进行自增
16
127.0.0.1:6379> get name2
17
"test"
18
127.0.0.1:6379> incr name2
19
(error) ERR value is not an integer or out of range
20
21
# 超出最大值在自增,报错
22
127.0.0.1:6379> set max_value 9223372036854775807
23
OK
24
127.0.0.1:6379> incr max_value
25
(error) ERR increment or decrement would overflow
list(列表)
它是链表而不是数组,也就意味着list的插入和删除操作都非常的快,时间复杂度为O(1),不过他索引定位很慢,时间复杂度为O(n),列表中的每个元素都使用双向指针顺序,串起来可以同时支持向前向后遍历。
Redis的列表结构常用于做异步队列使用。将需要延后处理的任务结构体序列化成字符串,塞进Redis列表,另一个线程从这个列表中轮询数据进行处理。
Redis底层存储的不是一个简单的链表,而是称之为快速链表(quicklist)的一个结构,在列表元素较少的情况下,会使用一块连续的内存存储,这个结构是ziplist(压缩列表)。它将所有的元素彼此紧挨着一起存储,分配的是一块连续的内存。当数据量比较多的时候才会改成quicklist队列(右边进左边出)
队列是先进先出的数据结构,常用于消息排队和异步逻辑处理,因为它可以保证元素的访问顺序性。
1
127.0.0.1:6379> rpush r_list go python mysql mongodb
2
(integer) 4
3
127.0.0.1:6379> llen r_list
4
(integer) 4
5
127.0.0.1:6379> lpop r_list
6
"go"
7
127.0.0.1:6379> llen r_list
8
(integer) 3
9
127.0.0.1:6379> lpop r_list
10
"python"
11
127.0.0.1:6379> lpop r_list
12
"mysql"
13
127.0.0.1:6379> lpop r_list
14
"mongodb"
15
127.0.0.1:6379> lpop r_list
16
(nil)
栈(右边进右边出)
栈是先进后出的数据结构,与队列是相反的。
1
127.0.0.1:6379> rpush r_list go python mysql mongodb
2
(integer) 4
3
127.0.0.1:6379> rpop r_list
4
"mongodb"
5
127.0.0.1:6379> rpop r_list
6
"mysql"
7
127.0.0.1:6379> rpop r_list
8
"python"
9
127.0.0.1:6379> rpop r_list
10
"go"
慢操作
在Redis中,操作list的时候需要注意,因为链表会随着list数量增大而影响性能
1
127.0.0.1:6379> rpush r_list go python mysql mongodb
2
(integer) 4
3
# 取出索引为2的值,O(n) 慎用
4
127.0.0.1:6379> lindex r_list 2
5
"mysql"
6
# 不要试图用get获取list类型的数据,get是用来获取string类型的key
7
127.0.0.1:6379> get r_list
8
(error) WRONGTYPE Operation against a key holding the wrong kind of value
9
10
# 获取所有list的值(指定区间,-1为倒数第一个),O(n) 慎用
11
127.0.0.1:6379> lrange r_list 0 -1
12
1) "go"
13
2) "python"
14
3) "mysql"
15
4) "mongodb"
16
17
# 截取list(指定区间),会改变原有list,O(n) 慎用
18
127.0.0.1:6379> ltrim r_list 1 -1
19
OK
20
127.0.0.1:6379> lrange r_list 0 -1
21
1) "python"
22
2) "mysql"
23
3) "mongodb"
24
25
# 相当于清空list,因为区间值此时为负
26
127.0.0.1:6379> ltrim r_list 1 0
27
OK
28
127.0.0.1:6379> lrange r_list 0 -1
29
(empty list or set)
hash(字典)
是一种无序的字典,内部存储了很多键值对。实现方式也是
数组+链表
的二维结构。
Redis的字典的值只能是字符串。普通的rehash是个耗时的操作(java的HashMap),Redis为了追求高性能,不能堵塞服务,所以采用了渐进式rehash策略。渐进式策略
会在rehash(重新散列)的同时,保留新旧两个hash结构,查询时会同时查询两个hash结构,然后在后续的定时任务以及hash操作指令中,循序渐进的将旧hash的内容一点点的迁移到新的hash结构中。当搬迁完成了,就会使用新hash结构取而代之。
当hash移除最后一个元素之后,数据结构被自动删除,内存被回收。
hash使用
hash可以用来存储一些结构化的信息(比如:用户信息)。hash内部的值(数值字符串)也是可进行自增的。
1 | # 设置hash键值 |
2 | 127.0.0.1:6379> hset user age 24 |
3 | (integer) 1 |
4 | 127.0.0.1:6379> hset user name pocket |
5 | (integer) 1 |
6 | |
7 | # 取出hash全部键值对 |
8 | 127.0.0.1:6379> hgetall user |
9 | 1) "age" |
10 | 2) "24" |
11 | 3) "name" |
12 | 4) "pocket" |
13 | |
14 | # 取出指定key的值 |
15 | 127.0.0.1:6379> hget user name |
16 | "pocket" |
17 | 127.0.0.1:6379> hget user age |
18 | "24" |
19 | |
20 | # 获取hash有几个键值 |
21 | 127.0.0.1:6379> hlen user |
22 | (integer) 2 |
23 | |
24 | # 批量获取指定key的值 |
25 | 127.0.0.1:6379> hmget user name age |
26 | 1) "pocket" |
27 | 2) "24" |
28 | |
29 | # 批量设置(如果已有的key则会更新值) |
30 | 127.0.0.1:6379> hmset user age 25 weight 70 |
31 | OK |
32 | 127.0.0.1:6379> hgetall user |
33 | 1) "age" |
34 | 2) "25" |
35 | 3) "name" |
36 | 4) "pocket" |
37 | 5) "weight" |
38 | 6) "70" |
39 | |
40 | # 自增 |
41 | 127.0.0.1:6379> hincrby user age 1 |
42 | (integer) 26 |
43 | 127.0.0.1:6379> hget user age |
44 | "26" |
set(集合)
Redis的集合相当于Java语言的HashSet,它内部的键值对是无序的、唯一的。
当集合中最后一个元素被移除之后,数据结构被自动删除,内存被回收。
因为set具备唯一性,也就是天生具备了去重的能力,在实际项目中,如果有些功能需要去重,可以用它去实现。
1 | 127.0.0.1:6379> sadd class python |
2 | (integer) 1 |
3 | # 添加重复的返回0 |
4 | 127.0.0.1:6379> sadd class python |
5 | (integer) 0 |
6 | # 可以一次添加多个 |
7 | 127.0.0.1:6379> sadd class object golang |
8 | (integer) 2 |
9 | |
10 | # 取出的时候是无序的 |
11 | 127.0.0.1:6379> SMEMBERS class |
12 | 1) "golang" |
13 | 2) "object" |
14 | 3) "python" |
15 | |
16 | # 查询key是否存在 |
17 | 127.0.0.1:6379> EXISTS class |
18 | (integer) 1 |
19 | # 查询set集合的某个value是否存在,存在返回1,不存在返回0 |
20 | 127.0.0.1:6379> sismember class python |
21 | (integer) 1 |
22 | 127.0.0.1:6379> sismember class test |
23 | (integer) 0 |
24 | |
25 | # 获取集合的长度(count),也就是个数 |
26 | 127.0.0.1:6379> scard class |
27 | (integer) 3 |
28 | |
29 | # 弹出一个值 |
30 | 127.0.0.1:6379> spop class |
31 | "golang" |
32 | # 弹出两个 |
33 | 127.0.0.1:6379> spop class 2 |
34 | 1) "python" |
35 | 2) "object" |
36 | # 当集合没有值了,会返回nil |
37 | 127.0.0.1:6379> spop class |
38 | (nil) |
zset(有序集合)
它具备了set特点的同时,给每一个value都赋予了score,代表着这个value的排序权重。内部实现用的是一种
跳跃列表
的数据结构
1 | # 添加value,成功返回1 |
2 | 127.0.0.1:6379> zadd student 100 zhangsan |
3 | (integer) 1 |
4 | 127.0.0.1:6379> zadd student 98 lisi |
5 | (integer) 1 |
6 | 127.0.0.1:6379> zadd student 56 pang |
7 | (integer) 1 |
8 | |
9 | # 按score排序列出,默认从小到大,参数区间为排名范围 |
10 | 127.0.0.1:6379> zrange student 0 -1 |
11 | 1) "pang" |
12 | 2) "lisi" |
13 | 3) "zhangsan" |
14 | |
15 | # 按score逆序列出,参数区间为排名范围 |
16 | 127.0.0.1:6379> zrevrange student 0 -1 |
17 | 1) "zhangsan" |
18 | 2) "lisi" |
19 | 3) "pang" |
20 | |
21 | # 查询集合的个数 |
22 | 127.0.0.1:6379> zcard student |
23 | (integer) 3 |
24 | |
25 | # 显示所有的值,并显示每个的score |
26 | 127.0.0.1:6379> zrange student 0 -1 WITHSCORES |
27 | 1) "pang" |
28 | 2) "56" |
29 | 3) "lisi" |
30 | 4) "98" |
31 | 5) "zhangsan" |
32 | 6) "100" |
33 | # 显示指定值的排名 |
34 | 127.0.0.1:6379> zrank student zhangsan |
35 | (integer) 2 |
36 | 127.0.0.1:6379> zrank student lisi |
37 | (integer) 1 |
38 | |
39 | # 根据分值区间显示,同时返回分值(-inf代表负无穷大,+inf代表正无穷大) |
40 | 127.0.0.1:6379> zrangebyscore student -inf 80 withscores |
41 | 1) "pang" |
42 | 2) "56" |
43 | |
44 | # 删除value |
45 | 127.0.0.1:6379> zrem student pang |
46 | (integer) 1 |
47 | 127.0.0.1:6379> zrange student 0 -1 |
48 | 1) "lisi" |
49 | 2) "zhangsan" |
总结
list、set、hash、zset这四种数据结构都属于容器型数据结构,它们遵循以下通用规则。
- 如果容器不存在,就创建一个,在进行操作。
- 如果容器的元素没了,那么立即删除容器,释放内存。
- 过期时间
Redis所有的数据结构都可以设置过期时间,时间到了,Redis会自动删除相应的对象(只是啥时候删除,取决于Redis策略),需要注意的是,过期是以对象为单位的,比如一个hash结构的过期是整个hash对象的过期,而不是其中某个子key的过期。