Redis ziplist 压缩列表的源码解析

目录
  • 前言
  • 源码解读
  • ziplist 布局
  • entry 节点
  • prelen
  • encoding 编码
  • 总结

前言

相信对使用过 Redis 的人来说,数据类型 List 是不会陌生的吧。大多数人需要实现一个队列时候,首选的就是 List 了。但是其实 Redis 的 List 类型有多种实现方式。这篇文章就是介绍其中一种实现 ziplist - 压缩列表。

源码解读

一如既往,关于 ziplist 的定义和实现还是放在一对文件中,分别是 ziplist.h 和 ziplist.c。在 ziplist.c 文件的头部有着这么一段注释介绍什么是 ziplist。

ziplist 是一个经过特殊编码的双向链表,旨在提高内存效率。 它存储字符串和整数值,其中整数被编码为实际整数而不是一系列字符。 它允许在 O(1) 时间内在列表的任一侧进行推送和弹出操作。 但是,由于每个操作都需要重新分配 ziplist 使用的内存,因此实际复杂性与 ziplist 使用的内存量有关。

从这段话得到:对于不同的数据类型有着不同的编码方式,理解为会对数据进行压缩,从而达到减少内存使用的目的。但是随着存储的 value 数据级增加,使用 ziplist 所付出的代价也随之增加。

ziplist 布局

ziplist 是一个特殊双向链表,不像普通的链表使用前后指针关联在一起,它是存储在连续内存上的。整体的结构布局如下图:

  • zlbytes: 32 位无符号整型,记录 ziplist 整个结构体的占用空间大小。当然了也包括 zlbytes 本身。这个结构有个很大的用处,就是当需要修改 ziplist 时候不需要遍历即可知道其本身的大小。 这个 SDS 中记录字符串的长度有相似之处,这些好的设计往往在平时的开发中可以采纳一下。
  • zltail: 32 位无符号整型, 记录整个 ziplist 中最后一个 entry 的偏移量。所以在尾部进行 POP 操作时候不需要先遍历一次。
  • zllen: 16 位无符号整型, 记录 entry 的数量, 所以只能表示 2^16。但是 Redis 作了特殊的处理:当实体数超过 2^16 ,该值被固定为 2^16 - 1。 所以这种时候要知道所有实体的数量就必须要遍历整个结构了。
  • entry: 真正存数据的结构。
  • zlend: 8 位无符号整型, 固定为 255 。为 ziplist 的结束标识。

entry 节点

每个 entry 都包含两条信息的元数据为前缀。 第一元数据用来存储前一个 entry 的长度,以便能够从后向前遍历列表。 第二元数据是表示 entry 的编码形式。 用来表示 entry 类型,整数或字符串,在字符串的情况下,它还表示字符串有效的长度。

所以一个完整的 ziplist 是这样存储的:

prelen

记录前一个 entry 的长度。若前一个 entry 的长度小于 254 , 则使用 1 个字节的 8 位无符号整数来表示。

若前一个 entry 长度大于等于 254,则使用 5 个字节来表示。第 1 个字节固定为 254 (FE) 作为标识,剩余 4 字节则用来表示前一个 entry 的实际大小。

所以两种情况下的 entry 结构如下所示:

1. 前一个 entry 大小不超过 253。
<prevlen from 0 to 253> <encoding> <entry>
2. 前一个 entry 大小超过 253。
0xFE <4 bytes unsigned little endian prevlen> <encoding> <entry>

encoding 编码

entry 的编码字段取决于具体值的内容,分为字符串、数字两种类型单独处理。

一、当 entry 是字符串时,有 3 种编码方式。编码第 1 个字节的前 2 位将保存用于存储字符串长度的编码类型,后面是字符串的实际长度。

1. 长度小于或等于 63 字节(6 位)的字符串值。 “pppppp”表示无符号的 6 位数据长度。
|00pppppp| - 1 byte
2. 长度小于或等于 16383 字节(14 位)的字符串值。14 位的数据采用  big endian 存储。
big endian 是一种字节序方式,有Little-Endian、Big-Endian两种。
|01pppppp|qqqqqqqq| - 2 bytes
3. 长度大于或等于 16384 字节的字符串值。
采用 big endian 存储且可表示的字符串长度最大2^32-1,所以第一个字节没有用到,所以低6位没有用,所以都是0。
|10000000|qqqqqqqq|rrrrrrrr|ssssssss|tttttttt| - 5 bytes 

