本文首发于公众号:Hunter后端

原文链接:Redis数据结构三之压缩列表

本篇笔记介绍压缩列表。

在 Redis 3.2 版本之前,压缩列表是列表对象、哈希对象、有序集合对象的的底层实现之一。

因为压缩列表本身结构上的一些缺陷,压缩列表这个结构被替换了,但是压缩列表结构本身有一些可取之处,并且替换它的新结构 listpack 与之很相似,所以我们这里还是介绍一下压缩列表的结构和存储

1、压缩列表的结构

压缩列表是 Redis 为了节约内存而开发的,由一个连续内存块组成的顺序型数据结构。

它的组成结构如下:

| zlbytes | zltail | zllen | entry1 | entry2 | ... | entryN | zlend |

压缩列表的英文名是 ziplist,所以它的属性都是 zl 开头。

1. 列表属性介绍

zlbytes

zlbytes 长度为 4 字节,记录整个压缩列表占用的内存字节数

zltail

zltail 长度为 4 字节,记录压缩列表最后一个节点,也就是我们结构示例中的 entryN 到 zlbytes 的地址之间的偏移量。

zllen

zllen 长度为 2 字节,记录的是压缩列表包含的节点数量,也就是结构中的 N。

zlend

zlend 长度为 1 字节,它的值为 0xFF(十进制 255),用于标记压缩列表的末端。

2. 压缩列表节点属性介绍

对于每一个 entry 节点,也就是压缩列表中的元素节点,它的结构示意如下:

| previous_entry_length | encoding | content |

previous_entry_length

previous_entry_length 属性记录的是压缩列表中前一个节点的长度

previous_entry_length 属性本身的长度可以是 1 字节或者 5 字节

如果前一节点的长度小于 254 字节,那么 previous_entry_length 属性的长度为 1 字节,前一个节点的长度保存在这个字节里

如果前一节点的长度大于等于 254 字节,那么 previous_entry_length 属性的长度为 5 字节,第一个字节被设置成 0xFE(也就是 254),之后的四个字节用于前一节点的长度。

通过 previous_entry_length 属性,我们可以通过当前节点的地址和 previous_entry_length 属性,计算出前一个节点的起始地址,压缩列表的从表尾到表头的遍历操作就是使用这个原理一个节点一个节点往前回溯实现的。

encoding

encoding 属性记录了节点的 content 属性所保存数据的类型以及长度。

encoding 的值如果是一字节长,且值的最高位以 11 开头,那么表示是整数编码,表示 content 属性保存着整数值。

encoding 的值为 一字节、两字节、五字节长,且值的最高位为 00、01、10 则表示是字节数组编码

content

content 属性保存的是节点的值,可以是一个字节数组或者整数,值的类型和长度由节点的 encoding 属性决定。

2、 连锁更新

在压缩列表的节点属性中,previous_entry_length 属性的长度是根据前一节点的长度来进行赋值的,如果前一节点的长度小于 254 字节,那么下一节点的 previous_entry_length 属性长度为 1 字节,如果前一节点的长度大于等于 254 字节,那么下一节点的 previous_entry_length 属性为 5 字节。

那么在这种情况下,就存在一种较为极端的情况,那就是压缩列表中每个节点的长度都在 250 - 253 字节之间,这时候,如果在表头插入一个长度大于等于 254 字节的节点,那么相对应的第二个节点的 previous_entry_length 的长度就要从 1 字节变为 4 字节,那么该节点的整体长度就大于等于 254 字节。

依此类推,压缩列表的每一个节点的长度都会产生连锁反应,长度都会逐个变成大于等于 254 字节长度,程序就需要不断地对压缩列表执行空间重分配的操作。

Redis 将这种在特殊情况下产生的连续多次空间扩展操作称为连锁更新。

除了新增节点这种情况,还有一种删除节点也可能造成连锁更新的情况,比如有这么几个节点,big 节点的长度大于等于 254 字节,small 节点长度小于254 字节,e1 到 eN 的节点长度都在 250-253 字节之间。

| zlbytes | zltail | zllen | big | small | entry1 | entry2 | ... | entryN | zlend |

这时候,删除 small 节点,entry1 节点的前节点的长度就会从 250-253 变成大于等于 254,因此 entry1 节点的 previous_entry_length 长度就会变成 5 字节,entry1 整体长度就会大于等于 254 字节,依次之后每个节点都会这样产生连锁更新。

尽管连锁更新的复杂度较高,但它真正造成性能问题的几率是很低的:

首先,压缩列表里要恰好有多个连续的、长度介于 250-253 字节之间的节点,连锁反应才有可能被引发

其次,即使出现连锁更新,但只要被更新的节点数量不多,就不会对性能造成任何影响,比如对只拥有三五个节点的压缩列表进行连锁更新。

3、压缩列表缺点

压缩列表虽然能节约内存,但仍然存在一些缺点:

  • 需要通过遍历操作来查找节点,元素过多时会造成查询效率低下
  • 对压缩列表节点进行新增或者修改时,可能会造成连锁更新的问题

如果想获取更多后端相关文章,可扫码关注阅读:

