redis五种常用的数据结构为string (字符串)、list (列表)、set (集合)、hash (哈希) 和 zset (有序集合)。小白易读,建议收藏。
万丈高楼平地起
reids是键值对结构的NoSql数据库,key
都是字符串,常说的数据类型不同,说的都是value
。
redis所有的数据都会有一个dicEntry
,众多dicEntry
组成一个链表。上方那个sds
就是key
,可以看出是一个字符串。下方那个绿色的redisObject
就是value
。可以看出图中给的例子就是string
类型。redisObject
会指向真实的数据(比如图中的字符串“world”)。后面我们说的数据类型特指value
部分。
string (字符串)
Redis 的字符串是动态字符串,是可以修改的字符串。当字符串长度小于 1M 时,扩容都是加倍现有的空间,如果超过 1M,扩容时一次只会多扩 1M 的空间。一个字符串最大可以承受512M。
常用指令
设置获取值
127.0.0.1:6379> set name pjjlt
OK
127.0.0.1:6379> get name
"pjjlt"
127.0.0.1:6379> exists name
(integer) 1
设置使用set
,获取使用get
,查看某key是否存在用exists
。
设置过期时间
127.0.0.1:6379> setex company 10 gongsi
OK
127.0.0.1:6379> get company
"gongsi"
127.0.0.1:6379> get company
(nil)
可以在设置值的时候直接指定,keycompany
可以存活10秒。此外,也可以将设置值和设置过期时间分开,使用expire
。
127.0.0.1:6379> set company gongsi
OK
127.0.0.1:6379> expire company 10
(integer) 1
127.0.0.1:6379> get company
"gongsi"
127.0.0.1:6379> get company
(nil)
保证不覆盖value
redis还提供了命令,在设置值的时候,如果发现key
已存在,此次设置失败,保证原始value
不被覆盖。使用setnx
命令。
127.0.0.1:6379> setnx company gongsi
(integer) 1
# 可以看到第二次设置失败,返回值为 0.
127.0.0.1:6379> setnx company haha
(integer) 0
127.0.0.1:6379> get company
"gongsi"
批量设置获取值
127.0.0.1:6379> mset name pjjlt age 26 company gongsi
OK
127.0.0.1:6379> mget name age company
1) "pjjlt"
2) "26"
3) "gongsi"
批量设置使用mset
,批量获取使用mget
。批量设置获取,减少IO,提高性能,你值得拥有。
计数
redis还可以通过自增的方式计数。
127.0.0.1:6379> set key 10
OK
127.0.0.1:6379> incr key
(integer) 11
127.0.0.1:6379> incr key
(integer) 12
# 字符串报错
127.0.0.1:6379> set key2 haha
OK
127.0.0.1:6379> incr key2
(error) ERR value is not an integer or out of range
# 超出long的范围
127.0.0.1:6379> set key3 9223372036854775807
OK
127.0.0.1:6379> incr key3
(error) ERR increment or decrement would overflow
# key4不存在
127.0.0.1:6379> incr key4
(integer) 1
127.0.0.1:6379> incr key4
(integer) 2
可以通过incr
关键字自增,可以看出key自增了两次。不能给字符串自增,那样会报错,例如key2。不能超过long
的范围,那样也会报错,例如key3。如果初始key不存在,则增从0开始,例如key4。
追加值
127.0.0.1:6379> set name pj
OK
127.0.0.1:6379> append name jlt
(integer) 5
127.0.0.1:6379> get name
"pjjlt"
字符串长度
127.0.0.1:6379> get name
"pjjlt"
127.0.0.1:6379> strlen name
(integer) 5
设置并返回原先值
127.0.0.1:6379> get name
"pjjlt"
127.0.0.1:6379> getset name mj
"pjjlt"
127.0.0.1:6379> get name
"mj"
设置指定位置的字符
127.0.0.1:6379> get name
"mj"
127.0.0.1:6379> setrange name 0 p
(integer) 2
127.0.0.1:6379> get name
"pj"
获取部分字符串
127.0.0.1:6379> set name pjjlt
OK
127.0.0.1:6379> getrange name 0 2
"pjj"
总结
命令 | 解释 |
---|---|
set | 设置值 |
get | 获取值 |
setex | 设置值并添加过期时间 |
setnx | 保证不覆盖value |
mset | 批量设置值 |
mget | 批量获取值 |
incr | 计数 |
append | 追加值 |
strlen | 字符串长度 |
getset | 设置并返回原先值 |
setrange | 设置指定位置的字符 |
getrange | 获取部分字符串 |
内部编码
虽然某种数据类型的value
名称是一致的,比如都是string
,但是根据数据量的大小,会采用不同的内部编码,这样可以更高效的利用空间嘛。内部编码类型也储存在redisObject
中。利用object encoding key
可查看内部编码类型。
int
:长整型,超越长整型或者是字符串会升级。
embstr
:小于等于44个字节的字符串。笔者用的是redis5.0.9,有人说这个字节范围是39,亲测是44。查了一下,源码确实改了,现在是44.
raw
:大于44个字节的字符串。
127.0.0.1:6379> set name 1234567890
OK
127.0.0.1:6379> object encoding name
"int"
# 这里设置44个字符
127.0.0.1:6379> set name qwertyuiopqwertyuiopqwertyuiopqwertyuiopqwer
OK
127.0.0.1:6379> object encoding name
"embstr"
# 这里设置45个字符
127.0.0.1:6379> set name qwertyuiopqwertyuiopqwertyuiopqwertyuiopqwert
OK
127.0.0.1:6379> object encoding name
"raw"
使用场景
可以用于计数
,比如网站访问量。
可以共享Session
,比如分布式系统,多个实例验证用户是否登录。
可以限速
,比如控制一个ip或者一个用户一定时间内访问次数。
list (列表)
Redis 的列表相当于 Java 语言里面的 LinkedList,注意它是链表而不是数组。这意味着list 的插入和删除操作非常快,时间复杂度为 O(1),但是索引定位很慢,时间复杂度为O(n)。list的两端都可以弹入弹出数据,所以可以做栈
和队列
。
栈与队列
栈
栈如同一个死胡同,只有一个出口,后进来的先出,先进来的后出。
127.0.0.1:6379> rpush books python java golong
(integer) 3
127.0.0.1:6379> rpop books
"golong"
127.0.0.1:6379> rpop books
"java"
127.0.0.1:6379> rpop books
"python"
127.0.0.1:6379> rpop books
(nil)
数据从右边进(rpush),右边出(rpop),先进去的最后出来。
队列
队列如同排队打饭的同学们,先进先出。
127.0.0.1:6379> rpush books python java golong
(integer) 3
127.0.0.1:6379> lpop books
"python"
127.0.0.1:6379> lpop books
"java"
127.0.0.1:6379> lpop books
"golong"
127.0.0.1:6379> lpop books
(nil)
数据从右边进(rpush),左边出(lpop),先进先出。
常用命令
向队列任意位置加入元素
刚才演示的rpush
、lpush
都是从两头加入元素,这两个命令不再演示。还可以使用linsert
在某指定元素前或后插入新的元素。
127.0.0.1:6379> rpush books python java golong
(integer) 3
# 在java前面插入 ruby
127.0.0.1:6379> linsert books before java ruby
(integer) 4
# 在java后面插入 c#
127.0.0.1:6379> linsert books after java c#
(integer) 5
# 查看所有元素
127.0.0.1:6379> lrange books 0 -1
1) "python"
2) "ruby"
3) "java"
4) "c#"
5) "golong"
根据上面在java
前后插入了ruby
和c#
查找元素
127.0.0.1:6379> lrange books 0 -1
1) "python"
2) "ruby"
3) "java"
4) "c#"
5) "golong"
127.0.0.1:6379> lindex books 2
"java"
127.0.0.1:6379> llen books
(integer) 5
指令简单,索性写一块吧。
lrange
可以遍历列表,参数为start
,end
。这里0 -1,是指从第一个到最后一个,即遍历列表。
lindex
查找指定位置的元素,参数是下标值。这个命令是慢查询,需要遍历链表。
llen
可以查看列表元素个数。
删除数据
刚才演示的rpop
、lpop
可以弹出一个元素,不再演示。
可以使用lrem 删除多个同一元素
count > 0:从左到右,删除最多 count 个元素。
count < 0:从右到左,删除最多 count 绝对值 个元素。
count = 0,删除所有。
# 从左删除a元素,删除了3个
127.0.0.1:6379> rpush char a a b b a a c
(integer) 7
127.0.0.1:6379> lrem chae 3 a
(integer) 0
127.0.0.1:6379> lrem char 3 a
(integer) 3
127.0.0.1:6379> lrange char 0 -1
1) "b"
2) "b"
3) "a"
4) "c"
# 从右删除 3 个a元素
127.0.0.1:6379> rpush char1 a a b b a a c
(integer) 7
127.0.0.1:6379> lrem char1 -3 a
(integer) 3
127.0.0.1:6379> lrange char1 0 -1
1) "a"
2) "b"
3) "b"
4) "c"
# 删除所有 a 元素
127.0.0.1:6379> rpush char2 a a b b a a c
(integer) 7
127.0.0.1:6379> lrem char2 0 a
(integer) 4
127.0.0.1:6379> lrange char2 0 -1
1) "b"
2) "b"
3) "c"
还可以使用ltrim
截取一部分数据,删除其余数据
127.0.0.1:6379> rpush char3 a b c d e f g
(integer) 7
127.0.0.1:6379> ltrim char3 1 3
OK
127.0.0.1:6379> lrange char3 0 -1
1) "b"
2) "c"
3) "d"
修改
127.0.0.1:6379> lrange books 0 -1
1) "python"
2) "ruby"
3) "java"
4) "c#"
5) "golong"
127.0.0.1:6379> lset books 2 javaScript
OK
127.0.0.1:6379> lrange books 0 -1
1) "python"
2) "ruby"
3) "javaScript"
4) "c#"
5) "golong"
可以用lset
更改某个位置上的元素,这也是个慢查询,时间复杂度为O(n)。
阻塞操作
blpop
和brpop
在lpop
和rpop
基础上增加了阻塞时间,如果直接获取,发现列表中没有数据,那么会阻塞等待一段时间,如果该段时间内还是无法得到数据,就返回等待时长。若设置的时间是0
的话,即为无限等待。这里需要两个终端做配合。
# 终端1
127.0.0.1:6379> lpop books
(nil)
127.0.0.1:6379> blpop books 5
(nil)
(5.06s)
# 这里需要在终端1 执行blpop后插入一条数据
127.0.0.1:6379> blpop books 20
1) "books"
2) "java"
(4.61s)
# 这里需要在终端1 执行blpop后插入一条数据
127.0.0.1:6379> blpop books 0
1) "books"
2) "python"
(9.66s)
# 除此之外,还可以同时阻塞多个队列,先有数据的那个弹出
127.0.0.1:6379> blpop books schools 0
1) "schools"
2) "hzsy"
(26.75s)
总结
命令 | 解释 |
---|---|
rpush lpush | 弹入数据 |
rpop lpop | 弹出数据 |
brpop blpop | 阻塞弹出数据 |
linsert | 向队列任意位置加入元素 |
lrange | 遍历列表 |
lindex | 查找指定位置上元素 |
llen | 列表长度 |
lrem | 删除多个同一元素 |
ltrim | 截取指定列表 |
lset | 修改列表指定位置元素 |
内部编码
ziplist
:当列表的元素个数小于list-max-ziplist-entries
配置(默认 512 个),同时列表中每个元素的值都小于list-max-ziplist-value
配置时(默认 64 字节),Redis 会选用ziplist
来作为 列表 的内部实现来减少内存的使用。
linkedlist
:当列表类型无法满足ziplist
的条件时,Redis会使用linkedlist
作为列表的内部实现。
使用场景
可以做栈
或者队列
。
还可以利用阻塞功能做消息队列
。
hash (哈希)
Redis 的字典相当于Java语言里面的HashMap,它是无序字典。内部实现结构上同Java的HashMap也是一致的,同样的数组 + 链表二维结构。扩容rehash
的时候,采用渐进式。在rehash
时,保留两个新旧hash结构,查询的时候都查,再根据定时任务,一点点将旧hash上的数据迁移到新的hash上,迁移完毕,旧hash被删除,并收回内存。我们默认key为hashKey
,filed为小key
。
常用命令
设置值
127.0.0.1:6379> hset user name pjjlt
(integer) 1
127.0.0.1:6379> hset user age 26
(integer) 1
127.0.0.1:6379> hset user company gongsi
(integer) 1
获取值
127.0.0.1:6379> hget user name
"pjjlt"
删除field
127.0.0.1:6379> hdel user company
(integer) 1
计算field个数
127.0.0.1:6379> hlen user
(integer) 2
批量设置获取值
127.0.0.1:6379> hmset user name pjjlt age 26 city shijiazhuang
OK
127.0.0.1:6379> hmget user name age
1) "pjjlt"
2) "26"
判断filed是否存在
127.0.0.1:6379> hexists user name
(integer) 1
获取所有filed或者value
127.0.0.1:6379> hkeys user
1) "name"
2) "age"
3) "city"
127.0.0.1:6379> hvals user
1) "pjjlt"
2) "26"
获取所有filed和value
127.0.0.1:6379> hgetall user
1) "name"
2) "pjjlt"
3) "age"
4) "26"
5) "city"
6) "shijiazhuang"
自增
127.0.0.1:6379> hincrby user age -8
(integer) 18
127.0.0.1:6379> hset user scroe 99.6
(integer) 1
127.0.0.1:6379> hincrbyfloat user scroe 0.4
"100"
hincrby
和hincrbyfloat
分别增加或者减少整型与浮点型。
计算值的长度
127.0.0.1:6379> hget user name
"pjjlt"
127.0.0.1:6379> hstrlen user name
(integer) 5
总结
命令 | 解释 |
---|---|
hset | 设置值 |
hget | 获取值 |
hdel | 删除值 |
hlen | 计算field个数 |
hmset | 批量设置值 |
hmget | 批量获取值 |
hexists | 判断field是否存在 |
hkeys | 获取所有field |
hvals | 获取所有value |
hgetall | 获取所有filed和value |
hincrby | 增加整型数值 |
hincrbyfloat | 增加浮点型数值 |
hstrlen | 计算值的长度 |
内部编码
ziplist
:当列表的元素个数小于list-max-ziplist-entries
配置(默认 512 个),同时列表中每个元素的值都小于list-max-ziplist-value
配置时(默认 64 字节),Redis 会选用ziplist
来作为 列表 的内部实现来减少内存的使用。
hashtable:当哈希类型无法满足ziplist
的条件时,Redis会使用hashtable
作为哈希的内部实现,因为此时ziplist
的读写效率会下降,而hashtable
的读写时间复杂度为O(1)。
使用场景
hash
很适合缓存对象,比如商城系统可以存放商品,hash
key为商品id,field
为各种属性,value
为数据。当然string
也可以存放商品,只不过它的value
,时json串,还需要解析,从代码角度和网络代价来讲都不如hash
。
set (集合)
set
相当于Java语言里面的 HashSet,它内部的键值对是无序的唯一的。它的内部实现相当于一个特殊的字典,字典中所有的value
都是一个值NULL。
常用命令
增加元素
127.0.0.1:6379> sadd books java python python ruby java
(integer) 3
sadd
可以添加一个或者多个元素,并且去重。
删除元素
127.0.0.1:6379> srem books python ruby
(integer) 2
srem
可以删除一个或者多个元素。
计算元素个数
127.0.0.1:6379> sadd books python ruby c#
(integer) 3
127.0.0.1:6379> scard books
(integer) 4
判断元素是否在集合中
127.0.0.1:6379> sismember books java
(integer) 1
127.0.0.1:6379> sismember books c
(integer) 0
随机返回一定数量的元素
127.0.0.1:6379> srandmember books 2
1) "java"
2) "ruby"
127.0.0.1:6379> srandmember books 2
1) "c#"
2) "ruby"
随机弹出一个元素
127.0.0.1:6379> spop books
"ruby"
127.0.0.1:6379> scard books
(integer) 3
获取所有元素
127.0.0.1:6379> smembers books
1) "c#"
2) "java"
3) "python"
计算并查集
127.0.0.1:6379> sadd set1 a b c d e
(integer) 5
127.0.0.1:6379> sadd set2 d e f g
(integer) 4
# 计算两个集合交集
127.0.0.1:6379> sinter set1 set2
1) "e"
2) "d"
# 计算两个集合并集
127.0.0.1:6379> sunion set1 set2
1) "g"
2) "a"
3) "d"
4) "e"
5) "c"
6) "f"
7) "b"
# 计算两个集合差集
127.0.0.1:6379> sdiff set1 set2
1) "c"
2) "b"
3) "a"
总结
命令 | 解释 |
---|---|
sadd | 增加元素 |
srem | 删除元素 |
scard | 计算元素个数 |
sismember | 判断元素是否在集合中 |
srandmember | 随机返回一定数量的元素 |
spop | 随机弹出一个元素 |
smembers | 获取所有元素 |
sinter | 计算两个集合交集 |
sunion | 计算两个集合并集 |
sdiff | 计算两个集合差集 |
内部编码
intset
:当集合中的元素都是整数且元素个数小于set-max-intset-entries
配置(默认 512 个)时,Redis会选用intset
来作为集合的内部实现,从而减少内存的使用。
hashtable
:当集合类型无法满足intset
的条件时,Redis会使用hashtable
作为集合的内部实现。
使用场景
利用并查集
可以用于查找用户共同爱好。
利用不可重复性
,可以用于抽奖,保证每个用户只能中一次奖。
zset(有序集合)
zset可能是Redis提供的最为特色的数据结构。它类似于Java的SortedSet和HashMap的结合体,一方面它是一个set,保证了内部value的唯一性,另一方面它可以给每个value赋予一个score,代表这个value的排序权重。
常用命令
# 设置值
127.0.0.1:6379> zadd books 9 java
(integer) 1
127.0.0.1:6379> zadd books 8 python
(integer) 1
127.0.0.1:6379> zadd books 7 golang
(integer) 1
# 查看一定范围内的值
127.0.0.1:6379> zrange books 0 -1
1) "golang"
2) "python"
3) "java"
# 删除某个值
127.0.0.1:6379> zrem books golang
(integer) 1
# 根据score 正序排
127.0.0.1:6379> zrange books 0 -1
1) "python"
2) "java"
# 根据score 倒叙排
127.0.0.1:6379> zrevrange books 0 -1
1) "java"
2) "python"
# 查看元素个数
127.0.0.1:6379> zcard books
(integer) 2
# 查看某元素分值
127.0.0.1:6379> zscore books java
"9"
# 正序排名,从0开始
127.0.0.1:6379> zrank books python
(integer) 0
127.0.0.1:6379> zrank books java
(integer) 1
# 一定范围内scor内的元素
127.0.0.1:6379> zrangebyscore books 0 8.8
1) "python"
总结
命令 | 解释 |
---|---|
zadd | 设置值 |
zrange | 查看一定范围内的值 |
zrem | 删除某个值 |
zrange | 根据score正序排 |
zrevrange | 根据score倒叙排 |
zcard | 查看元素个数 |
zscore | 查看某元素分值 |
zrank | 正序排名,从0开始 |
zrangebyscore | 一定范围内scor内的元素 |
内部编码
zset内部的排序功能是通过「跳跃列表」数据结构来实现的,它的结构非常特殊,也比较复杂。举个例子吧,就好像一个公司,有9个员工,分为3各小组,每个小组算一个小组长(注意小组长还具备员工角色,只不过多了小组长角色)小组长再选出一个技术总监(技术总监同时具备员工、小组长、技术总监角色)
使用场景
适合排名性质的场景,比如微博热搜,某技术网站热门博客等等。
总结不易,小伙伴给个赞再走吧。