服务器之家:专注于服务器技术及软件下载分享
分类导航

Mysql|Sql Server|Oracle|Redis|MongoDB|PostgreSQL|Sqlite|DB2|mariadb|Access|数据库技术|

服务器之家 - 数据库 - Redis - Redis都做了哪些加快速度的设计

Redis都做了哪些加快速度的设计

2021-07-26 16:23双子孤狼 Redis

这篇文章主要介绍了Redis都做了哪些加快速度的设计的相关资料,需要的朋友可以参考下

列表对象是 redis5 种基础数据类型之一,在 redis 3.2 版本之前,列表对象底层存储结构有两种:linkedlist(双端列表)和 ziplist(压缩列表),而在 redis 3.2 版本之后,列表对象底层存储结构只有一种:quicklist(快速列表),难道通过精心设计的 ziplist 最终被 redis 抛弃了吗?

列表对象

 

同字符串对象一样,列表对象到底使用哪一种数据结构来进行存储也是通过编码来进行区分:

 

 

编码属性 描述 object encoding命令返回值
obj_encoding_linkedlist 使用 linkedlist 实现列表对象 linkedlist
obj_encoding_ziplist 使用 ziplist 实现列表对象 ziplist
obj_encoding_quicklist 使用 quicklist 实现列表对象 quicklist

 

linkedlist

linkedlist 是一个双向列表,每个节点都会存储指向上一个节点和指向下一个节点的指针。linkedlist 因为每个节点之间的空间是不连续的,所以可能会造成过多的内存空间碎片。

linkedlist存储结构

链表中每一个节点都是一个 listnode 对象(源码 adlist.h 内),不过需要注意的是,列表中的 value 其实也是一个字符串对象,其他几种数据类型其内部最终也是会嵌套字符串对象,字符串对象也是唯一一种会被其他对象引用的基本类型:

?
1
2
3
4
5
typedef struct listnode {
  struct listnode *prev;//前一个节点
  struct listnode *next;//后一个节点
  void *value;//值(字符串对象)
} listnode;

然后会将其再进行封装成为一个 list 对象(源码 adlist.h 内):

?
1
2
3
4
5
6
7
8
typedef struct list {
  listnode *head;//头节点
  listnode *tail;//尾节点
  void *(*dup)(void *ptr);//节点值复制函数
  void (*free)(void *ptr);//节点值释放函数
  int (*match)(void *ptr, void *key);//节点值对比函数
  unsigned long len;//节点数量
} list;

redis 中对 linkedlist 的访问是以 null 值为终点的,因为 head 节点的 prev 节点为 nulltail 节点的 next 节点也为 null,所以从头节点开始遍历,当发现 tailnull 时,则可以认为已经到了列表末尾。

当我们设置一个列表对象时,在 redis 3.2 版本之前我们可以得到如下存储示意图:

Redis都做了哪些加快速度的设计

ziplist

 

压缩列表在前面已经介绍过,想要详细了解的可以点击这里

linkedlist 和 ziplist 的选择

 

redis3.2 之前,linkedlistziplist 两种编码可以进选择切换,如果需要列表使用 ziplist 编码进行存储,则必须满足以下两个条件:

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

一旦不满足这两个条件的任意一个,则会使用 linkedlist 编码进行存储。

ps:这两个条件可以通过参数 list-max-ziplist-valuelist-max-ziplist-entries 进行修改。

这两种列表能在特定的场景下发挥各自的作用,应该来说已经能满足大部分需求了,然后 redis 并不满足于此,于是一场改革引发了,quicklist 横空出世。

quicklist

 

redis 3.2 版本之后,为了进一步提升 redis 的性能,列表对象统一采用 quicklist 来存储列表对象。quicklist存储了一个双向列表,每个列表的节点是一个 ziplist,所以实际上 quicklist 并不是一个新的数据结构,它就是linkedlistziplist 的结合,然后被命名为快速列表。

quicklist 内部存储结构

