Redis中数据结构和编码详细图解(应用场景及优缺点)

专业术语

sds:simple dynamic string 简单动态字符串,redis自己开发的一个字符串的抽象类型

embstr:embedded sds string embstr编码的SDS,与SDS区别在于内存仅需要申请一次,而SDS需要申请两次。适用于短字符串,优点是效率高

Redis 对象结构

Redis 五种对象类型

redis对象数据结构如图所示:
《Redis中数据结构和编码详细图解(应用场景及优缺点)》
《Redis中数据结构和编码详细图解(应用场景及优缺点)》

每一个redis对象都用一个key进行存储,在redis对象中有对象类型和编码,对象类型和编码的对应关系如下:
《Redis中数据结构和编码详细图解(应用场景及优缺点)》

string 字符串类型

应用场景:
5中类型中最简单、常用用的类型,扩展性是非常高得

  1. key是string,value可以是json,常规key-value缓存
  2. 可以用于一些计数器,比如文章点赞数

编码选择:
REDIS_ENCODING_INT:如果value是long类型的整数,则使用此编码
REDIS_ENCODING_RAW:专门保存长字符串的编码,如果value长度>=40(redis-3.3),则使用的是raw编码进行存储。

  • 不足:raw需要调用两次的内存分配函数来分别创建redisObjectsdshdr

REDIS_ENCODING_EMBSTR:专门保存短字符串的编码,如果value长度<40,则使用embstr编码,属于紧凑型的数据结构

  • 优点只需要一次内存分配,数据比较小的时候使用的是这种编码,数据更紧凑,效率更高
  • 缺点:没有提供修改的函数,所以是只读的,如果要对此编码数据进行修改,会变成raw再执行修改,然后结束

底层数据结构:

string的int编码:

《Redis中数据结构和编码详细图解(应用场景及优缺点)》
在ptr指针中直接指向long类型整数

string的raw编码

《Redis中数据结构和编码详细图解(应用场景及优缺点)》
ptr指向一个redis自定义的string类型->sdshdr,然后在buf缓冲中指向一个字符数组

string的embstr编码

《Redis中数据结构和编码详细图解(应用场景及优缺点)》
于raw类似,只不过在所有属性都和RedisObject在一块连续的内存空间上,所以它只需要进行一次的内存申请,也代表着它很快!

string 相关命令

命令行含义
set key value赋值key的值为value
get key获取key的value值
del key删除key
expire key seconds设置key在seconds秒后过期
setex key seconds value赋值key的值为value,在seconds秒后过期
ttl key查看key还有多久过期
setnx key value如果key不存在,才新增key和value
strlen key计算指定key的值的长度
incr key加1
incrby key numbers指定增加值,numbers可以是负值
mset key1 value1 key2 value2 …批量添加
mget key1 key2 key3 …批量获取

list 列表类型

应用场景:

  1. 各种列表, twitter的关注列表、粉丝列表等,最新消息排行
  2. 消息队列,可以利用Lists的PUSH操作,将任务存在Lists中,然后工作线程再用POP操作将任务取出执行。

编码选择:
当列表对象同时满足以下两个条件时,列表对象使用ziplist进行存储,否则使用linkedlist存储

  1. 列表对象保存的所有字符串元素的长度小于64字节
  2. 列表对象保存的元素数量小于512个

list的zpilist编码

《Redis中数据结构和编码详细图解(应用场景及优缺点)》
ptr指向一个压缩列表,所有的压缩列表都类似数据结构

list的linkedlist编码

《Redis中数据结构和编码详细图解(应用场景及优缺点)》
双向无循环的链表,无循环代表头和尾都指向NULL,不会形成一个循环,优点和java的链表一样,插入快但索引慢

list 相关命令

命令行含义
lpush key value1 value2左侧插入value
rpush key value1 value2右侧插入value
lpop key左侧弹出value
rpop key右侧弹出value
llen key查看key的长度
lindex key index查看列表中某个index对应的value值
setnx key value如果key不存在,才新增key和value
lrange key startIndex endIndex查看指定元素,下标从0开始,-1为倒数第一个
ltrim key startIndex endIndex让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除,下标同上

set 集合类型

应用场景:

  1. 增加的时候可以保证去重,比如文章中用户每次点赞的时候不允许重复

编码选择:
同时符合以下两种情况时,使用的是intset编码,否则采用hashtable编码

  1. 集合对象保存的所有元素都是整数值
  2. 集合对象保存的所有元素<=512个

set的intset编码(intset底层为整形集合实现)

《Redis中数据结构和编码详细图解(应用场景及优缺点)》
intset编码与raw类似,只不过将字符数组换成了整形数组

set的hashtable编码(底层是字典,每个键都是字符串对象。字典对应的值为NULL)

《Redis中数据结构和编码详细图解(应用场景及优缺点)》
set集合类型下使用了hashtable,那么它指向的value保存什么呢?
看图中可以看出,所有的数据都指向了NULL

set 相关命令

命令行含义
sadd key value1 value2添加元素到集合中
smembers key查看集合中的所有元素
sismember key value查看value是否在集合中
scard key查询集合的长度
spop key取出集合中的一个元素
del key删除集合

zset 有序集合类型

应用场景:
一些需要对存放的数据进行排序,比如正序、倒序;不允许重复的成员,后面的数据会覆盖前面的数据

  1. 根据时间排序的新闻列表
  2. csdn的访问量排行榜

编码选择:
当zset满足以下两个条件的时候,使用ziplist,否则使用skiplist

  1. 保存的元素个数小于128个
  2. 保存的所有元素大小都小于64字节