Redis数据结构三之压缩列表的更多相关文章

  1. Redis学习之ziplist压缩列表源码分析

    一.压缩列表ziplist在redis中的应用 1.做列表键 当一个列表键只包含少量列表项,并且每个列表项要么是小整数,要么是短字符串,那么redis会使用压缩列表作为列表键的底层实现 2.哈希键 当 ...

  2. 图解Redis之数据结构篇——压缩列表

    前言     同整数集合一样压缩列表也不是基础数据结构,而是 Redis 自己设计的一种数据存储结构.它有点儿类似数组,通过一片连续的内存空间,来存储数据.不过,它跟数组不同的一点是,它允许存储的数据 ...

  3. [redis读书笔记] 第一部分 数据结构与对象 压缩列表

    压缩列表是为了节省内存而设计的,是列表键和哈希键的底层实现之一. 压缩列表的逻辑如下,

  4. redis源码之压缩列表ziplist

    压缩列表ziplist1.简介连续,无序的数据结构.压缩列表是 Redis 为了节约内存而开发的, 由一系列特殊编码的连续内存块组成的顺序型(sequential)数据结构. 2.组成 属性 类型 长 ...

  5. redis 底层数据结构 压缩列表 ziplist

    压缩列表是列表键和哈希键的底层实现之一.当一个列表键只包含少量列表项,并且每个列表项要么就是小整数,要么就是长度比较短的字符串,redis就会使用压缩列表来做列表键的底层实现 当一个哈希键只包含少量键 ...

  6. Redis数据结构之压缩列表

    压缩列表是Redis为了节约内存而开发的,由一系列特殊编码的连续内存块组成的顺序型数据结构.一个压缩列表可以包含任意多个节点,每个节点可以保存一个字节数组或者一个整数值. 一.压缩列表结构1. 压缩列 ...

  7. Redis 底层数据结构之压缩列表

    文章参考:<Redis 设计与实现>黄建宏 压缩列表 压缩列表 ziplist 是列表键和哈希键的底层实现之一.当一个列表键只包含少量列表项,并且每个列表项要么就是小整数值,要么就是长度比 ...

  8. Redis压缩列表原理与应用分析

    摘要 Redis是一款著名的key-value内存数据库软件,同时也是一款卓越的数据结构服务软件.它支持字符串.列表.哈希表.集合.有序集合五种数据结构类型,同时每种数据结构类型针对不同的应用场景又支 ...

  9. Redis压缩列表

    此篇文章是主要介绍Redis在数据存储方面的其中一种方式,压缩列表.本文会介绍1. 压缩列表(ziplist)的使用场景 2.如何达到节约内存的效果?3.压缩列表的存储格式 4. 连锁更新的问题  5 ...

  10. redis 笔记01 简单动态字符串、链表、字典、跳跃表、整数集合、压缩列表

    文中内容摘自<redis设计与实现> 简单动态字符串 1. Redis只会使用C字符串作为字面量,在大多数情况下,Redis使用SDS(Simple Dynamic String,简单动态 ...

随机推荐

  1. Python学习笔记--列表来啦!

    列表的定义格式 示例: 列表的下标索引(千万别超出范围!!!) 示例: 正向和反向 取出嵌套列表的元素:(二维数组) 列表的常用的操作方法 查找某元素的下标 示例: 修改元素 插入元素 示例: 追加元 ...

  2. Java8 Optional使用方式

    参考博客:https://blog.csdn.net/zjhred/article/details/84976734

  3. Kattis mapcolouring(状压dp)

    刚知道vj上查看别人代码,看不到汉字...我理解的都注明后边了. #include <bits/stdc++.h> #define ll long long #define met(a, ...

  4. 自己动手从零写桌面操作系统GrapeOS系列教程——16.封装打印字符串函数

    学习操作系统原理最好的方法是自己写一个简单的操作系统. 在上一讲中我们向屏幕打印字符串"GrapeOS"用了十几行汇编代码,如果要输出的字符比较多,这种方法太繁琐了.本讲我们将打印 ...

  5. Java面试——Java基础

    更多内容,移步IT-BLOG 一.JAVA中的几种基本数据类型 Java语言中一共提供了8种原始的数据类型(byte,short,int,long,float,double,char,boolean) ...

  6. markdown---->Typora搭配uPic

    记录一下在mac上面使用Typora和uPic来发表博客的过程,图床用的是阿里Oss.We all, whether we know it or not, are fighting to make t ...

  7. php in_array 遍历,in_array大数组查询性能问题

    问题最近在实现一个项目接口的时候发现当数组过大的时候,数据返回的速度有点慢.接口数据返回最长反应时间2s,经过反复调试发现代码段耗时最长的部分在in_array()函数.解决过程在stackoverf ...

  8. 浅谈$\mathcal{LCT}$初步使用及具体操作

    \(0x01\) 闲话 · \(LCT\)的用途以及具体思路 \(LCT\)是啥?百度一下的话--貌似是一种检查妇科病的东西?Oier的口味可是真不一般啊 咳,其实在我最近只是浅浅地学了一部分的基础上 ...

  9. ACM-NEFUOJ-P210畅通工程并查集

    题目:我已经明示到这个程度了你还不用并查集? #include<bits/stdc++.h> using namespace std; const int MAXN=1010; int F ...

  10. 2.@Param()注解

    前言 在咋们的mapper层中,@Param()注解是很常见的,它是专门服务于SQL相关联的mapper接口,它有两个功能:1)多参数传值,2)取别名,替换传值 1.取别名,替换传值 1.1 代码演示 ...