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

导读:本篇文章讲解 Redis中数据结构和编码详细图解(应用场景及优缺点),希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

专业术语

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

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

Redis 对象结构

Redis 五种对象类型

redis对象数据结构如图所示:
在这里插入图片描述
在这里插入图片描述

每一个redis对象都用一个key进行存储,在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编码:

在这里插入图片描述
在ptr指针中直接指向long类型整数

string的raw编码

在这里插入图片描述
ptr指向一个redis自定义的string类型->sdshdr,然后在buf缓冲中指向一个字符数组

string的embstr编码

在这里插入图片描述
于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编码

在这里插入图片描述
ptr指向一个压缩列表,所有的压缩列表都类似数据结构

list的linkedlist编码

在这里插入图片描述
双向无循环的链表,无循环代表头和尾都指向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底层为整形集合实现)

在这里插入图片描述
intset编码与raw类似,只不过将字符数组换成了整形数组

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

在这里插入图片描述
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编码

在这里插入图片描述
一样使用ziplist压缩列表,在中间数据的部分保存的时key-value,连续存储的结构

zset的skiplist编码

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

在这里插入图片描述
使用字典+跳表的方式,将字典和跳表两种数据结构组合
其中跳表通过在每个节点中(基于层和跨度等)维持多个指向其它节点的指针来实现快速访问
更多参考: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编码

在这里插入图片描述
zpilist压缩链表,数据部分存储的是一个对象的多个属性名和属性值,修改就是直接对所在数据位置修改,而不需要进行反序列化,因为它们是结构化存储,而不是像json一样当作一个string来存储

hash的hashtable编码

在这里插入图片描述
使用字典的方式存储属性名和属性值,修改也是直接对数据进行修改,不需要反序列化操作

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_INT long类型的整数 占用内存少、速度快 仅仅针对long类型整数
REDIS_ENCODING_EMBSTR embstr编码的简单动态字符串 一次内存申请与释放 不适用于长的字符串
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://www.bmabk.com/index.php/post/17848.html

(0)
小半的头像小半

相关推荐

极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!