二、当 entry 是整数时,有 6 种编码方式。前 2 位都固定为 1,接下来的 2 位用于指定将在此标头后存储哪种类型的整数。

与 ziplist 标头一样,所有整数都以 Little-Endian 序表示,即使此代码是在 Big-Endian 系统中编译的。

1. 整数编码为 int16_t(2 字节)。
|11000000| - 3 bytes
2. 整数编码为int32_t(4个字节)。
|11010000| - 5 bytes
3. 整数编码为 int64_t(8 字节)。
|11100000| - 9 bytes
4. 整数编码为24位带符号(3个字节)。
|11110000| - 4 bytes
5. 整数编码为 8 位有符号(1 字节)。
|11111110| - 2 bytes
6. 0到12的无符号整数。编码后的值实际上是1到13,因为0000和1111不能用,所以要从编码后的4位值中减去1才能得到正确的值。
|1111xxxx| - (with xxxx between 0001 and 1101) immediate 4 bit integer

三、结尾编码标识

1. 表示 ziplist 结尾的标识。
|11111111|

总结

  • ziplist 为了节省内存,采用了紧凑的连续存储。所以在修改操作下并不能像一般的链表那么容易,需要从新分配新的内存,然后复制到新的空间。
  • ziplist 是一个双向链表,可以在时间复杂度为O(1)从下头部、尾部进行pop或push。
  • 可能会出现连锁更新现象。

其实使用中并没有直接操作这种数据结构,但是可以设置何种情况下使用它。可以在 Redis 的配置文件中进行设置。

如有以下可选设置项:

  • hash-max-ziplist-entries:hash 类型元素数量超过指定数据后时候。使用 hash 存储, 否则使用压缩表。
  • hash-max-ziplist-value: hash 类型元素长度超过指定数据后时候。 使用 hash 存储,否则使用压缩链表。
  • zset-max-ziplist-entries:zset 类型 压缩列表 ziplist 最大限制元素数。超过指定值将会使用跳表 skiplist + dict 来存储。
  • zset-max-ziplist-value:set 类型 压缩列表 ziplist 最大限制大小。超过指定将会使用跳表 skiplist+dict 来存储。

