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

683 阅读13分钟

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

万丈高楼平地起

reids是键值对结构的NoSql数据库,key都是字符串,常说的数据类型不同,说的都是value图片来自:https://searchdatabase.techtarget.com.cn/7-20218/ 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各小组,每个小组算一个小组长(注意小组长还具备员工角色,只不过多了小组长角色)小组长再选出一个技术总监(技术总监同时具备员工、小组长、技术总监角色)

使用场景

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

总结不易,小伙伴给个赞再走吧。 在这里插入图片描述