quicklist 中每一个节点都是一个 quicklistnode 对象,其数据结构定义如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
typedef struct quicklistnode {
  struct quicklistnode *prev;//前一个节点
  struct quicklistnode *next;//后一个节点
  unsigned char *zl;//当前指向的ziplist或者quicklistlzf
  unsigned int sz;//当前ziplist占用字节
  unsigned int count : 16;//ziplist中存储的元素个数,16字节(最大65535个)
  unsigned int encoding : 2; //是否采用了lzf压缩算法压缩节点 1:raw 2:lzf
  unsigned int container : 2; //存储结构,none=1, ziplist=2
  unsigned int recompress : 1; //当前ziplist是否需要再次压缩(如果前面被解压过则为true,表示需要再次被压缩)
  unsigned int attempted_compress : 1;//测试用
  unsigned int extra : 10; //后期留用
} quicklistnode;

然后各个 quicklistnode 就构成了一个快速列表 quicklist

?
1
2
3
4
5
6
7
8
typedef struct quicklist {
  quicklistnode *head;//列表头节点
  quicklistnode *tail;//列表尾节点
  unsigned long count;//ziplist中一共存储了多少元素,即:每一个quicklistnode内的count相加
  unsigned long len; //双向链表的长度,即quicklistnode的数量
  int fill : 16;//填充因子
  unsigned int compress : 16;//压缩深度 0-不压缩
} quicklist;

根据这两个结构,我们可以得到 redis 3.2 版本之后的列表对象的一个存储结构示意图:

Redis都做了哪些加快速度的设计

quicklist 的 compress 属性

compress 是用来表示压缩深度,ziplist 除了内存空间是连续之外,还可以采用特定的 lzf 压缩算法来将节点进行压缩存储,从而更进一步的节省空间,压缩深度可以通过参数 list-compress-depth 控制:

0:不压缩(默认值)
1:首尾第1个元素不压缩
2:首位前2个元素不压缩
3:首尾前3个元素不压缩以此类推

注意:之所以采取这种压缩两端节点的方式是因为很多场景都是两端的元素访问率最高的,而中间元素访问率相对较低,所以在实际使用时,我们可以根据自己的实际情况选择是否进行压缩,以及具体的压缩深度。

quicklistnode 的 zl 指针

zl 指针默认指向了 ziplist,上面提到 quicklistnode 中有一个 sz 属性记录了当前 ziplist 占用的字节,不过这仅仅限于当前节点没有被压缩(通过lzf 压缩算法)的情况,如果当前节点被压缩了,那么被压缩节点的 zl 指针会指向另一个对象 quicklistlzf,而不会直接指向 ziplistquicklistlzf 是一个 4+n 字节的结构:

?
1
2
3
4
typedef struct quicklistlzf {
  unsigned int sz;// lzf大小,占用4字节
  char compressed[];//被压缩的内容,占用n字节
} quicklistlzf;

quicklist 对比原始两种编码的改进

quicklist 同样采用了 linkedlist 的双端列表特性,然后 quicklist 中的每个节点又是一个 ziplist,所以quicklist 就是综合平衡考虑了 linkedlist 容易产生空间碎片的问题和 ziplist 的读写性能两个维度而设计出来的一种数据结构。使用 quicklist 需要注意以下 2 点:

如果 ziplist 中的 entry 个数过少,最极端情况就是只有 1entry 的压缩列表,那么此时 quicklist 就相当于退化成了一个普通的 linkedlist。如果 ziplist 中的 entry 过多,那么也会导致一次性需要申请的内存空间过大(ziplist 空间是连续的),而且因为 ziplist 本身的就是以时间换空间,所以会过多 entry 也会影响到列表对象的读写性能。

ziplist 中的 entry 个数可以通过参数 list-max-ziplist-size 来控制:

list-max-ziplist-size 1

注意:这个参数可以配置正数也可以配置负数。正数表示限制每个节点中的 entry 数量,如果是负数则只能为 -1~-5,其代表的含义如下:

-1:每个 ziplist 最多只能为 4kb

-2:每个 ziplist 最多只能为 8kb

-3:每个 ziplist 最多只能为 16kb

-4:每个 ziplist 最多只能为 32kb

-5:每个 ziplist 最多只能为 64kb

列表对象常用操作命令

 