zset的ziplist编码

《Redis中数据结构和编码详细图解(应用场景及优缺点)》
一样使用ziplist压缩列表,在中间数据的部分保存的时key-value,连续存储的结构

zset的skiplist编码

typedef struct zset{ 
     // 跳表
     zskiplist *zsl;
     // 字典
     dict *dice;
} zset;

《Redis中数据结构和编码详细图解(应用场景及优缺点)》
使用字典+跳表的方式,将字典和跳表两种数据结构组合
其中跳表通过在每个节点中(基于层和跨度等)维持多个指向其它节点的指针来实现快速访问
更多参考:Redis 源码分析(七) :skiplist

zset有序集合单独使用字典或跳表中的一种数据结构就可以实现,那么为什么要使用两者的组合呢?
  • 字典的优势在于如果要查找成员的分值,时间复杂度是O(1),但是字典是以无序的方式保存集合元素,所以每次范围操作的时候需要进行排序
  • 跳表的优势在于如果要执行范围操作,不需要进行排序,可以直接获取,但是跳表如果是进行查找操作,则时间复杂度为O(logN)
  • 因此Redis使用两种数据结构的组合,扬长避短的方式来共同实现有序集合
zset有序集合使用两种数据结构会不会造成数据冗余?

不会,两种数据结构会通过指针来共享相同数据元素的成员和分值,所以不会产生重复的成员和分值而造成内存的浪费。

zset 相关命令

命令行含义
zadd key value1 score1 value2 score2添加元素到有序集合中
zscore key value查看key的score值,输出score>=负无穷,score<=正无穷的所有元素
zrange key 0 -1正序输出
zrangebyscore key -inf +inf正序输出
zrevrange key 0 -1倒序输出
zcard key查看key中的元素个数
zrangebyscore key indexStart endStart获得key中score>=indexStart 且score<=endStart的元素,正序排列
zrevrangebyscore key indexStart endStart同上,倒序排列
zrem key value删除key中的元素value

hash 字典类型

应用场景:

  1. 存放结构化数据,比如user对象,修改时不需要反序列化可以直接修改

编码选择:
同时满足以下两种情况,则是zpilist,否则就是hashtable
3. hash里面的键值对中,key和value的长度全都小于46个字节
4. hash里面的键值对中,个数小于512个

hash的ziplist编码

《Redis中数据结构和编码详细图解(应用场景及优缺点)》
zpilist压缩链表,数据部分存储的是一个对象的多个属性名和属性值,修改就是直接对所在数据位置修改,而不需要进行反序列化,因为它们是结构化存储,而不是像json一样当作一个string来存储

hash的hashtable编码

《Redis中数据结构和编码详细图解(应用场景及优缺点)》
使用字典的方式存储属性名和属性值,修改也是直接对数据进行修改,不需要反序列化操作

hash 相关命令

命令行含义
hset key name value添加属性元素name和value到key中
hget key name查看key的name值
hmset key name1 value1 name2 value2批量添加key的属性元素
hmget key name1 name2批量获取key的属性元素
hlen key获得key的属性元素个数
hgetall key查询key中的所有元素

思考

为什么redis要自己开发一个字符串类型?

主要有如下几种原因:

  1. C中字符串中没有长度数据,每次都需要进行计算,使用一个元素去遍历,直到\0结束符号,复杂度为O(n)
    而在SDS中的复杂度是O(1),更快
  2. 缓冲区溢出:C字符串是存在缓存冲溢出的情况,前提是在增加元素之前没有进行足够的内存分配
    而SDS动态的执行空间的扩展,API会自动的进行空间的扩展,
  3. 在C字符串中,内存的重新分配是很耗费性能的
    • 空间预分配:
      • 如果对SDS进行修改后,SDS的长度<1M,那么这个时候,预分配的len=free
      • 如果对SDS进行修改后,SDS的长度>=1M,只会按照1M去预分配
    • 惰性释放:
      • 在删除字符串的时候,不及时释放,还继续保留空间,下次使用的时候就不需要再进行申请了
  4. C字符串中字符串是以\0结尾的,并且字符串不能包含空字符串,因此C字符串使用范围较小
    而在SDS中,使用len来判断数据长度,可以保存任意的数据。

总结

文章以图解的方式介绍了Redis中的五种数据类型和8中数据编码,并对其应用场景进行分析举例,相信大家看完对Redis的底层有了一定的了解

8种编码encoding对比总结:

数据编码描述优点缺点
REDIS_ENCODING_INTlong类型的整数占用内存少、速度快仅仅针对long类型整数
REDIS_ENCODING_EMBSTRembstr编码的简单动态字符串一次内存申请与释放不适用于长的字符串
REDIS_ENCODING_RAW简单动态字符串适用于长的字符串存储相对于embstr,需要两次内存申请与释放
REDIS_ENCODING_HT字典读写时间复杂度O(1)占用内存较多
REDIS_ENCODING_LINKEDLIST双向链表元素的个数较多时,访问元素的时间比压缩列表更快每个节点都维护了前置指针、后置指针,占用更多内存且内存不连续容易产生内存碎片
REDIS_ENCODING_ZIPLIST压缩列表空间连续,占用内存少,速度快元素数量大时,访问元素的时间较长
REDIS_ENCODING_INTSET整数集合占用内存远小于hashtable仅仅存整数
REDIS_ENCODING_SKIPLIST跳表+字典不满足ziplist可以使用skiplist来作为内部实现
    原文作者:我思知我在
    原文地址: https://blog.csdn.net/qq_32828253/article/details/110840159
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