【那些年我们用过的Redis】还记得大明湖畔那些Redis数据吗?

redis五种常用的数据结构为string (字符串)、list (列表)、set (集合)、hash (哈希) 和 zset (有序集合)。小白易读,建议收藏。

万丈高楼平地起

reids是键值对结构的NoSql数据库,key都是字符串,常说的数据类型不同,说的都是value
【那些年我们用过的Redis】还记得大明湖畔那些Redis数据吗?_第1张图片
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),先进先出。

常用命令

向队列任意位置加入元素

刚才演示的rpushlpush都是从两头加入元素,这两个命令不再演示。还可以使用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前后插入了rubyc#

查找元素

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可以遍历列表,参数为startend。这里0 -1,是指从第一个到最后一个,即遍历列表。
lindex查找指定位置的元素,参数是下标值。这个命令是慢查询,需要遍历链表。
llen可以查看列表元素个数。

删除数据

刚才演示的rpoplpop可以弹出一个元素,不再演示。

可以使用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)。

阻塞操作

blpopbrpoplpoprpop基础上增加了阻塞时间,如果直接获取,发现列表中没有数据,那么会阻塞等待一段时间,如果该段时间内还是无法得到数据,就返回等待时长。若设置的时间是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"

hincrbyhincrbyfloat分别增加或者减少整型浮点型

计算值的长度

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很适合缓存对象,比如商城系统可以存放商品,hashkey为商品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各小组,每个小组算一个小组长(注意小组长还具备员工角色,只不过多了小组长角色)小组长再选出一个技术总监(技术总监同时具备员工、小组长、技术总监角色)

使用场景

适合排名性质的场景,比如微博热搜,某技术网站热门博客等等。

总结不易,小伙伴给个赞再走吧。

你可能感兴趣的