到此这篇关于Redis ziplist 压缩列表的源码解析的文章就介绍到这了,更多相关Redis ziplist 压缩列表内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • redis数据结构之压缩列表

    目录 压缩列表是列表键和哈希键的底层实现之一.当一个列表键只包含少量列表项,并且每个列表项要么就是小整数,要么就是长度比较短的字符串,redis就会使用压缩列表来做列表键的底层实现 当一个哈希键只包含少量键值对,并且每个键值对的键和值要么就是小整数值,要么就是长度比较短的字符串,那么Redis就会使用压缩列表来做哈希键的底层实现. 压缩列表是Redis为了节约内存而开发的是由一系列特殊编码的连续内存块组成的顺序型数据结构,一个压缩列表可以包含任意多个节点,每个节点可以保存一个字节数组或者一个整数

  • Redis底层数据结构之dict、ziplist、quicklist详解

    目录 1 Redis dict 1.1 扩缩容的条件 1.2 渐进式rehash操作 2 Redis ziplist 2.1 ziplist结构 2.2 entry结构 3 Redis quicklist 此前我们学习了常见的Reids数据类型,这些数据类型都需要底层的数据结构的支持,现在我们来看看Redis常见的底层数据结构:dict.ziplist.quicklist. 1 Redis dict dict就是"字典"的意思,它是Redis中的一种使用的非常多的底层的数据结构,除了h

  • 压缩列表牺牲速度来节省内存,Redis是膨胀了吗

    正常情况下我们选择使用 Redis 就是为了提升查询速度,然而让人意外的是,Redis 当中却有一种比较有意思的数据结构,这种数据结构通过牺牲部分读写速度来达到节省内存的目的,这就是 ziplist(压缩列表),Redis 为什么要这么做呢?难道真的是觉得自己的速度太快了,牺牲一点速度也不影响吗? 什么是压缩列表 ziplist 是为了节省内存而设计出来的一种数据结构.ziplist 是由一系列特殊编码组成的连续内存块的顺序型数据结构,一个 ziplist 可以包含任意多个 entry,而每一个

  • redis源码分析教程之压缩链表ziplist详解

    前言 压缩列表(ziplist)是由一系列特殊编码的内存块构成的列表,它对于Redis的数据存储优化有着非常重要的作用.这篇文章总结一下redis中使用非常多的一个数据结构压缩链表ziplist.该数据结构在redis中说是无处不在也毫不过分,除了链表以外,很多其他数据结构也是用它进行过渡的,比如前面文章提到的SortedSet.下面话不多说了,来一起看看详细的介绍吧. 一.压缩链表ziplist数据结构简介 首先从整体上看下ziplist的结构,如下图: 压缩链表ziplist结构图 可以看出

  • 详解redis数据结构之压缩列表

     详解redis数据结构之压缩列表 redis使用压缩列表作为列表键和哈希键的底层实现之一.当一个列表键只包含少量的列表项,并且每个列表项都是由小整数值或者是短字符串组成,那么redis就会使用压缩列表存储列表项:同理,当一个哈希表包含的键值对都是由小整数值或者是短字符串组成,并且存储的键值对数目不多时,redis也会使用压缩列表来存储哈希表.以下是压缩列表存储结构: zlbytes长度为4个字节,记录了整个压缩列表所占用的字节数 zltail长度为4个字节,记录了压缩列表起始位置到压缩列表尾节

  • redis专属链表ziplist的使用

    目录 问题抛出 结构设计 实际节点 基本操作 增 问题抛出 用过 Python 的列表吗?就是那种可以存储任意类型数据的,支持随机读取的数据结构. 没有用过的话那就没办法了. 本质上这种列表可以使用数组.链表作为其底层结构,不知道Python中的列表是以什么作为底层结构的. 但是redis的列表既不是用链表,也不是用数组作为其底层实现的,原因也显而易见:数组不方便,弄个二维的?柔性的?怎么写?链表可以实现,通用链表嘛,数据域放 void* 就可以实现列表功能.但是,链表的缺点也很明显,容易造成内

  • Redis ziplist 压缩列表的源码解析

    目录 前言 源码解读 ziplist 布局 entry 节点 prelen encoding 编码 总结 前言 相信对使用过 Redis 的人来说,数据类型 List 是不会陌生的吧.大多数人需要实现一个队列时候,首选的就是 List 了.但是其实 Redis 的 List 类型有多种实现方式.这篇文章就是介绍其中一种实现 ziplist - 压缩列表. 源码解读 一如既往,关于 ziplist 的定义和实现还是放在一对文件中,分别是 ziplist.h 和 ziplist.c.在 ziplis

  • Redis源码解析:集群手动故障转移、从节点迁移详解

    一:手动故障转移 Redis集群支持手动故障转移.也就是向从节点发送"CLUSTER  FAILOVER"命令,使其在主节点未下线的情况下,发起故障转移流程,升级为新的主节点,而原来的主节点降级为从节点. 为了不丢失数据,向从节点发送"CLUSTER  FAILOVER"命令后,流程如下: a:从节点收到命令后,向主节点发送CLUSTERMSG_TYPE_MFSTART包:          b:主节点收到该包后,会将其所有客户端置于阻塞状态,也就是在10s的时间内

  • 详解Redis 缓存删除机制(源码解析)

    删除的范围 过期的 key 在内存满了的情况下,如果继续执行 set 等命令,且所有 key 都没有过期,那么会按照缓存淘汰策略选中的 key 过期删除 redis 中设置了过期时间的 key 会单独存储一份 typedef struct redisDb { dict *dict; // 所有的键值对 dict *expires; //设置了过期时间的键值对 // ... } redisDb; 设置有效期 Redis 中有 4 个命令可以给 key 设置过期时间,分别是 expire pexpi

  • Java源码解析之GenericDeclaration详解

    学习别人实现某个功能的设计思路,来提高自己的编程水平.话不多说,下面进入正题. GenericDeclaration 可以声明类型变量的实体的公共接口,也就是说,只有实现了该接口才能在对应的实体上声明(定义)类型变量,这些实体目前只有三个:Class(类).Construstor(构造器).Method(方法)(详见:Java源码解析之TypeVariable详解 源码 public interface GenericDeclaration { //获得声明列表上的类型变量数组 public T

  • .properties文件读取及占位符${...}替换源码解析

    前言 我们在开发中常遇到一种场景,Bean里面有一些参数是比较固定的,这种时候通常会采用配置的方式,将这些参数配置在.properties文件中,然后在Bean实例化的时候通过Spring将这些.properties文件中配置的参数使用占位符"${}"替换的方式读入并设置到Bean的相应参数中. 这种做法最典型的就是JDBC的配置,本文就来研究一下.properties文件读取及占位符"${}"替换的源码,首先从代码入手,定义一个DataSource,模拟一下JDB

  • Spring SpringMVC在启动完成后执行方法源码解析

    关键字:spring容器加载完毕做一件事情(利用ContextRefreshedEvent事件) 应用场景:很多时候我们想要在某个类加载完毕时干某件事情,但是使用了spring管理对象,我们这个类引用了其他类(可能是更复杂的关联),所以当我们去使用这个类做事情时发现包空指针错误,这是因为我们这个类有可能已经初始化完成,但是引用的其他类不一定初始化完成,所以发生了空指针错误,解决方案如下: 1.写一个类继承spring的ApplicationListener监听,并监控ContextRefresh

  • Android源码解析之截屏事件流程

    今天这篇文章我们主要讲一下Android系统中的截屏事件处理流程.用过android系统手机的同学应该都知道,一般的android手机按下音量减少键和电源按键就会触发截屏事件(国内定制机做个修改的这里就不做考虑了).那么这里的截屏事件是如何触发的呢?触发之后android系统是如何实现截屏操作的呢?带着这两个问题,开始我们的源码阅读流程. 我们知道这里的截屏事件是通过我们的按键操作触发的,所以这里就需要我们从android系统的按键触发模块开始看起,由于我们在不同的App页面,操作音量减少键和电

  • 详解无限滚动插件vue-infinite-scroll源码解析

    最近在项目中遇到一个需求,有一个列表需要滚动加载,类似于微博的无限滚动.当时第一反应时监听滚动事件,在判断滚动到达底部时加载下一页,同时心里也清楚,监听滚动事件需要做好截流.顺手搜索了下发现有一个现成的插件vue-infinite-scroll,用法也很简单,于是乎就用了起来. 需求上线后,对它的实现挺好奇的,于是研究了一番源码,这篇文章就是源码解析笔记. 插件使用方法 这是一个 vue 的指令,按照 github 仓库上的介绍,用法挺简单的,例如: <div class="app&quo

  • .NET Core源码解析配置文件及依赖注入

    写在前面 上篇文章我给大家讲解了ASP.NET Core的概念及为什么使用它,接着带着你一步一步的配置了.NET Core的开发环境并创建了一个ASP.NET Core的mvc项目,同时又通过一个实战教你如何在页面显示一个Content的列表.不知道你有没有跟着敲下代码,千万不要做眼高手低的人哦. 这篇文章我们就会设计一些复杂的概念了,因为要对ASP.NET Core的启动及运行原理.配置文件的加载过程进行分析,依赖注入,控制反转等概念的讲解等. 俗话说,授人以鱼不如授人以渔,所以文章旨在带着大

  • 源码解析JDK 1.8 中的 Map.merge()

    Map 中ConcurrentHashMap是线程安全的,但不是所有操作都是,例如get()之后再put()就不是了,这时使用merge()确保没有更新会丢失. 因为Map.merge()意味着我们可以原子地执行插入或更新操作,它是线程安全的. 一.源码解析 default V merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction) { Objects.requireNonNu

随机推荐