lpush key value1 value2:将一个或者多个 value 插入到列表 key 的头部,key 不存在则创建 keyvalue2value1 之后)。

  • lpushx key value1 value2:将一个或者多个 value 插入到列表 key 的头部,key 不存在则不做任何处理(value2value1 之后)。
  • lpop key:移除并返回 key 值的列表头元素。
  • rpush key value1 value2:将一个或者多个 value 插入到列表 key 的尾部,key 不存在则创建 keyvalue2value1 之后)。
  • rpushx key value1 vaue2:将一个或者多个 value 插入到列表 key 的尾部,key 不存在则不做任何处理(value2value1 之后)。
  • rpop key:移除并返回列表 key 的尾元素。
  • llen key:返回列表 key 的长度。
  • lindex key index:返回列表 key 中下标为 index 的元素。index 为正数(从 0 开始)表示从队头开始算,index 为负数(从-1开始)则表示从队尾开始算。
  • lrange key start stop:返回列表 key 中下标 [start,end] 之间的元素。
  • lset key index value:将 value 设置到列表 key 中指定 index 位置,key 不存在或者 index 超出范围则会报错。 ltrim key start end:截取列表中 [start,end] 之间的元素,并替换原列表保存。

了解了操作列表对象的常用命令,我们就可以来验证下前面提到的列表对象的类型和编码了,在测试之前为了防止其他 key 值的干扰,我们先执行 flushall 命令清空 redis 数据库。

接下来依次输入命令:

lpush name zhangsan type name object encoding name

Redis都做了哪些加快速度的设计

可以看到,通过 type 命令输出的是 list,说明当前 name 存的是一个列表对象,并且编码是 quicklist(示例中用的是 5.0.5 版本)。

总结

 

本文主要介绍了 redis5 种常用数据类型中的 列表对象,并介绍了底层的存储结构 quicklist,并分别对旧版本的两种底层数据 linkedlistziplist 进行了分析对比得出了为什么 redis 最终要采用 quicklist 来存储列表对象。

到此这篇关于redis都做了哪些加快速度的设计的文章就介绍到这了,更多相关redis 加快速度的设计内容请搜索服务器之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持服务器之家!

原文链接:https://www.cnblogs.com/lonely-wolf/p/14290336.html

延伸 · 阅读

精彩推荐
  • Redis关于Redis数据库入门详细介绍

    关于Redis数据库入门详细介绍

    大家好,本篇文章主要讲的是关于Redis数据库入门详细介绍,感兴趣的同学赶快来看一看吧,对你有帮助的话记得收藏一下,方便下次浏览...

    沃尔码6982022-01-24
  • Redis详解三分钟快速搭建分布式高可用的Redis集群

    详解三分钟快速搭建分布式高可用的Redis集群

    这篇文章主要介绍了详解三分钟快速搭建分布式高可用的Redis集群,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,...

    万猫学社4502021-07-25
  • Redisredis缓存存储Session原理机制

    redis缓存存储Session原理机制

    这篇文章主要为大家介绍了redis缓存存储Session原理机制详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪...

    程序媛张小妍9252021-11-25
  • Redis如何使用Redis锁处理并发问题详解

    如何使用Redis锁处理并发问题详解

    这篇文章主要给大家介绍了关于如何使用Redis锁处理并发问题的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用Redis具有一定的参考学习...

    haofly4522019-11-26
  • RedisRedis 6.X Cluster 集群搭建

    Redis 6.X Cluster 集群搭建

    码哥带大家完成在 CentOS 7 中安装 Redis 6.x 教程。在学习 Redis Cluster 集群之前,我们需要先搭建一套集群环境。机器有限,实现目标是一台机器上搭建 6 个节...

    码哥字节15752021-04-07
  • RedisRedis集群的5种使用方式,各自优缺点分析

    Redis集群的5种使用方式,各自优缺点分析

    Redis 多副本,采用主从(replication)部署结构,相较于单副本而言最大的特点就是主从实例间数据实时同步,并且提供数据持久化和备份策略。...

    优知学院4082021-08-10
  • RedisRedis Template实现分布式锁的实例代码

    Redis Template实现分布式锁的实例代码

    这篇文章主要介绍了Redis Template实现分布式锁,需要的朋友可以参考下 ...

    晴天小哥哥2592019-11-18
  • Redis《面试八股文》之 Redis十六卷

    《面试八股文》之 Redis十六卷

    redis 作为我们最常用的内存数据库,很多地方你都能够发现它的身影,比如说登录信息的存储,分布式锁的使用,其经常被我们当做缓存去使用。...

    moon聊技术8182021-07-26